| # Copyright 2022 The Dawn & Tint Authors | 
 | # | 
 | # Redistribution and use in source and binary forms, with or without | 
 | # modification, are permitted provided that the following conditions are met: | 
 | # | 
 | # 1. Redistributions of source code must retain the above copyright notice, this | 
 | #    list of conditions and the following disclaimer. | 
 | # | 
 | # 2. Redistributions in binary form must reproduce the above copyright notice, | 
 | #    this list of conditions and the following disclaimer in the documentation | 
 | #    and/or other materials provided with the distribution. | 
 | # | 
 | # 3. Neither the name of the copyright holder nor the names of its | 
 | #    contributors may be used to endorse or promote products derived from | 
 | #    this software without specific prior written permission. | 
 | # | 
 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | 
 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 |  | 
 | # Pretty printers for the Tint project. | 
 | # | 
 | # If using lldb from command line, add a line to your ~/.lldbinit to import the printers: | 
 | # | 
 | #    command script import /path/to/dawn/src/tint/tint_lldb.py | 
 | # | 
 | # | 
 | # If using VS Code on MacOS with the Microsoft C/C++ extension, add the following to | 
 | # your launch.json (make sure you specify an absolute path to tint_lldb.py): | 
 | # | 
 | #    "name": "Launch", | 
 | #    "type": "cppdbg", | 
 | #    "request": "launch", | 
 | #    ... | 
 | #    "setupCommands": [ | 
 | #        { | 
 | #            "description": "Load tint pretty printers", | 
 | #            "ignoreFailures": false, | 
 | #            "text": "command script import /path/to/dawn/src/tint/tint_lldb.py, | 
 | #        } | 
 | #    ] | 
 | # | 
 | # If using VS Code with the CodeLLDB extension (https://github.com/vadimcn/vscode-lldb), | 
 | # add the following to your launch.json: | 
 | # | 
 | #    "name": "Launch", | 
 | #    "type": "lldb", | 
 | #    "request": "launch", | 
 | #    ... | 
 | #    "initCommands": [ | 
 | #        "command script import /path/to/dawn/src/tint/tint_lldb.py" | 
 | #    ] | 
 |  | 
 | # Based on pretty printers for: | 
 | # Rust: https://github.com/vadimcn/vscode-lldb/blob/master/formatters/rust.py | 
 | # Dlang: https://github.com/Pure-D/dlang-debug/blob/master/lldb_dlang.py | 
 | # | 
 | # | 
 | # Tips for debugging using VS Code: | 
 | # | 
 | # - Set a breakpoint where you can view the types you want to debug/write pretty printers for. | 
 | # - Debug Console: -exec command script import /path/to/dawn/src/tint/tint_lldb.py | 
 | # - You can re-run the above command to reload the printers after modifying the python script. | 
 |  | 
 | # - Useful docs: | 
 | #   Formattesr: https://lldb.llvm.org/use/variable.html | 
 | #   Python API: https://lldb.llvm.org/python_api.html | 
 | #   Especially: | 
 | #     SBType: https://lldb.llvm.org/python_api/lldb.SBType.html | 
 | #     SBValue: https://lldb.llvm.org/python_api/lldb.SBValue.html | 
 |  | 
 | from __future__ import print_function, division | 
 | import sys | 
 | import logging | 
 | import re | 
 | import lldb | 
 | import types | 
 |  | 
 | if sys.version_info[0] == 2: | 
 |     # python2-based LLDB accepts utf8-encoded ascii strings only. | 
 |     def to_lldb_str(s): return s.encode( | 
 |         'utf8', 'backslashreplace') if isinstance(s, unicode) else s | 
 |     range = xrange | 
 | else: | 
 |     to_lldb_str = str | 
 |  | 
 | string_encoding = "escape"  # remove | unicode | escape | 
 |  | 
 | log = logging.getLogger(__name__) | 
 |  | 
 | module = sys.modules[__name__] | 
 | tint_category = None | 
 |  | 
 |  | 
 | def __lldb_init_module(debugger, dict): | 
 |     global tint_category | 
 |  | 
 |     tint_category = debugger.CreateCategory('tint') | 
 |     tint_category.SetEnabled(True) | 
 |  | 
 |     attach_synthetic_to_type(UtilsSlicePrinter, r'^tint::Slice<.+>$', True) | 
 |  | 
 |     attach_synthetic_to_type(UtilsVectorPrinter, r'^tint::Vector<.+>$', True) | 
 |  | 
 |     attach_synthetic_to_type(UtilsVectorRefPrinter, r'^tint::VectorRef<.+>$', | 
 |                              True) | 
 |  | 
 |     attach_synthetic_to_type(UtilsHashsetPrinter, r'^tint::Hashset<.+>$', True) | 
 |  | 
 |     attach_synthetic_to_type(UtilsHashmapPrinter, r'^tint::Hashmap<.+>$', True) | 
 |  | 
 |  | 
 | def attach_synthetic_to_type(synth_class, type_name, is_regex=False): | 
 |     global module, tint_category | 
 |     synth = lldb.SBTypeSynthetic.CreateWithClassName( | 
 |         __name__ + '.' + synth_class.__name__) | 
 |     synth.SetOptions(lldb.eTypeOptionCascade) | 
 |     ret = tint_category.AddTypeSynthetic( | 
 |         lldb.SBTypeNameSpecifier(type_name, is_regex), synth) | 
 |     log.debug('attaching synthetic %s to "%s", is_regex=%s -> %s', | 
 |               synth_class.__name__, type_name, is_regex, ret) | 
 |  | 
 |     def summary_fn(valobj, dict): return get_synth_summary( | 
 |         synth_class, valobj, dict) | 
 |     # LLDB accesses summary fn's by name, so we need to create a unique one. | 
 |     summary_fn.__name__ = '_get_synth_summary_' + synth_class.__name__ | 
 |     setattr(module, summary_fn.__name__, summary_fn) | 
 |     attach_summary_to_type(summary_fn, type_name, is_regex) | 
 |  | 
 |  | 
 | def attach_summary_to_type(summary_fn, type_name, is_regex=False): | 
 |     global module, tint_category | 
 |     summary = lldb.SBTypeSummary.CreateWithFunctionName( | 
 |         __name__ + '.' + summary_fn.__name__) | 
 |     summary.SetOptions(lldb.eTypeOptionCascade) | 
 |     ret = tint_category.AddTypeSummary( | 
 |         lldb.SBTypeNameSpecifier(type_name, is_regex), summary) | 
 |     log.debug('attaching summary %s to "%s", is_regex=%s -> %s', | 
 |               summary_fn.__name__, type_name, is_regex, ret) | 
 |  | 
 |  | 
 | def get_synth_summary(synth_class, valobj, dict): | 
 |     '''' | 
 |     get_summary' is annoyingly not a part of the standard LLDB synth provider API. | 
 |     This trick allows us to share data extraction logic between synth providers and their sibling summary providers. | 
 |     ''' | 
 |     synth = synth_class(valobj.GetNonSyntheticValue(), dict) | 
 |     synth.update() | 
 |     summary = synth.get_summary() | 
 |     return to_lldb_str(summary) | 
 |  | 
 |  | 
 | def member(valobj, *chain): | 
 |     '''Performs chained GetChildMemberWithName lookups''' | 
 |     for name in chain: | 
 |         valobj = valobj.GetChildMemberWithName(name) | 
 |     return valobj | 
 |  | 
 |  | 
 | class Printer(object): | 
 |     '''Base class for Printers''' | 
 |  | 
 |     def __init__(self, valobj, dict={}): | 
 |         self.valobj = valobj | 
 |         self.initialize() | 
 |  | 
 |     def initialize(self): | 
 |         return None | 
 |  | 
 |     def update(self): | 
 |         return False | 
 |  | 
 |     def num_children(self): | 
 |         return 0 | 
 |  | 
 |     def has_children(self): | 
 |         return False | 
 |  | 
 |     def get_child_at_index(self, index): | 
 |         return None | 
 |  | 
 |     def get_child_index(self, name): | 
 |         return None | 
 |  | 
 |     def get_summary(self): | 
 |         return None | 
 |  | 
 |     def member(self, *chain): | 
 |         '''Performs chained GetChildMemberWithName lookups''' | 
 |         return member(self.valobj, *chain) | 
 |  | 
 |     def template_params(self): | 
 |         '''Returns list of template params values (as strings)''' | 
 |         type_name = self.valobj.GetTypeName() | 
 |         params = [] | 
 |         level = 0 | 
 |         start = 0 | 
 |         for i, c in enumerate(type_name): | 
 |             if c == '<': | 
 |                 level += 1 | 
 |                 if level == 1: | 
 |                     start = i + 1 | 
 |             elif c == '>': | 
 |                 level -= 1 | 
 |                 if level == 0: | 
 |                     params.append(type_name[start:i].strip()) | 
 |             elif c == ',' and level == 1: | 
 |                 params.append(type_name[start:i].strip()) | 
 |                 start = i + 1 | 
 |         return params | 
 |  | 
 |     def template_param_at(self, index): | 
 |         '''Returns template param value at index (as string)''' | 
 |         return self.template_params()[index] | 
 |  | 
 |  | 
 | class UtilsSlicePrinter(Printer): | 
 |     '''Printer for tint::Slice<T>''' | 
 |  | 
 |     def initialize(self): | 
 |         self.len = self.valobj.GetChildMemberWithName('len') | 
 |         self.cap = self.valobj.GetChildMemberWithName('cap') | 
 |         self.data = self.valobj.GetChildMemberWithName('data') | 
 |         self.elem_type = self.data.GetType().GetPointeeType() | 
 |         self.elem_size = self.elem_type.GetByteSize() | 
 |  | 
 |     def get_summary(self): | 
 |         return 'length={} capacity={}'.format(self.len.GetValueAsUnsigned(), self.cap.GetValueAsUnsigned()) | 
 |  | 
 |     def num_children(self): | 
 |         # NOTE: VS Code on MacOS hangs if we try to expand something too large, so put an artificial limit | 
 |         # until we can figure out how to know if this is a valid instance. | 
 |         return min(self.len.GetValueAsUnsigned(), 256) | 
 |  | 
 |     def has_children(self): | 
 |         return True | 
 |  | 
 |     def get_child_at_index(self, index): | 
 |         try: | 
 |             if not 0 <= index < self.num_children(): | 
 |                 return None | 
 |             # TODO: return self.value_at(index) | 
 |             offset = index * self.elem_size | 
 |             return self.data.CreateChildAtOffset('[%s]' % index, offset, self.elem_type) | 
 |         except Exception as e: | 
 |             log.error('%s', e) | 
 |             raise | 
 |  | 
 |     def value_at(self, index): | 
 |         '''Returns array value at index''' | 
 |         offset = index * self.elem_size | 
 |         return self.data.CreateChildAtOffset('[%s]' % index, offset, self.elem_type) | 
 |  | 
 |  | 
 | class UtilsVectorPrinter(Printer): | 
 |     '''Printer for tint::Vector<T, N>''' | 
 |  | 
 |     def initialize(self): | 
 |         self.slice = self.member('impl_', 'slice') | 
 |         self.slice_printer = UtilsSlicePrinter(self.slice) | 
 |         self.fixed_size = int(self.template_param_at(1)) | 
 |         self.cap = self.slice_printer.member('cap') | 
 |  | 
 |     def get_summary(self): | 
 |         using_heap = self.cap.GetValueAsUnsigned() > self.fixed_size | 
 |         return 'heap={} {}'.format(using_heap, self.slice_printer.get_summary()) | 
 |  | 
 |     def num_children(self): | 
 |         return self.slice_printer.num_children() | 
 |  | 
 |     def has_children(self): | 
 |         return self.slice_printer.has_children() | 
 |  | 
 |     def get_child_at_index(self, index): | 
 |         return self.slice_printer.get_child_at_index(index) | 
 |  | 
 |     def make_slice_printer(self): | 
 |         return UtilsSlicePrinter(self.slice) | 
 |  | 
 |  | 
 | class UtilsVectorRefPrinter(Printer): | 
 |     '''Printer for tint::VectorRef<T>''' | 
 |  | 
 |     def initialize(self): | 
 |         self.slice = self.member('slice_') | 
 |         self.slice_printer = UtilsSlicePrinter(self.slice) | 
 |         self.can_move = self.member('can_move_') | 
 |  | 
 |     def get_summary(self): | 
 |         return 'can_move={} {}'.format(self.can_move.GetValue(), self.slice_printer.get_summary()) | 
 |  | 
 |     def num_children(self): | 
 |         return self.slice_printer.num_children() | 
 |  | 
 |     def has_children(self): | 
 |         return self.slice_printer.has_children() | 
 |  | 
 |     def get_child_at_index(self, index): | 
 |         return self.slice_printer.get_child_at_index(index) | 
 |  | 
 |  | 
 | class UtilsHashmapBasePrinter(Printer): | 
 |     '''Base Printer for HashmapBase-derived types''' | 
 |  | 
 |     def initialize(self): | 
 |         self.slice = UtilsVectorPrinter( | 
 |             self.member('slots_')).make_slice_printer() | 
 |  | 
 |         self.try_read_std_optional_func = self.try_read_std_optional | 
 |  | 
 |     def update(self): | 
 |         self.valid_slots = [] | 
 |         for slot in range(0, self.slice.num_children()): | 
 |             v = self.slice.value_at(slot) | 
 |             if member(v, 'hash').GetValueAsUnsigned() != 0: | 
 |                 self.valid_slots.append(slot) | 
 |         return False | 
 |  | 
 |     def get_summary(self): | 
 |         return 'length={}'.format(self.num_children()) | 
 |  | 
 |     def num_children(self): | 
 |         return len(self.valid_slots) | 
 |  | 
 |     def has_children(self): | 
 |         return True | 
 |  | 
 |     def get_child_at_index(self, index): | 
 |         slot = self.valid_slots[index] | 
 |         v = self.slice.value_at(slot) | 
 |         entry = member(v, 'entry') | 
 |  | 
 |         # entry is a std::optional, let's try to extract its value for display | 
 |         kvp = self.try_read_std_optional_func(slot, entry) | 
 |         if kvp is None: | 
 |             # If we failed, just output the slot and entry as is, which will use | 
 |             # the default printer for std::optional. | 
 |             kvp = slot, entry | 
 |  | 
 |         return kvp[1].CreateChildAtOffset('[{}]'.format(kvp[0]), 0, kvp[1].GetType()) | 
 |  | 
 |     def try_read_std_optional(self, slot, entry): | 
 |         return None | 
 |  | 
 |  | 
 | class UtilsHashsetPrinter(UtilsHashmapBasePrinter): | 
 |     '''Printer for Hashset<T, N, HASH, EQUAL>''' | 
 |  | 
 |     def try_read_std_optional(self, slot, entry): | 
 |         try: | 
 |             # libc++ | 
 |             v = entry.EvaluateExpression('__val_') | 
 |             if v.name is not None: | 
 |                 return slot, v | 
 |  | 
 |             # libstdc++ | 
 |             v = entry.EvaluateExpression('_M_payload._M_payload._M_value') | 
 |             if v.name is not None: | 
 |                 return slot, v | 
 |             return None | 
 |         except: | 
 |             return None | 
 |  | 
 |  | 
 | class UtilsHashmapPrinter(UtilsHashsetPrinter): | 
 |     '''Printer for Hashmap<K, V, N, HASH, EQUAL>''' | 
 |  | 
 |     def try_read_std_optional(self, slot, entry): | 
 |         try: | 
 |             # libc++ | 
 |             val = entry.EvaluateExpression('__val_') | 
 |             k = val.EvaluateExpression('key') | 
 |             v = val.EvaluateExpression('value') | 
 |             if k.name is not None and v.name is not None: | 
 |                 return k.GetValue(), v | 
 |  | 
 |             # libstdc++ | 
 |             val = entry.EvaluateExpression('_M_payload._M_payload._M_value') | 
 |             k = val.EvaluateExpression('key') | 
 |             v = val.EvaluateExpression('value') | 
 |             if k.name is not None and v.name is not None: | 
 |                 return k.GetValue(), v | 
 |             return None | 
 |         except: | 
 |             return None |