tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

prettyprinters.py (14970B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 # You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 # mozilla/prettyprinters.py --- infrastructure for SpiderMonkey's auto-loaded pretty-printers.
      6 
      7 import re
      8 
      9 import gdb
     10 
     11 # Decorators for declaring pretty-printers.
     12 #
     13 # In each case, the decoratee should be a SpiderMonkey-style pretty-printer
     14 # factory, taking both a gdb.Value instance and a TypeCache instance as
     15 # arguments; see TypeCache, below.
     16 
     17 
     18 def check_for_reused_pretty_printer(fn):
     19    # Check that |fn| hasn't been registered as a pretty-printer under some
     20    # other name already. (The 'enabled' flags used by GDB's
     21    # 'enable/disable/info pretty-printer' commands are simply stored as
     22    # properties of the function objects themselves, so a single function
     23    # object can't carry the 'enabled' flags for two different printers.)
     24    if hasattr(fn, "enabled"):
     25        raise RuntimeError("pretty-printer function %r registered more than once" % fn)
     26 
     27 
     28 # a dictionary mapping gdb.Type tags to pretty-printer functions.
     29 printers_by_tag = {}
     30 
     31 # A decorator: add the decoratee as a pretty-printer lookup function for types
     32 # named |type_name|.
     33 
     34 
     35 def pretty_printer(type_name):
     36    def add(fn):
     37        check_for_reused_pretty_printer(fn)
     38        add_to_subprinter_list(fn, type_name)
     39        printers_by_tag[type_name] = fn
     40        return fn
     41 
     42    return add
     43 
     44 
     45 # a dictionary mapping gdb.Type tags to pretty-printer functions for pointers to
     46 # that type.
     47 ptr_printers_by_tag = {}
     48 
     49 # A decorator: add the decoratee as a pretty-printer lookup function for
     50 # pointers to types named |type_name|.
     51 
     52 
     53 def ptr_pretty_printer(type_name):
     54    def add(fn):
     55        check_for_reused_pretty_printer(fn)
     56        add_to_subprinter_list(fn, "ptr-to-" + type_name)
     57        ptr_printers_by_tag[type_name] = fn
     58        return fn
     59 
     60    return add
     61 
     62 
     63 # a dictionary mapping gdb.Type tags to pretty-printer functions for
     64 # references to that type.
     65 ref_printers_by_tag = {}
     66 
     67 # A decorator: add the decoratee as a pretty-printer lookup function for
     68 # references to instances of types named |type_name|.
     69 
     70 
     71 def ref_pretty_printer(type_name):
     72    def add(fn):
     73        check_for_reused_pretty_printer(fn)
     74        add_to_subprinter_list(fn, "ref-to-" + type_name)
     75        ref_printers_by_tag[type_name] = fn
     76        return fn
     77 
     78    return add
     79 
     80 
     81 # a dictionary mapping the template name portion of gdb.Type tags to
     82 # pretty-printer functions for instantiations of that template.
     83 template_printers_by_tag = {}
     84 
     85 # A decorator: add the decoratee as a pretty-printer lookup function for
     86 # instantiations of templates named |template_name|.
     87 
     88 
     89 def template_pretty_printer(template_name):
     90    def add(fn):
     91        check_for_reused_pretty_printer(fn)
     92        add_to_subprinter_list(fn, "instantiations-of-" + template_name)
     93        template_printers_by_tag[template_name] = fn
     94        return fn
     95 
     96    return add
     97 
     98 
     99 # A list of (REGEXP, PRINTER) pairs, such that if REGEXP (a RegexObject)
    100 # matches the result of converting a gdb.Value's type to a string, then
    101 # PRINTER is a pretty-printer lookup function that will probably like that
    102 # value.
    103 printers_by_regexp = []
    104 
    105 # A decorator: add the decoratee as a pretty-printer factory for types
    106 # that, when converted to a string, match |pattern|. Use |name| as the
    107 # pretty-printer's name, when listing, enabling and disabling.
    108 
    109 
    110 def pretty_printer_for_regexp(pattern, name):
    111    compiled = re.compile(pattern)
    112 
    113    def add(fn):
    114        check_for_reused_pretty_printer(fn)
    115        add_to_subprinter_list(fn, name)
    116        printers_by_regexp.append((compiled, fn))
    117        return fn
    118 
    119    return add
    120 
    121 
    122 # Forget all pretty-printer lookup functions defined in the module name
    123 # |module_name|, if any exist. Use this at the top of each pretty-printer
    124 # module like this:
    125 #
    126 #   clear_module_printers(__name__)
    127 
    128 
    129 def clear_module_printers(module_name):
    130    global printers_by_regexp
    131 
    132    # Remove all pretty-printers defined in the module named |module_name|
    133    # from d.
    134    def clear_dictionary(d):
    135        # Walk the dictionary, building a list of keys whose entries we
    136        # should remove. (It's not safe to delete entries from a dictionary
    137        # while we're iterating over it.)
    138        to_delete = []
    139        for k, v in d.items():
    140            if v.__module__ == module_name:
    141                to_delete.append(k)
    142                remove_from_subprinter_list(v)
    143        for k in to_delete:
    144            del d[k]
    145 
    146    clear_dictionary(printers_by_tag)
    147    clear_dictionary(ptr_printers_by_tag)
    148    clear_dictionary(ref_printers_by_tag)
    149    clear_dictionary(template_printers_by_tag)
    150 
    151    # Iterate over printers_by_regexp, deleting entries from the given module.
    152    new_list = []
    153    for p in printers_by_regexp:
    154        if p.__module__ == module_name:
    155            remove_from_subprinter_list(p)
    156        else:
    157            new_list.append(p)
    158    printers_by_regexp = new_list
    159 
    160 
    161 # Our subprinters array. The 'subprinters' attributes of all lookup
    162 # functions returned by lookup_for_objfile point to this array instance,
    163 # which we mutate as subprinters are added and removed.
    164 subprinters = []
    165 
    166 # Set up the 'name' and 'enabled' attributes on |subprinter|, and add it to our
    167 # list of all SpiderMonkey subprinters.
    168 
    169 
    170 def add_to_subprinter_list(subprinter, name):
    171    subprinter.name = name
    172    subprinter.enabled = True
    173    subprinters.append(subprinter)
    174 
    175 
    176 # Remove |subprinter| from our list of all SpiderMonkey subprinters.
    177 
    178 
    179 def remove_from_subprinter_list(subprinter):
    180    subprinters.remove(subprinter)
    181 
    182 
    183 # An exception class meaning, "This objfile has no SpiderMonkey in it."
    184 
    185 
    186 class NotSpiderMonkeyObjfileError(TypeError):
    187    pass
    188 
    189 
    190 # TypeCache: a cache for frequently used information about an objfile.
    191 #
    192 # When a new SpiderMonkey objfile is loaded, we construct an instance of
    193 # this class for it. Then, whenever we construct a pretty-printer for some
    194 # gdb.Value, we also pass, as a second argument, the TypeCache for the
    195 # objfile to which that value's type belongs.
    196 #
    197 # if objfile doesn't seem to have SpiderMonkey code in it, the constructor
    198 # raises NotSpiderMonkeyObjfileError.
    199 #
    200 # Pretty-printer modules may add attributes to this to hold their own
    201 # cached values. Such attributes should be named mod_NAME, where the module
    202 # is named mozilla.NAME; for example, mozilla.JSString should store its
    203 # metadata in the TypeCache's mod_JSString attribute.
    204 
    205 
    206 class TypeCache:
    207    def __init__(self, objfile):
    208        self.objfile = objfile
    209 
    210        # Unfortunately, the Python interface doesn't allow us to specify
    211        # the objfile in whose scope lookups should occur. But simply
    212        # knowing that we need to lookup the types afresh is probably
    213        # enough.
    214        self.void_t = gdb.lookup_type("void")
    215        self.void_ptr_t = self.void_t.pointer()
    216        self.uintptr_t = gdb.lookup_type("uintptr_t")
    217        try:
    218            self.JSString_ptr_t = gdb.lookup_type("JSString").pointer()
    219            self.JSSymbol_ptr_t = gdb.lookup_type("JS::Symbol").pointer()
    220            self.JSObject_ptr_t = gdb.lookup_type("JSObject").pointer()
    221        except gdb.error:
    222            raise NotSpiderMonkeyObjfileError
    223 
    224        self.mod_GCCellPtr = None
    225        self.mod_Interpreter = None
    226        self.mod_JSObject = None
    227        self.mod_JSOp = None
    228        self.mod_JSString = None
    229        self.mod_JS_Value = None
    230        self.mod_ExecutableAllocator = None
    231        self.mod_IonGraph = None
    232 
    233 
    234 # Yield a series of all the types that |t| implements, by following typedefs
    235 # and iterating over base classes. Specifically:
    236 # - |t| itself is the first value yielded.
    237 # - If we yield a typedef, we later yield its definition.
    238 # - If we yield a type with base classes, we later yield those base classes.
    239 # - If we yield a type with some base classes that are typedefs,
    240 #   we yield all the type's base classes before following the typedefs.
    241 #
    242 # This is a hokey attempt to order the implemented types by meaningfulness when
    243 # pretty-printed. Perhaps it is entirely misguided, and we should actually
    244 # collect all applicable pretty-printers, and then use some ordering on the
    245 # pretty-printers themselves.
    246 #
    247 # We may yield a type more than once (say, if it appears more than once in the
    248 # class hierarchy).
    249 
    250 
    251 def implemented_types(t):
    252    # Yield all types that follow |t|.
    253    def followers(t):
    254        if t.code == gdb.TYPE_CODE_TYPEDEF:
    255            yield t.target()
    256            for t2 in followers(t.target()):
    257                yield t2
    258        elif is_struct_or_union(t):
    259            base_classes = []
    260            for f in t.fields():
    261                if f.is_base_class:
    262                    yield f.type
    263                    base_classes.append(f.type)
    264            for b in base_classes:
    265                for t2 in followers(b):
    266                    yield t2
    267 
    268    yield t
    269    yield from followers(t)
    270 
    271 
    272 template_regexp = re.compile(r"([\w_:]+)<")
    273 
    274 
    275 def is_struct_or_union(t):
    276    return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION)
    277 
    278 
    279 def is_struct_or_union_or_enum(t):
    280    return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION, gdb.TYPE_CODE_ENUM)
    281 
    282 
    283 # Construct and return a pretty-printer lookup function for objfile, or
    284 # return None if the objfile doesn't contain SpiderMonkey code
    285 # (specifically, definitions for SpiderMonkey types).
    286 
    287 
    288 def lookup_for_objfile(objfile):
    289    # Create a type cache for this objfile.
    290    try:
    291        cache = TypeCache(objfile)
    292    except NotSpiderMonkeyObjfileError:
    293        if gdb.parameter("verbose"):
    294            gdb.write(
    295                "objfile '%s' has no SpiderMonkey code; not registering pretty-printers\n"
    296                % (objfile.filename,)
    297            )
    298        return None
    299 
    300    # Return a pretty-printer for |value|, if we have one. This is the lookup
    301    # function object we place in each gdb.Objfile's pretty-printers list, so it
    302    # carries |name|, |enabled|, and |subprinters| attributes.
    303    def lookup(value):
    304        # If |table| has a pretty-printer for |tag|, apply it to |value|.
    305        def check_table(table, tag):
    306            if tag in table:
    307                f = table[tag]
    308                if f.enabled:
    309                    return f(value, cache)
    310            return None
    311 
    312        def check_table_by_type_name(table, t):
    313            if t.code == gdb.TYPE_CODE_TYPEDEF:
    314                return check_table(table, str(t))
    315            elif is_struct_or_union_or_enum(t) and t.tag:
    316                return check_table(table, t.tag)
    317            else:
    318                return None
    319 
    320        for t in implemented_types(value.type):
    321            if t.code == gdb.TYPE_CODE_PTR:
    322                for t2 in implemented_types(t.target()):
    323                    p = check_table_by_type_name(ptr_printers_by_tag, t2)
    324                    if p:
    325                        return p
    326            elif t.code == gdb.TYPE_CODE_REF:
    327                for t2 in implemented_types(t.target()):
    328                    p = check_table_by_type_name(ref_printers_by_tag, t2)
    329                    if p:
    330                        return p
    331            else:
    332                p = check_table_by_type_name(printers_by_tag, t)
    333                if p:
    334                    return p
    335                if is_struct_or_union(t) and t.tag:
    336                    m = template_regexp.match(t.tag)
    337                    if m:
    338                        p = check_table(template_printers_by_tag, m.group(1))
    339                        if p:
    340                            return p
    341 
    342        # Failing that, look for a printer in printers_by_regexp. We have
    343        # to scan the whole list, so regexp printers should be used
    344        # sparingly.
    345        s = str(value.type)
    346        for r, f in printers_by_regexp:
    347            if f.enabled:
    348                m = r.match(s)
    349                if m:
    350                    p = f(value, cache)
    351                    if p:
    352                        return p
    353 
    354        # No luck.
    355        return None
    356 
    357    # Give |lookup| the attributes expected of a pretty-printer with
    358    # subprinters, for enabling and disabling.
    359    lookup.name = "SpiderMonkey"
    360    lookup.enabled = True
    361    lookup.subprinters = subprinters
    362 
    363    return lookup
    364 
    365 
    366 # A base class for pretty-printers for pointer values that handles null
    367 # pointers, by declining to construct a pretty-printer for them at all.
    368 # Derived classes may simply assume that self.value is non-null.
    369 #
    370 # To help share code, this class can also be used with reference types.
    371 #
    372 # This class provides the following methods, which subclasses are free to
    373 # override:
    374 #
    375 # __init__(self, value, cache): Save value and cache as properties by those names
    376 #     on the instance.
    377 #
    378 # to_string(self): format the type's name and address, as GDB would, and then
    379 #     call a 'summary' method (which the subclass must define) to produce a
    380 #     description of the referent.
    381 #
    382 #     Note that pretty-printers returning a 'string' display hint must not use
    383 #     this default 'to_string' method, as GDB will take everything it returns,
    384 #     including the type name and address, as string contents.
    385 
    386 
    387 class Pointer:
    388    def __new__(cls, value, cache):
    389        # Don't try to provide pretty-printers for NULL pointers.
    390        if value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR and value == 0:
    391            return None
    392        return super().__new__(cls)
    393 
    394    def __init__(self, value, cache):
    395        self.value = value
    396        self.cache = cache
    397 
    398    def to_string(self):
    399        # See comment above.
    400        assert not hasattr(self, "display_hint") or self.display_hint() != "string"
    401        concrete_type = self.value.type.strip_typedefs()
    402        if concrete_type.code == gdb.TYPE_CODE_PTR:
    403            address = self.value.cast(self.cache.void_ptr_t)
    404        elif concrete_type.code == gdb.TYPE_CODE_REF:
    405            address = "@" + str(self.value.address.cast(self.cache.void_ptr_t))
    406        else:
    407            assert not "mozilla.prettyprinters.Pointer applied to bad value type"
    408        try:
    409            summary = self.summary()
    410        except gdb.MemoryError as r:
    411            summary = str(r)
    412        v = "(%s) %s %s" % (self.value.type, address, summary)
    413        return v
    414 
    415    def summary(self):
    416        raise NotImplementedError
    417 
    418 
    419 field_enum_value = None
    420 
    421 # Given |t|, a gdb.Type instance representing an enum type, return the
    422 # numeric value of the enum value named |name|.
    423 #
    424 # Pre-2012-4-18 versions of GDB store the value of an enum member on the
    425 # gdb.Field's 'bitpos' attribute; later versions store it on the 'enumval'
    426 # attribute. This function retrieves the value from either.
    427 
    428 
    429 def enum_value(t, name):
    430    global field_enum_value
    431    f = t[name]
    432    # Monkey-patching is a-okay in polyfills! Just because.
    433    if not field_enum_value:
    434        if hasattr(f, "enumval"):
    435 
    436            def field_enum_value(f):
    437                return f.enumval
    438 
    439        else:
    440 
    441            def field_enum_value(f):
    442                return f.bitpos
    443 
    444    return field_enum_value(f)