| // Copyright 2020 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "tools/v8windbg/src/object-inspection.h" |
| |
| #include "src/flags/flags.h" |
| #include "tools/v8windbg/base/utilities.h" |
| #include "tools/v8windbg/src/v8-debug-helper-interop.h" |
| #include "tools/v8windbg/src/v8windbg-extension.h" |
| |
| V8CachedObject::V8CachedObject(Location location, |
| std::string uncompressed_type_name, |
| WRL::ComPtr<IDebugHostContext> context, |
| bool is_compressed) |
| : location_(std::move(location)), |
| uncompressed_type_name_(std::move(uncompressed_type_name)), |
| context_(std::move(context)), |
| is_compressed_(is_compressed) {} |
| HRESULT V8CachedObject::Create(IModelObject* p_v8_object_instance, |
| IV8CachedObject** result) { |
| Location location; |
| RETURN_IF_FAIL(p_v8_object_instance->GetLocation(&location)); |
| |
| WRL::ComPtr<IDebugHostContext> context; |
| RETURN_IF_FAIL(p_v8_object_instance->GetContext(&context)); |
| |
| // If the object is of type v8::internal::TaggedValue, and this build uses |
| // compressed pointers, then the value is compressed. Other types such as |
| // v8::internal::Object represent uncompressed tagged values. |
| WRL::ComPtr<IDebugHostType> sp_type; |
| _bstr_t type_name; |
| bool is_compressed = |
| COMPRESS_POINTERS_BOOL && |
| SUCCEEDED(p_v8_object_instance->GetTypeInfo(&sp_type)) && |
| SUCCEEDED(sp_type->GetName(type_name.GetAddress())) && |
| static_cast<const char*>(type_name) == std::string(kTaggedValue); |
| |
| const char* uncompressed_type_name = |
| is_compressed ? kObject : static_cast<const char*>(type_name); |
| |
| *result = WRL::Make<V8CachedObject>(location, uncompressed_type_name, context, |
| is_compressed) |
| .Detach(); |
| return S_OK; |
| } |
| V8CachedObject::V8CachedObject(V8HeapObject heap_object) |
| : heap_object_(std::move(heap_object)), heap_object_initialized_(true) {} |
| |
| V8CachedObject::~V8CachedObject() = default; |
| |
| IFACEMETHODIMP V8CachedObject::GetCachedV8HeapObject( |
| V8HeapObject** pp_heap_object) noexcept { |
| if (!heap_object_initialized_) { |
| heap_object_initialized_ = true; |
| uint64_t tagged_ptr = 0; |
| uint64_t bytes_read; |
| HRESULT hr = sp_debug_host_memory->ReadBytes( |
| context_.Get(), location_, reinterpret_cast<void*>(&tagged_ptr), |
| is_compressed_ ? i::kTaggedSize : sizeof(void*), &bytes_read); |
| // S_FALSE can be returned if fewer bytes were read than were requested. We |
| // need all of the bytes, so check for S_OK. |
| if (hr != S_OK) { |
| std::stringstream message; |
| message << "Unable to read memory"; |
| if (location_.IsVirtualAddress()) { |
| message << " at 0x" << std::hex << location_.GetOffset(); |
| } |
| heap_object_.friendly_name = ConvertToU16String(message.str()); |
| } else { |
| if (is_compressed_) |
| tagged_ptr = ExpandCompressedPointer(static_cast<uint32_t>(tagged_ptr)); |
| heap_object_ = |
| ::GetHeapObject(context_, tagged_ptr, location_.GetOffset(), |
| uncompressed_type_name_.c_str(), is_compressed_); |
| } |
| } |
| *pp_heap_object = &this->heap_object_; |
| return S_OK; |
| } |
| |
| IndexedFieldData::IndexedFieldData(Property property) |
| : property_(std::move(property)) {} |
| |
| IndexedFieldData::~IndexedFieldData() = default; |
| |
| IFACEMETHODIMP IndexedFieldData::GetProperty(Property** property) noexcept { |
| if (!property) return E_POINTER; |
| *property = &this->property_; |
| return S_OK; |
| } |
| |
| V8ObjectKeyEnumerator::V8ObjectKeyEnumerator( |
| WRL::ComPtr<IV8CachedObject>& v8_cached_object) |
| : sp_v8_cached_object_{v8_cached_object} {} |
| V8ObjectKeyEnumerator::~V8ObjectKeyEnumerator() = default; |
| |
| IFACEMETHODIMP V8ObjectKeyEnumerator::Reset() noexcept { |
| index_ = 0; |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8ObjectKeyEnumerator::GetNext(BSTR* key, IModelObject** value, |
| IKeyStore** metadata) noexcept { |
| V8HeapObject* p_v8_heap_object; |
| sp_v8_cached_object_->GetCachedV8HeapObject(&p_v8_heap_object); |
| |
| if (static_cast<size_t>(index_) >= p_v8_heap_object->properties.size()) |
| return E_BOUNDS; |
| |
| auto* name_ptr = p_v8_heap_object->properties[index_].name.c_str(); |
| *key = ::SysAllocString(U16ToWChar(name_ptr)); |
| ++index_; |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8LocalDataModel::InitializeObject( |
| IModelObject* model_object, |
| IDebugHostTypeSignature* matching_type_signature, |
| IDebugHostSymbolEnumerator* wildcard_matches) noexcept { |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8LocalDataModel::GetName(BSTR* model_name) noexcept { |
| return E_NOTIMPL; |
| } |
| |
| IFACEMETHODIMP V8ObjectDataModel::InitializeObject( |
| IModelObject* model_object, |
| IDebugHostTypeSignature* matching_type_signature, |
| IDebugHostSymbolEnumerator* wildcard_matches) noexcept { |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8ObjectDataModel::GetName(BSTR* model_name) noexcept { |
| return E_NOTIMPL; |
| } |
| |
| IFACEMETHODIMP V8ObjectDataModel::ToDisplayString( |
| IModelObject* context_object, IKeyStore* metadata, |
| BSTR* display_string) noexcept { |
| WRL::ComPtr<IV8CachedObject> sp_v8_cached_object; |
| RETURN_IF_FAIL(GetCachedObject(context_object, &sp_v8_cached_object)); |
| V8HeapObject* p_v8_heap_object; |
| RETURN_IF_FAIL(sp_v8_cached_object->GetCachedV8HeapObject(&p_v8_heap_object)); |
| *display_string = ::SysAllocString( |
| reinterpret_cast<const wchar_t*>(p_v8_heap_object->friendly_name.data())); |
| return S_OK; |
| } |
| |
| namespace { |
| |
| // Creates a synthetic object, attaches a parent model, and sets the context |
| // object for that parent data model. Caller is responsible for ensuring that |
| // the parent model's Concepts have been initialized correctly and that the |
| // data model context is of an appropriate type for the parent model. |
| HRESULT CreateSyntheticObjectWithParentAndDataContext( |
| IDebugHostContext* ctx, IModelObject* parent_model, IUnknown* data_context, |
| IModelObject** result) { |
| WRL::ComPtr<IModelObject> value; |
| RETURN_IF_FAIL(sp_data_model_manager->CreateSyntheticObject(ctx, &value)); |
| RETURN_IF_FAIL( |
| value->AddParentModel(parent_model, nullptr, true /*override*/)); |
| RETURN_IF_FAIL(value->SetContextForDataModel(parent_model, data_context)); |
| *result = value.Detach(); |
| return S_OK; |
| } |
| |
| // Creates an IModelObject for a V8 object whose value is represented by the |
| // data in cached_object. This is an alternative to CreateTypedObject for |
| // particularly complex cases (compressed values and those that don't exist |
| // anywhere in memory). |
| HRESULT CreateSyntheticObjectForV8Object(IDebugHostContext* ctx, |
| V8CachedObject* cached_object, |
| IModelObject** result) { |
| // Explicitly add the parent model and data context. On a plain typed object, |
| // the parent model would be attached automatically because we registered for |
| // a matching type signature, and the data context would be set during |
| // V8ObjectDataModel::GetCachedObject. |
| return CreateSyntheticObjectWithParentAndDataContext( |
| ctx, Extension::Current()->GetObjectDataModel(), cached_object, result); |
| } |
| |
| // Creates an IModelObject to represent a field that is not a struct or array. |
| HRESULT GetModelForBasicField(const uint64_t address, |
| const std::u16string& type_name, |
| const std::string& uncompressed_type_name, |
| WRL::ComPtr<IDebugHostContext>& sp_ctx, |
| IModelObject** result) { |
| if (type_name == ConvertToU16String(uncompressed_type_name)) { |
| // For untagged and uncompressed tagged fields, create an IModelObject |
| // representing a normal native data type. |
| WRL::ComPtr<IDebugHostType> type = |
| Extension::Current()->GetTypeFromV8Module(sp_ctx, type_name.c_str()); |
| if (type == nullptr) return E_FAIL; |
| return sp_data_model_manager->CreateTypedObject( |
| sp_ctx.Get(), Location{address}, type.Get(), result); |
| } |
| |
| // For compressed tagged fields, we need to do something a little more |
| // complicated. We could just use CreateTypedObject with the type |
| // v8::internal::TaggedValue, but then we'd sacrifice any other data |
| // that we've learned about the field's specific type. So instead we |
| // create a synthetic object. |
| WRL::ComPtr<V8CachedObject> cached_object = WRL::Make<V8CachedObject>( |
| Location(address), uncompressed_type_name, sp_ctx, |
| /*is_compressed=*/true); |
| return CreateSyntheticObjectForV8Object(sp_ctx.Get(), cached_object.Get(), |
| result); |
| } |
| |
| // Creates an IModelObject representing the value of a bitfield. |
| HRESULT GetModelForBitField(uint64_t address, const uint8_t num_bits, |
| uint8_t shift_bits, const std::u16string& type_name, |
| WRL::ComPtr<IDebugHostContext>& sp_ctx, |
| IModelObject** result) { |
| // Look up the type by name. |
| WRL::ComPtr<IDebugHostType> type = |
| Extension::Current()->GetTypeFromV8Module(sp_ctx, type_name.c_str()); |
| if (type == nullptr) return E_FAIL; |
| |
| // Figure out exactly which bytes contain the bitfield's data. This depends on |
| // platform byte order (little-endian for Windows). |
| constexpr int kBitsPerByte = 8; |
| uint8_t shift_bytes = shift_bits / kBitsPerByte; |
| address += shift_bytes; |
| shift_bits -= shift_bytes * kBitsPerByte; |
| size_t bits_to_read = shift_bits + num_bits; |
| size_t bytes_to_read = (bits_to_read + kBitsPerByte - 1) / kBitsPerByte; |
| |
| uintptr_t value = 0; |
| |
| // V8 guarantees that bitfield structs are no bigger than a single pointer. |
| if (bytes_to_read > sizeof(value)) { |
| std::stringstream message; |
| message << "Fatal v8windbg error: found bitfield struct of " |
| << bytes_to_read << "bytes, which exceeds the supported size of " |
| << sizeof(value); |
| return CreateString(ConvertToU16String(message.str()), result); |
| } |
| |
| uint64_t bytes_read; |
| HRESULT hr = sp_debug_host_memory->ReadBytes(sp_ctx.Get(), address, |
| reinterpret_cast<void*>(&value), |
| bytes_to_read, &bytes_read); |
| |
| // S_FALSE can be returned if fewer bytes were read than were requested. We |
| // need all of the bytes, so check for S_OK. |
| if (hr != S_OK) { |
| std::stringstream message; |
| message << "Unable to read memory at 0x" << std::hex << address; |
| return CreateString(ConvertToU16String(message.str()), result); |
| } |
| |
| // Decode the bitfield. |
| value = (value >> shift_bits) & ((1 << num_bits) - 1); |
| |
| return CreateTypedIntrinsic(value, type.Get(), result); |
| } |
| |
| // Creates an IModelObject to represent the packed fields in a Torque struct. |
| // Note that Torque structs are not C++ structs and do not have any type |
| // definitions in the V8 symbols. |
| HRESULT GetModelForStruct(const uint64_t address, |
| const std::vector<StructField>& fields, |
| WRL::ComPtr<IDebugHostContext>& sp_ctx, |
| IModelObject** result) { |
| WRL::ComPtr<IModelObject> sp_value; |
| RETURN_IF_FAIL( |
| sp_data_model_manager->CreateSyntheticObject(sp_ctx.Get(), &sp_value)); |
| |
| // There's no need for any fancy Concepts here; just add key-value pairs for |
| // each field. |
| for (const StructField& field : fields) { |
| WRL::ComPtr<IModelObject> field_model; |
| if (field.num_bits == 0) { |
| RETURN_IF_FAIL(GetModelForBasicField( |
| address + field.offset, field.type_name, field.uncompressed_type_name, |
| sp_ctx, &field_model)); |
| } else { |
| RETURN_IF_FAIL(GetModelForBitField(address + field.offset, field.num_bits, |
| field.shift_bits, field.type_name, |
| sp_ctx, &field_model)); |
| } |
| RETURN_IF_FAIL( |
| sp_value->SetKey(reinterpret_cast<const wchar_t*>(field.name.c_str()), |
| field_model.Get(), nullptr)); |
| } |
| |
| *result = sp_value.Detach(); |
| return S_OK; |
| } |
| |
| // Creates an IModelObject representing an array of some type that we expect to |
| // be defined in the V8 symbols. |
| HRESULT GetModelForNativeArray(const uint64_t address, |
| const std::u16string& type_name, size_t count, |
| WRL::ComPtr<IDebugHostContext>& sp_ctx, |
| IModelObject** result) { |
| WRL::ComPtr<IDebugHostType> type = |
| Extension::Current()->GetTypeFromV8Module(sp_ctx, type_name.c_str()); |
| if (type == nullptr) return E_FAIL; |
| |
| ULONG64 object_size{}; |
| RETURN_IF_FAIL(type->GetSize(&object_size)); |
| |
| ArrayDimension dimensions[] = { |
| {/*start=*/0, /*length=*/count, /*stride=*/object_size}}; |
| WRL::ComPtr<IDebugHostType> array_type; |
| RETURN_IF_FAIL( |
| type->CreateArrayOf(/*dimensions=*/1, dimensions, &array_type)); |
| |
| return sp_data_model_manager->CreateTypedObject( |
| sp_ctx.Get(), Location{address}, array_type.Get(), result); |
| } |
| |
| // Creates an IModelObject that represents an array of structs or compressed |
| // tagged values. |
| HRESULT GetModelForCustomArray(const Property& prop, |
| WRL::ComPtr<IDebugHostContext>& sp_ctx, |
| IModelObject** result) { |
| // Create the context which should be provided to the indexing and iterating |
| // functionality provided by the parent model. This is instance-specific data, |
| // whereas the parent model object could be shared among many custom arrays. |
| WRL::ComPtr<IndexedFieldData> context_data = |
| WRL::Make<IndexedFieldData>(prop); |
| |
| return CreateSyntheticObjectWithParentAndDataContext( |
| sp_ctx.Get(), Extension::Current()->GetIndexedFieldDataModel(), |
| context_data.Get(), result); |
| } |
| |
| |
| // Creates an IModelObject representing the data in an array at the given index. |
| // context_object is expected to be an object of the form created by |
| // GetModelForCustomArray, meaning its context for the IndexedFieldParent data |
| // model is an IIndexedFieldData containing the description of the array. |
| HRESULT GetModelForCustomArrayElement(IModelObject* context_object, |
| size_t index, IModelObject** object) { |
| // Open a few layers of wrapper objects to get to the Property object that |
| // describes the array. |
| WRL::ComPtr<IUnknown> data_model_context; |
| RETURN_IF_FAIL(context_object->GetContextForDataModel( |
| Extension::Current()->GetIndexedFieldDataModel(), &data_model_context)); |
| WRL::ComPtr<IIndexedFieldData> indexed_field_data; |
| RETURN_IF_FAIL(data_model_context.As(&indexed_field_data)); |
| Property* prop; |
| RETURN_IF_FAIL(indexed_field_data->GetProperty(&prop)); |
| |
| if (index >= prop->length) { |
| return E_BOUNDS; |
| } |
| |
| WRL::ComPtr<IDebugHostContext> sp_ctx; |
| RETURN_IF_FAIL(context_object->GetContext(&sp_ctx)); |
| |
| ULONG64 address = prop->addr_value + index * prop->item_size; |
| |
| switch (prop->type) { |
| case PropertyType::kArray: |
| return GetModelForBasicField(address, prop->type_name, |
| prop->uncompressed_type_name, sp_ctx, |
| object); |
| case PropertyType::kStructArray: |
| return GetModelForStruct(address, prop->fields, sp_ctx, object); |
| default: |
| return E_FAIL; // Only array properties should be possible here. |
| } |
| } |
| |
| } // namespace |
| |
| IFACEMETHODIMP IndexedFieldParent::InitializeObject( |
| IModelObject* model_object, |
| IDebugHostTypeSignature* matching_type_signature, |
| IDebugHostSymbolEnumerator* wildcard_matches) noexcept { |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP IndexedFieldParent::GetName(BSTR* model_name) noexcept { |
| return E_NOTIMPL; |
| } |
| |
| IFACEMETHODIMP IndexedFieldParent::GetDimensionality( |
| IModelObject* context_object, ULONG64* dimensionality) noexcept { |
| *dimensionality = 1; |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP IndexedFieldParent::GetAt(IModelObject* context_object, |
| ULONG64 indexer_count, |
| IModelObject** indexers, |
| IModelObject** object, |
| IKeyStore** metadata) noexcept { |
| if (indexer_count != 1) return E_INVALIDARG; |
| if (metadata != nullptr) *metadata = nullptr; |
| |
| ULONG64 index; |
| RETURN_IF_FAIL(UnboxULong64(indexers[0], &index, /*convert=*/true)); |
| |
| return GetModelForCustomArrayElement(context_object, index, object); |
| } |
| |
| IFACEMETHODIMP IndexedFieldParent::SetAt(IModelObject* context_object, |
| ULONG64 indexer_count, |
| IModelObject** indexers, |
| IModelObject* value) noexcept { |
| return E_NOTIMPL; |
| } |
| |
| IFACEMETHODIMP IndexedFieldParent::GetDefaultIndexDimensionality( |
| IModelObject* context_object, ULONG64* dimensionality) noexcept { |
| *dimensionality = 1; |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP IndexedFieldParent::GetIterator( |
| IModelObject* context_object, IModelIterator** iterator) noexcept { |
| auto indexed_field_iterator{WRL::Make<IndexedFieldIterator>(context_object)}; |
| *iterator = indexed_field_iterator.Detach(); |
| return S_OK; |
| } |
| |
| IndexedFieldIterator::IndexedFieldIterator(IModelObject* context_object) |
| : context_object_(context_object) {} |
| IndexedFieldIterator::~IndexedFieldIterator() = default; |
| |
| IFACEMETHODIMP IndexedFieldIterator::Reset() noexcept { |
| next_ = 0; |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP IndexedFieldIterator::GetNext(IModelObject** object, |
| ULONG64 dimensions, |
| IModelObject** indexers, |
| IKeyStore** metadata) noexcept { |
| if (dimensions > 1) return E_INVALIDARG; |
| |
| WRL::ComPtr<IModelObject> sp_index, sp_value; |
| RETURN_IF_FAIL( |
| GetModelForCustomArrayElement(context_object_.Get(), next_, &sp_value)); |
| RETURN_IF_FAIL(CreateULong64(next_, &sp_index)); |
| |
| // Everything that could fail (including the bounds check) has succeeded, so |
| // increment the index. |
| ++next_; |
| |
| // Write results (none of these steps can fail, which is important because we |
| // transfer ownership of two separate objects). |
| if (dimensions == 1) { |
| indexers[0] = sp_index.Detach(); |
| } |
| *object = sp_value.Detach(); |
| if (metadata != nullptr) *metadata = nullptr; |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8ObjectDataModel::GetKey(IModelObject* context_object, |
| PCWSTR key, IModelObject** key_value, |
| IKeyStore** metadata, |
| bool* has_key) noexcept { |
| if (metadata != nullptr) *metadata = nullptr; |
| |
| WRL::ComPtr<IV8CachedObject> sp_v8_cached_object; |
| RETURN_IF_FAIL(GetCachedObject(context_object, &sp_v8_cached_object)); |
| V8HeapObject* p_v8_heap_object; |
| RETURN_IF_FAIL(sp_v8_cached_object->GetCachedV8HeapObject(&p_v8_heap_object)); |
| |
| *has_key = false; |
| for (const auto& prop : p_v8_heap_object->properties) { |
| const char16_t* p_key = reinterpret_cast<const char16_t*>(key); |
| if (prop.name.compare(p_key) == 0) { |
| *has_key = true; |
| if (key_value != nullptr) { |
| WRL::ComPtr<IDebugHostContext> sp_ctx; |
| RETURN_IF_FAIL(context_object->GetContext(&sp_ctx)); |
| RETURN_IF_FAIL(GetModelForProperty(prop, sp_ctx, key_value)); |
| } |
| return S_OK; |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8ObjectDataModel::SetKey(IModelObject* context_object, |
| PCWSTR key, IModelObject* key_value, |
| IKeyStore* metadata) noexcept { |
| return E_NOTIMPL; |
| } |
| |
| IFACEMETHODIMP V8ObjectDataModel::EnumerateKeys( |
| IModelObject* context_object, IKeyEnumerator** pp_enumerator) noexcept { |
| WRL::ComPtr<IV8CachedObject> sp_v8_cached_object; |
| RETURN_IF_FAIL(GetCachedObject(context_object, &sp_v8_cached_object)); |
| |
| auto enumerator{WRL::Make<V8ObjectKeyEnumerator>(sp_v8_cached_object)}; |
| *pp_enumerator = enumerator.Detach(); |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8LocalValueProperty::GetValue( |
| PCWSTR pwsz_key, IModelObject* p_v8_local_instance, |
| IModelObject** pp_value) noexcept { |
| // Get the parametric type within v8::Local<*> |
| // Set value to a pointer to an instance of this type. |
| |
| WRL::ComPtr<IDebugHostType> sp_type; |
| RETURN_IF_FAIL(p_v8_local_instance->GetTypeInfo(&sp_type)); |
| |
| bool is_generic; |
| RETURN_IF_FAIL(sp_type->IsGeneric(&is_generic)); |
| if (!is_generic) return E_FAIL; |
| |
| WRL::ComPtr<IDebugHostSymbol> sp_generic_arg; |
| RETURN_IF_FAIL(sp_type->GetGenericArgumentAt(0, &sp_generic_arg)); |
| |
| _bstr_t generic_name; |
| RETURN_IF_FAIL(sp_generic_arg->GetName(generic_name.GetAddress())); |
| |
| WRL::ComPtr<IDebugHostContext> sp_ctx; |
| RETURN_IF_FAIL(p_v8_local_instance->GetContext(&sp_ctx)); |
| |
| WRL::ComPtr<IDebugHostType> sp_value_type = |
| Extension::Current()->GetTypeFromV8Module( |
| sp_ctx, reinterpret_cast<const char16_t*>( |
| static_cast<const wchar_t*>(generic_name))); |
| if (sp_value_type == nullptr || |
| !Extension::Current()->DoesTypeDeriveFromObject(sp_value_type)) { |
| // The value type doesn't derive from v8::internal::Object (probably a |
| // public API type), so just use plain v8::internal::Object. We could |
| // consider mapping some public API types to their corresponding internal |
| // types here, at the possible cost of increased maintenance. |
| sp_value_type = Extension::Current()->GetV8ObjectType(sp_ctx); |
| } |
| |
| Location loc; |
| RETURN_IF_FAIL(p_v8_local_instance->GetLocation(&loc)); |
| |
| // Read the pointer at the Object location |
| ULONG64 obj_address; |
| RETURN_IF_FAIL( |
| sp_debug_host_memory->ReadPointers(sp_ctx.Get(), loc, 1, &obj_address)); |
| |
| // If the val_ is a nullptr, then there is no value in the Local. |
| if (obj_address == 0) { |
| RETURN_IF_FAIL(CreateString(std::u16string{u"<empty>"}, pp_value)); |
| } else { |
| // Should be a v8::internal::Object at the address |
| RETURN_IF_FAIL(sp_data_model_manager->CreateTypedObject( |
| sp_ctx.Get(), obj_address, sp_value_type.Get(), pp_value)); |
| } |
| |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8LocalValueProperty::SetValue( |
| PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/, |
| IModelObject* /*p_value*/) noexcept { |
| return E_NOTIMPL; |
| } |
| |
| IFACEMETHODIMP V8InternalCompilerNodeIdProperty::GetValue( |
| PCWSTR pwsz_key, IModelObject* p_v8_compiler_node_instance, |
| IModelObject** pp_value) noexcept { |
| WRL::ComPtr<IModelObject> sp_bit_field; |
| RETURN_IF_FAIL(p_v8_compiler_node_instance->GetRawValue( |
| SymbolKind::SymbolField, L"bit_field_", RawSearchNone, &sp_bit_field)); |
| |
| uint64_t bit_field_value; |
| RETURN_IF_FAIL( |
| UnboxULong64(sp_bit_field.Get(), &bit_field_value, true /*convert*/)); |
| |
| WRL::ComPtr<IDebugHostContext> sp_host_context; |
| RETURN_IF_FAIL(p_v8_compiler_node_instance->GetContext(&sp_host_context)); |
| |
| WRL::ComPtr<IDebugHostType> sp_id_field_type; |
| RETURN_IF_FAIL(Extension::Current() |
| ->GetV8Module(sp_host_context) |
| ->FindTypeByName(L"v8::internal::compiler::Node::IdField", |
| &sp_id_field_type)); |
| |
| // Get 2nd template parameter as 24 in class. |
| // v8::base::BitField<v8::internal::compiler::NodeId, 0, 24>. |
| bool is_generic; |
| RETURN_IF_FAIL(sp_id_field_type->IsGeneric(&is_generic)); |
| if (!is_generic) return E_FAIL; |
| |
| WRL::ComPtr<IDebugHostSymbol> sp_k_size_arg; |
| RETURN_IF_FAIL(sp_id_field_type->GetGenericArgumentAt(2, &sp_k_size_arg)); |
| |
| WRL::ComPtr<IDebugHostConstant> sp_k_size_constant; |
| RETURN_IF_FAIL(sp_k_size_arg.As(&sp_k_size_constant)); |
| |
| int k_size; |
| RETURN_IF_FAIL(GetInt32(sp_k_size_constant.Get(), &k_size)); |
| |
| // Compute node_id. |
| uint32_t node_id = bit_field_value & (0xFFFFFFFF >> k_size); |
| RETURN_IF_FAIL(CreateUInt32(node_id, pp_value)); |
| |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8InternalCompilerNodeIdProperty::SetValue( |
| PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/, |
| IModelObject* /*p_value*/) noexcept { |
| return E_NOTIMPL; |
| } |
| |
| IFACEMETHODIMP V8InternalCompilerBitsetNameProperty::GetValue( |
| PCWSTR pwsz_key, IModelObject* p_v8_compiler_type_instance, |
| IModelObject** pp_value) noexcept { |
| WRL::ComPtr<IModelObject> sp_payload; |
| RETURN_IF_FAIL(p_v8_compiler_type_instance->GetRawValue( |
| SymbolKind::SymbolField, L"payload_", RawSearchNone, &sp_payload)); |
| |
| uint64_t payload_value; |
| RETURN_IF_FAIL( |
| UnboxULong64(sp_payload.Get(), &payload_value, true /*convert*/)); |
| |
| const char* bitset_name = ::BitsetName(payload_value); |
| if (!bitset_name) return E_FAIL; |
| std::string name(bitset_name); |
| RETURN_IF_FAIL(CreateString(ConvertToU16String(name), pp_value)); |
| |
| return S_OK; |
| } |
| |
| IFACEMETHODIMP V8InternalCompilerBitsetNameProperty::SetValue( |
| PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/, |
| IModelObject* /*p_value*/) noexcept { |
| return E_NOTIMPL; |
| } |
| |
| constexpr wchar_t usage[] = |
| LR"(Invalid arguments. |
| First argument should be a uint64 representing the tagged value to investigate. |
| Second argument is optional, and may be a fully-qualified type name such as |
| v8::internal::String.)"; |
| |
| IFACEMETHODIMP InspectV8ObjectMethod::Call(IModelObject* p_context_object, |
| ULONG64 arg_count, |
| _In_reads_(arg_count) |
| IModelObject** pp_arguments, |
| IModelObject** pp_result, |
| IKeyStore** pp_metadata) noexcept { |
| // Read the arguments. |
| ULONG64 tagged_value; |
| _bstr_t type_name; |
| if (arg_count < 1 || |
| FAILED(UnboxULong64(pp_arguments[0], &tagged_value, /*convert=*/true)) || |
| (arg_count >= 2 && |
| FAILED(UnboxString(pp_arguments[1], type_name.GetAddress())))) { |
| sp_data_model_manager->CreateErrorObject(E_INVALIDARG, usage, pp_result); |
| return E_INVALIDARG; |
| } |
| |
| WRL::ComPtr<IDebugHostContext> sp_ctx; |
| RETURN_IF_FAIL(sp_debug_host->GetCurrentContext(&sp_ctx)); |
| |
| // We can't use CreateTypedObject for a value which may not actually reside |
| // anywhere in memory, so create a synthetic object. |
| WRL::ComPtr<V8CachedObject> cached_object = |
| WRL::Make<V8CachedObject>(::GetHeapObject( |
| sp_ctx, tagged_value, 0, static_cast<const char*>(type_name), |
| /*is_compressed=*/false)); |
| return CreateSyntheticObjectForV8Object(sp_ctx.Get(), cached_object.Get(), |
| pp_result); |
| } |
| |
| // Creates an IModelObject representing the data in the given property. |
| HRESULT GetModelForProperty(const Property& prop, |
| WRL::ComPtr<IDebugHostContext>& sp_ctx, |
| IModelObject** result) { |
| switch (prop.type) { |
| case PropertyType::kPointer: |
| return GetModelForBasicField(prop.addr_value, prop.type_name, |
| prop.uncompressed_type_name, sp_ctx, result); |
| case PropertyType::kStruct: |
| return GetModelForStruct(prop.addr_value, prop.fields, sp_ctx, result); |
| case PropertyType::kArray: |
| case PropertyType::kStructArray: |
| if (prop.type == PropertyType::kArray && |
| prop.type_name == ConvertToU16String(prop.uncompressed_type_name)) { |
| // An array of things that are not structs or compressed tagged values |
| // is most cleanly represented by a native array. |
| return GetModelForNativeArray(prop.addr_value, prop.type_name, |
| prop.length, sp_ctx, result); |
| } |
| // Otherwise, we must construct a custom iterable object. |
| return GetModelForCustomArray(prop, sp_ctx, result); |
| default: |
| return E_FAIL; |
| } |
| } |