// 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));

  WRL::ComPtr<IDebugHostType> 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<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) {
      if (FAILED(GetModelForBasicField(address + field.offset, field.type_name,
                                       field.uncompressed_type_name, sp_ctx,
                                       &field_model))) {
        continue;
      }
    } else {
      if (FAILED(GetModelForBitField(address + field.offset, field.num_bits,
                                     field.shift_bits, field.type_name, sp_ctx,
                                     &field_model))) {
        continue;
      }
    }
    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;
  }
}
