// 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/v8windbg-extension.h"

#include <iostream>

#include "tools/v8windbg/base/utilities.h"
#include "tools/v8windbg/src/cur-isolate.h"
#include "tools/v8windbg/src/js-stack.h"
#include "tools/v8windbg/src/list-chunks.h"
#include "tools/v8windbg/src/local-variables.h"
#include "tools/v8windbg/src/object-inspection.h"

std::unique_ptr<Extension> Extension::current_extension_ = nullptr;
const wchar_t* pcur_isolate = L"curisolate";
const wchar_t* pjs_stack = L"jsstack";
const wchar_t* plist_chunks = L"listchunks";
const wchar_t* pv8_object = L"v8object";

HRESULT CreateExtension() {
  if (Extension::Current() != nullptr || sp_data_model_manager == nullptr ||
      sp_debug_host == nullptr) {
    return E_FAIL;
  } else {
    std::unique_ptr<Extension> new_extension(new (std::nothrow) Extension());
    if (new_extension == nullptr) return E_FAIL;
    RETURN_IF_FAIL(new_extension->Initialize());
    Extension::SetExtension(std::move(new_extension));
    return S_OK;
  }
}

void DestroyExtension() { Extension::SetExtension(nullptr); }

bool Extension::DoesTypeDeriveFromObject(
    const WRL::ComPtr<IDebugHostType>& sp_type) {
  _bstr_t name;
  HRESULT hr = sp_type->GetName(name.GetAddress());
  if (!SUCCEEDED(hr)) return false;
  if (std::string(static_cast<const char*>(name)) == kObject) return true;

  WRL::ComPtr<IDebugHostSymbolEnumerator> sp_super_class_enumerator;
  hr = sp_type->EnumerateChildren(SymbolKind::SymbolBaseClass, nullptr,
                                  &sp_super_class_enumerator);
  if (!SUCCEEDED(hr)) return false;

  while (true) {
    WRL::ComPtr<IDebugHostSymbol> sp_type_symbol;
    if (sp_super_class_enumerator->GetNext(&sp_type_symbol) != S_OK) break;
    WRL::ComPtr<IDebugHostBaseClass> sp_base_class;
    if (FAILED(sp_type_symbol.As(&sp_base_class))) continue;
    WRL::ComPtr<IDebugHostType> sp_base_type;
    hr = sp_base_class->GetType(&sp_base_type);
    if (!SUCCEEDED(hr)) continue;
    if (DoesTypeDeriveFromObject(sp_base_type)) {
      return true;
    }
  }

  return false;
}

WRL::ComPtr<IDebugHostType> Extension::GetV8ObjectType(
    WRL::ComPtr<IDebugHostContext>& sp_ctx) {
  return GetTypeFromV8Module(sp_ctx, kObjectU);
}

WRL::ComPtr<IDebugHostType> Extension::GetTypeFromV8Module(
    WRL::ComPtr<IDebugHostContext>& sp_ctx, const char16_t* type_name) {
  bool is_equal;
  if (sp_v8_module_ctx_ == nullptr ||
      !SUCCEEDED(sp_v8_module_ctx_->IsEqualTo(sp_ctx.Get(), &is_equal)) ||
      !is_equal) {
    // Context changed; clear the dictionary.
    cached_v8_module_types_.clear();
  }

  GetV8Module(sp_ctx);  // Will force the correct module to load
  if (sp_v8_module_ == nullptr) return nullptr;

  auto& dictionary_entry = cached_v8_module_types_[type_name];
  if (dictionary_entry == nullptr) {
    const std::wstring type_name_w(reinterpret_cast<const wchar_t*>(type_name));
    // The contract from debug_helper functions is to provide type names that
    // would be valid if used in C++ code within the v8::internal namespace.
    // They might be fully qualified but aren't required to be. Thus, we must
    // simluate an "unqualified name lookup" here, by searching for the type
    // starting in the innermost namespace and working outward.
    if (SUCCEEDED(sp_v8_module_->FindTypeByName(
            (L"v8::internal::" + type_name_w).c_str(), &dictionary_entry))) {
      return dictionary_entry;
    }
    if (SUCCEEDED(sp_v8_module_->FindTypeByName((L"v8::" + type_name_w).c_str(),
                                                &dictionary_entry))) {
      return dictionary_entry;
    }
    sp_v8_module_->FindTypeByName(reinterpret_cast<PCWSTR>(type_name),
                                  &dictionary_entry);
  }
  return dictionary_entry;
}

