# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
    Provides visualizers for common types for debugging in LLDB.

    To make these available, add the following to your ~/.lldbinit or your
    .vscode/launch.json, or run after launching lldb:

    command script import {Path to SRC Root}/tools/lldb/chromium_visualizers.py
"""

import traceback

import lldb


def lazy_unsigned_global(name, child=None):
  sb_value = None
  uint_value = 0

  def getter(fromValue):
    nonlocal sb_value
    nonlocal uint_value
    if sb_value is None:
      sb_value = fromValue.GetTarget().FindFirstGlobalVariable(name)
      if child:
        sb_value = sb_value.GetChildMemberWithName(child)
      uint_value = sb_value.GetValueAsUnsigned()
    return uint_value

  return getter


kPointerCompressionShift_getter = lazy_unsigned_global(
    'cppgc::internal::api_constants::kPointerCompressionShift')
CageBaseGlobal_getter = lazy_unsigned_global(
    'cppgc::internal::CageBaseGlobal::g_base_', 'base')

PLACEHOLDER_VALUE = None


class SingleChildProvider:
  """A base class for providers that create one child."""

  def __init__(self, valobj):
    self.child = None
    self.valobj = valobj

  def update(self):
    # By default, reevaluate
    self.child = None

  def ensure_populated(self):
    if self.child is None:
      try:
        self.populate()
      except:
        print(traceback.format_exc())
    if self.child is None or not self.child.IsValid():
      global PLACEHOLDER_VALUE
      if PLACEHOLDER_VALUE is None:
        PLACEHOLDER_VALUE = self.valobj.CreateValueFromExpression(
            '<failed to load child>', 'nullptr')
      self.child = PLACEHOLDER_VALUE

  def populate(self):
    raise NotImplementedError()

  def num_children(self):
    self.ensure_populated()
    return 1

  def has_children(self):
    return True

  def get_child_index(self, name):
    self.ensure_populated()
    return 0

  def get_child_at_index(self, index):
    self.ensure_populated()
    return self.child


class VectorProvider(SingleChildProvider):
  """Provides children for a vector."""

  def __init__(self, valobj, internal_dict):
    super().__init__(valobj)

  def populate(self):
    lldbtype = self.valobj.GetType()
    addr = self.valobj.GetValueAsAddress() if lldbtype.IsPointerType(
    ) else self.valobj.GetLoadAddress()
    data_pointer = self.valobj.GetChildMemberWithName('buffer_')
    size = self.valobj.GetChildMemberWithName('size_').GetValueAsUnsigned()
    self.child = data_pointer.Cast(
        data_pointer.GetType().GetPointeeType().GetArrayType(
            size).GetPointerType())


class SmartPointerProvider(SingleChildProvider):
  """
    A base class for providers that create one child generated by calling a
    method on the object.
  """

  def __init__(self,
               raw_name,
               valobj,
               internal_dict,
               child_name='$$dereference$$'):
    super().__init__(valobj)
    self.raw_name = raw_name
    self.child_name = child_name

  def update(self):
    global PLACEHOLDER_VALUE
    if self.child is PLACEHOLDER_VALUE:
      self.child = None

  def populate(self):
    self.child = self.valobj.GetChildMemberWithName(self.raw_name).Cast(
        self.valobj.GetType().GetTemplateArgumentType(0).GetPointerType())


class ScopedRefProvider(SmartPointerProvider):

  def __init__(self, valobj, internal_dict):
    super().__init__('ptr_', valobj, internal_dict)


class MemberProvider(SingleChildProvider):

  def __init__(self, valobj, internal_dict):
    super().__init__(valobj)
    self.last_child = None
    self.last_addr = None
    self.raw_storage = None
    self.pointer_type = None
    self.compressed = None

  def update(self):
    global PLACEHOLDER_VALUE
    if self.compressed is not None or self.child is PLACEHOLDER_VALUE:
      self.child = None

  def populate(self):
    if self.raw_storage is None:
      self.raw_storage = self.valobj.GetChildMemberWithName('raw_')
      self.pointer_type = self.raw_storage.GetType().GetCanonicalType().GetName(
      )
    pointee_type = self.valobj.GetType().GetTemplateArgumentType(0)
    if self.pointer_type == 'cppgc::internal::RawPointer':
      data_pointer = self.raw_storage.GetChildMemberWithName('ptr_').Cast(
          pointee_type.GetPointerType())
    elif self.pointer_type == 'cppgc::internal::CompressedPointer':
      # Need to reproduce the behavior of CompressedPointer::Decompress()
      # because it is optimized away.
      if self.compressed is None:
        self.compressed = self.raw_storage.GetChildMemberWithName('value_')
      compressed = self.compressed.GetValueAsUnsigned()
      if self.last_child and self.last_addr == compressed:
        # Last child is still valid
        self.child = self.last_child
        return
      pointer_shift = kPointerCompressionShift_getter(self.raw_storage)
      cage_base = CageBaseGlobal_getter(self.raw_storage)
      sign_bit = 0x80000000
      decompressed = ((
          (compressed ^ sign_bit) - sign_bit) << pointer_shift) & cage_base
      data_pointer = self.valobj.CreateValueFromAddress('$$dereference$$',
                                                        decompressed,
                                                        pointee_type)
      self.last_addr = compressed
    else:
      # This seems to happen when the pointer is null. Ignore.
      return
    self.child = data_pointer
    self.last_child = self.child


class MethodProvider(SingleChildProvider):
  """
    A base class for providers that create one child generated by calling a
    method on the object.
  """

  def __init__(self,
               method,
               valobj,
               internal_dict,
               child_name='$$dereference$$'):
    super().__init__(valobj)
    self.method = method
    self.child_name = child_name
    self.last_child = None
    self.last_addr = None

  def populate(self):
    lldbtype = self.valobj.GetType()
    addr = self.valobj.GetValueAsAddress() if lldbtype.IsPointerType(
    ) else self.valobj.GetLoadAddress()
    if self.last_child and self.last_addr == addr:
      # Last child is still valid
      self.child = self.last_child
      return
    self.child = self.valobj.CreateValueFromExpression(
        self.child_name,
        f'(({lldbtype.GetCanonicalType().GetName()} *){addr})->{self.method}')
    self.last_child = self.child
    self.last_addr = addr


class WTFStringProvider(MethodProvider):

  def __init__(self, valobj, internal_dict):
    super().__init__('Utf8(WTF::Utf8ConversionMode::kLenient)', valobj,
                     internal_dict, 'utf8_')


def __lldb_init_module(debugger, unused_dict):
  debugger.HandleCommand(
      'type synthetic add -l chromium_visualizers.ScopedRefProvider -x "^scoped_refptr<.*>$"'
  )
  debugger.HandleCommand(
      'type synthetic add -p -r -l chromium_visualizers.MemberProvider -x "^cppgc::internal::BasicMember<.*>$"'
  )
  debugger.HandleCommand(
      'type synthetic add -l chromium_visualizers.WTFStringProvider -x "^WTF::String$"'
  )
  debugger.HandleCommand(
      'type synthetic add -l chromium_visualizers.VectorProvider -x "^WTF::Vector<.*>$"'
  )
  debugger.HandleCommand(
      'type summary add --summary-string "${svar.utf8_}" WTF::String')
  debugger.HandleCommand(
      'type summary add --summary-string "${var.string_}" WTF::AtomicString')
  debugger.HandleCommand(
      'type summary add --summary-string "size = ${var.size_}" -x "^WTF::Vector<.*>$"'
  )
  debugger.HandleCommand(
      'type summary add --summary-string "${var.raw_}" -x "^cppgc::internal::BasicMember<.*>$"'
  )
