// 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 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 context; RETURN_IF_FAIL(p_v8_object_instance->GetContext(&context)); WRL::ComPtr sp_type; _bstr_t type_name; RETURN_IF_FAIL(p_v8_object_instance->GetTypeInfo(&sp_type)); RETURN_IF_FAIL(sp_type->GetName(type_name.GetAddress())); // 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. bool is_compressed = COMPRESS_POINTERS_BOOL && static_cast(type_name) == std::string(kTaggedValue); const char* uncompressed_type_name = is_compressed ? kObject : static_cast(type_name); *result = WRL::Make(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(&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(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& 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(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 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(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 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& 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 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 cached_object = WRL::Make( 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& sp_ctx, IModelObject** result) { // Look up the type by name. WRL::ComPtr 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(&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& fields, WRL::ComPtr& sp_ctx, IModelObject** result) { WRL::ComPtr 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 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(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& sp_ctx, IModelObject** result) { WRL::ComPtr 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 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& 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 context_data = WRL::Make(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 data_model_context; RETURN_IF_FAIL(context_object->GetContextForDataModel( Extension::Current()->GetIndexedFieldDataModel(), &data_model_context)); WRL::ComPtr 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 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(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 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 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(key); if (prop.name.compare(p_key) == 0) { *has_key = true; if (key_value != nullptr) { WRL::ComPtr 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 sp_v8_cached_object; RETURN_IF_FAIL(GetCachedObject(context_object, &sp_v8_cached_object)); auto enumerator{WRL::Make(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 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 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 sp_ctx; RETURN_IF_FAIL(p_v8_local_instance->GetContext(&sp_ctx)); WRL::ComPtr sp_value_type = Extension::Current()->GetTypeFromV8Module( sp_ctx, reinterpret_cast( static_cast(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""}, 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 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 sp_host_context; RETURN_IF_FAIL(p_v8_compiler_node_instance->GetContext(&sp_host_context)); WRL::ComPtr 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. bool is_generic; RETURN_IF_FAIL(sp_id_field_type->IsGeneric(&is_generic)); if (!is_generic) return E_FAIL; WRL::ComPtr sp_k_size_arg; RETURN_IF_FAIL(sp_id_field_type->GetGenericArgumentAt(2, &sp_k_size_arg)); WRL::ComPtr 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 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 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 cached_object = WRL::Make(::GetHeapObject( sp_ctx, tagged_value, 0, static_cast(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& 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; } }