namespace {

// Returns whether the given module appears to have symbols for V8 code.
bool IsV8Module(IDebugHostModule* module) {
  WRL::ComPtr<IDebugHostSymbol> sp_isolate_sym;
  // The below symbol is specific to the main V8 module.
  if (FAILED(module->FindSymbolByName(L"v8::Script::Run", &sp_isolate_sym))) {
    return false;
  }
  return true;
}

}  // namespace

WRL::ComPtr<IDebugHostModule> Extension::GetV8Module(
    WRL::ComPtr<IDebugHostContext>& sp_ctx) {
  // Return the cached version if it exists and the context is the same

  // Note: Context will often have the CUSTOM flag set, which never compares
  // equal. So for now DON'T compare by context, but by proc_id. (An API is in
  // progress to compare by address space, which should be usable when shipped).
  /*
  if (sp_v8_module_ != nullptr) {
    bool is_equal;
    if (SUCCEEDED(sp_v8_module_ctx_->IsEqualTo(sp_ctx.Get(), &is_equal)) &&
  is_equal) { return sp_v8_module_; } else { sp_v8_module_ = nullptr;
      sp_v8_module_ctx_ = nullptr;
    }
  }
  */
  WRL::ComPtr<IDebugSystemObjects> sp_sys_objects;
  ULONG proc_id = 0;
  if (SUCCEEDED(sp_debug_control.As(&sp_sys_objects))) {
    if (SUCCEEDED(sp_sys_objects->GetCurrentProcessSystemId(&proc_id))) {
      if (proc_id == v8_module_proc_id_ && sp_v8_module_ != nullptr)
        return sp_v8_module_;
    }
  }

  // Search first for a few known module names, to avoid loading symbols for
  // unrelated modules if we can easily avoid it. Generally, failing to find a
  // module is fast but failing to find a symbol within a module is slow. Note
  // that "v8" is listed first because it's highly likely to be the correct
  // module if it exists. The others might include V8 symbols depending on the
  // build configuration.
  std::vector<const wchar_t*> known_names = {
      L"v8", L"v8_for_testing", L"cctest_exe", L"chrome",
      L"d8", L"msedge",         L"node",       L"unittests_exe"};
  for (const wchar_t* name : known_names) {
    WRL::ComPtr<IDebugHostModule> sp_module;
    if (SUCCEEDED(sp_debug_host_symbols->FindModuleByName(sp_ctx.Get(), name,
                                                          &sp_module))) {
      if (IsV8Module(sp_module.Get())) {
        sp_v8_module_ = sp_module;
        sp_v8_module_ctx_ = sp_ctx;
        v8_module_proc_id_ = proc_id;
        return sp_v8_module_;
      }
    }
  }

  // Loop through all modules looking for the one that holds a known symbol.
  WRL::ComPtr<IDebugHostSymbolEnumerator> sp_enum;
  if (SUCCEEDED(
          sp_debug_host_symbols->EnumerateModules(sp_ctx.Get(), &sp_enum))) {
    HRESULT hr = S_OK;
    while (true) {
      WRL::ComPtr<IDebugHostSymbol> sp_mod_sym;
      hr = sp_enum->GetNext(&sp_mod_sym);
      // hr == E_BOUNDS : hit the end of the enumerator
      // hr == E_ABORT  : a user interrupt was requested
      if (FAILED(hr)) break;
      WRL::ComPtr<IDebugHostModule> sp_module;
      if (SUCCEEDED(sp_mod_sym.As(&sp_module))) /* should always succeed */
      {
        if (IsV8Module(sp_module.Get())) {
          sp_v8_module_ = sp_module;
          sp_v8_module_ctx_ = sp_ctx;
          v8_module_proc_id_ = proc_id;
          break;
        }
      }
    }
  }
  // This will be the located module, or still nullptr if above fails
  return sp_v8_module_;
}

Extension::Extension() = default;

HRESULT Extension::Initialize() {
  // Create an instance of the DataModel parent for v8::internal::Object types.
  auto object_data_model{WRL::Make<V8ObjectDataModel>()};
  RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject(
      object_data_model.Get(), &sp_object_data_model_));
  RETURN_IF_FAIL(sp_object_data_model_->SetConcept(
      __uuidof(IStringDisplayableConcept),
      static_cast<IStringDisplayableConcept*>(object_data_model.Get()),
      nullptr));
  RETURN_IF_FAIL(sp_object_data_model_->SetConcept(
      __uuidof(IDynamicKeyProviderConcept),
      static_cast<IDynamicKeyProviderConcept*>(object_data_model.Get()),
      nullptr));

  // Register that parent model for all known types of V8 object.
  std::vector<std::u16string> object_class_names = ListObjectClasses();
  object_class_names.push_back(kObjectU);
  object_class_names.push_back(kTaggedValueU);
  for (const std::u16string& name : object_class_names) {
    WRL::ComPtr<IDebugHostTypeSignature> sp_object_type_signature;
    RETURN_IF_FAIL(sp_debug_host_symbols->CreateTypeSignature(
        reinterpret_cast<const wchar_t*>(name.c_str()), nullptr,
        &sp_object_type_signature));
    RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature(
        sp_object_type_signature.Get(), sp_object_data_model_.Get()));
    registered_types_.push_back(
        {sp_object_type_signature.Get(), sp_object_data_model_.Get()});
  }

  // Create an instance of the DataModel parent for custom iterable fields.
  auto indexed_field_model{WRL::Make<IndexedFieldParent>()};
  RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject(
      indexed_field_model.Get(), &sp_indexed_field_model_));
  RETURN_IF_FAIL(sp_indexed_field_model_->SetConcept(
      __uuidof(IIndexableConcept),
      static_cast<IIndexableConcept*>(indexed_field_model.Get()), nullptr));
  RETURN_IF_FAIL(sp_indexed_field_model_->SetConcept(
      __uuidof(IIterableConcept),
      static_cast<IIterableConcept*>(indexed_field_model.Get()), nullptr));

  // Create an instance of the DataModel parent class for v8::Local<*> types.
  auto local_data_model{WRL::Make<V8LocalDataModel>()};
  RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject(
      local_data_model.Get(), &sp_local_data_model_));

  // Register that parent model for all known types that act like v8::Local.
  std::vector<const wchar_t*> handle_class_names = {
      L"v8::Local<*>", L"v8::MaybeLocal<*>", L"v8::internal::Handle<*>",
      L"v8::internal::MaybeHandle<*>"};
  for (const wchar_t* name : handle_class_names) {
    WRL::ComPtr<IDebugHostTypeSignature> signature;
    RETURN_IF_FAIL(
        sp_debug_host_symbols->CreateTypeSignature(name, nullptr, &signature));
    RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature(
        signature.Get(), sp_local_data_model_.Get()));
    registered_types_.push_back({signature.Get(), sp_local_data_model_.Get()});
  }

  // Add the 'Value' property to the parent model.
  auto local_value_property{WRL::Make<V8LocalValueProperty>()};
  WRL::ComPtr<IModelObject> sp_local_value_property_model;
  RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(),
                                local_value_property.Get(),
                                &sp_local_value_property_model));
  RETURN_IF_FAIL(sp_local_data_model_->SetKey(
      L"Value", sp_local_value_property_model.Get(), nullptr));

  // Register all function aliases.
  std::vector<std::pair<const wchar_t*, WRL::ComPtr<IModelMethod>>> functions =
      {{pcur_isolate, WRL::Make<CurrIsolateAlias>()},
       {pjs_stack, WRL::Make<JSStackAlias>()},
       {plist_chunks, WRL::Make<ListChunksAlias>()},
       {pv8_object, WRL::Make<InspectV8ObjectMethod>()}};
  for (const auto& function : functions) {
    WRL::ComPtr<IModelObject> method;
    RETURN_IF_FAIL(CreateMethod(sp_data_model_manager.Get(),
                                function.second.Get(), &method));
    RETURN_IF_FAIL(sp_debug_host_extensibility->CreateFunctionAlias(
        function.first, method.Get()));
  }

  // Register a handler for supplying stack frame locals. It has to override the
  // getter functions for "LocalVariables" and "Parameters".
  WRL::ComPtr<IModelObject> stack_frame;
  RETURN_IF_FAIL(sp_data_model_manager->AcquireNamedModel(
      L"Debugger.Models.StackFrame", &stack_frame));
  RETURN_IF_FAIL(OverrideLocalsGetter(stack_frame.Get(), L"LocalVariables",
                                      /*is_parameters=*/false));
  RETURN_IF_FAIL(OverrideLocalsGetter(stack_frame.Get(), L"Parameters",
                                      /*is_parameters=*/true));

  // Add node_id property for v8::internal::compiler::Node.
  RETURN_IF_FAIL(
      RegisterAndAddPropertyForClass<V8InternalCompilerNodeIdProperty>(
          L"v8::internal::compiler::Node", L"node_id",
          sp_compiler_node_data_model_));

  // Add bitset_name property for v8::internal::compiler::Type.
  RETURN_IF_FAIL(
      RegisterAndAddPropertyForClass<V8InternalCompilerBitsetNameProperty>(
          L"v8::internal::compiler::Type", L"bitset_name",
          sp_compiler_type_data_model_));

  return S_OK;
}

template <class PropertyClass>
HRESULT Extension::RegisterAndAddPropertyForClass(
    const wchar_t* class_name, const wchar_t* property_name,
    WRL::ComPtr<IModelObject> sp_data_model) {
  // Create an instance of the DataModel parent class.
  auto instance_data_model{WRL::Make<V8LocalDataModel>()};
  RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject(
      instance_data_model.Get(), &sp_data_model));

  // Register that parent model.
  WRL::ComPtr<IDebugHostTypeSignature> class_signature;
  RETURN_IF_FAIL(sp_debug_host_symbols->CreateTypeSignature(class_name, nullptr,
                                                            &class_signature));
  RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature(
      class_signature.Get(), sp_data_model.Get()));
  registered_types_.push_back({class_signature.Get(), sp_data_model.Get()});

  // Add the property to the parent model.
  auto property{WRL::Make<PropertyClass>()};
  WRL::ComPtr<IModelObject> sp_property_model;
  RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(), property.Get(),
                                &sp_property_model));
  RETURN_IF_FAIL(
      sp_data_model->SetKey(property_name, sp_property_model.Get(), nullptr));

  return S_OK;
}

HRESULT Extension::OverrideLocalsGetter(IModelObject* stack_frame,
                                        const wchar_t* key_name,
                                        bool is_parameters) {
  WRL::ComPtr<IModelObject> original_boxed_getter;
  WRL::ComPtr<IKeyStore> original_getter_metadata;
  RETURN_IF_FAIL(stack_frame->GetKey(key_name, &original_boxed_getter,
                                     &original_getter_metadata));
  WRL::ComPtr<IModelPropertyAccessor> original_getter;
  RETURN_IF_FAIL(UnboxProperty(original_boxed_getter.Get(), &original_getter));
  auto new_getter{WRL::Make<V8LocalVariables>(original_getter, is_parameters)};
  WRL::ComPtr<IModelObject> new_boxed_getter;
  RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(), new_getter.Get(),
                                &new_boxed_getter));
  RETURN_IF_FAIL(stack_frame->SetKey(key_name, new_boxed_getter.Get(),
                                     original_getter_metadata.Get()));
  overridden_properties_.push_back(
      {stack_frame, reinterpret_cast<const char16_t*>(key_name),
       original_boxed_getter.Get(), original_getter_metadata.Get()});
  return S_OK;
}

Extension::PropertyOverride::PropertyOverride() = default;
Extension::PropertyOverride::PropertyOverride(IModelObject* parent,
                                              std::u16string key_name,
                                              IModelObject* original_value,
                                              IKeyStore* original_metadata)
    : parent(parent),
      key_name(std::move(key_name)),
      original_value(original_value),
      original_metadata(original_metadata) {}
Extension::PropertyOverride::~PropertyOverride() = default;
Extension::PropertyOverride::PropertyOverride(const PropertyOverride&) =
    default;
Extension::PropertyOverride& Extension::PropertyOverride::operator=(
    const PropertyOverride&) = default;

Extension::RegistrationType::RegistrationType() = default;
Extension::RegistrationType::RegistrationType(
    IDebugHostTypeSignature* sp_signature, IModelObject* sp_data_model)
    : sp_signature(sp_signature), sp_data_model(sp_data_model) {}
Extension::RegistrationType::~RegistrationType() = default;
Extension::RegistrationType::RegistrationType(const RegistrationType&) =
    default;
Extension::RegistrationType& Extension::RegistrationType::operator=(
    const RegistrationType&) = default;

Extension::~Extension() {
  sp_debug_host_extensibility->DestroyFunctionAlias(pcur_isolate);
  sp_debug_host_extensibility->DestroyFunctionAlias(pjs_stack);
  sp_debug_host_extensibility->DestroyFunctionAlias(plist_chunks);
  sp_debug_host_extensibility->DestroyFunctionAlias(pv8_object);

  for (const auto& registered : registered_types_) {
    sp_data_model_manager->UnregisterModelForTypeSignature(
        registered.sp_data_model.Get(), registered.sp_signature.Get());
  }

  for (const auto& override : overridden_properties_) {
    override.parent->SetKey(
        reinterpret_cast<const wchar_t*>(override.key_name.c_str()),
        override.original_value.Get(), override.original_metadata.Get());
  }
}
