summaryrefslogtreecommitdiffstats
path: root/js/src/gdb/mozilla/prettyprinters.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /js/src/gdb/mozilla/prettyprinters.py
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/gdb/mozilla/prettyprinters.py')
-rw-r--r--js/src/gdb/mozilla/prettyprinters.py447
1 files changed, 447 insertions, 0 deletions
diff --git a/js/src/gdb/mozilla/prettyprinters.py b/js/src/gdb/mozilla/prettyprinters.py
new file mode 100644
index 0000000000..130e7d847c
--- /dev/null
+++ b/js/src/gdb/mozilla/prettyprinters.py
@@ -0,0 +1,447 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# mozilla/prettyprinters.py --- infrastructure for SpiderMonkey's auto-loaded pretty-printers.
+
+import re
+
+import gdb
+
+# Decorators for declaring pretty-printers.
+#
+# In each case, the decoratee should be a SpiderMonkey-style pretty-printer
+# factory, taking both a gdb.Value instance and a TypeCache instance as
+# arguments; see TypeCache, below.
+
+
+def check_for_reused_pretty_printer(fn):
+ # Check that |fn| hasn't been registered as a pretty-printer under some
+ # other name already. (The 'enabled' flags used by GDB's
+ # 'enable/disable/info pretty-printer' commands are simply stored as
+ # properties of the function objects themselves, so a single function
+ # object can't carry the 'enabled' flags for two different printers.)
+ if hasattr(fn, "enabled"):
+ raise RuntimeError("pretty-printer function %r registered more than once" % fn)
+
+
+# a dictionary mapping gdb.Type tags to pretty-printer functions.
+printers_by_tag = {}
+
+# A decorator: add the decoratee as a pretty-printer lookup function for types
+# named |type_name|.
+
+
+def pretty_printer(type_name):
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, type_name)
+ printers_by_tag[type_name] = fn
+ return fn
+
+ return add
+
+
+# a dictionary mapping gdb.Type tags to pretty-printer functions for pointers to
+# that type.
+ptr_printers_by_tag = {}
+
+# A decorator: add the decoratee as a pretty-printer lookup function for
+# pointers to types named |type_name|.
+
+
+def ptr_pretty_printer(type_name):
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, "ptr-to-" + type_name)
+ ptr_printers_by_tag[type_name] = fn
+ return fn
+
+ return add
+
+
+# a dictionary mapping gdb.Type tags to pretty-printer functions for
+# references to that type.
+ref_printers_by_tag = {}
+
+# A decorator: add the decoratee as a pretty-printer lookup function for
+# references to instances of types named |type_name|.
+
+
+def ref_pretty_printer(type_name):
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, "ref-to-" + type_name)
+ ref_printers_by_tag[type_name] = fn
+ return fn
+
+ return add
+
+
+# a dictionary mapping the template name portion of gdb.Type tags to
+# pretty-printer functions for instantiations of that template.
+template_printers_by_tag = {}
+
+# A decorator: add the decoratee as a pretty-printer lookup function for
+# instantiations of templates named |template_name|.
+
+
+def template_pretty_printer(template_name):
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, "instantiations-of-" + template_name)
+ template_printers_by_tag[template_name] = fn
+ return fn
+
+ return add
+
+
+# A list of (REGEXP, PRINTER) pairs, such that if REGEXP (a RegexObject)
+# matches the result of converting a gdb.Value's type to a string, then
+# PRINTER is a pretty-printer lookup function that will probably like that
+# value.
+printers_by_regexp = []
+
+# A decorator: add the decoratee as a pretty-printer factory for types
+# that, when converted to a string, match |pattern|. Use |name| as the
+# pretty-printer's name, when listing, enabling and disabling.
+
+
+def pretty_printer_for_regexp(pattern, name):
+ compiled = re.compile(pattern)
+
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, name)
+ printers_by_regexp.append((compiled, fn))
+ return fn
+
+ return add
+
+
+# Forget all pretty-printer lookup functions defined in the module name
+# |module_name|, if any exist. Use this at the top of each pretty-printer
+# module like this:
+#
+# clear_module_printers(__name__)
+
+
+def clear_module_printers(module_name):
+ global printers_by_tag, ptr_printers_by_tag, ref_printers_by_tag
+ global template_printers_by_tag, printers_by_regexp
+
+ # Remove all pretty-printers defined in the module named |module_name|
+ # from d.
+ def clear_dictionary(d):
+ # Walk the dictionary, building a list of keys whose entries we
+ # should remove. (It's not safe to delete entries from a dictionary
+ # while we're iterating over it.)
+ to_delete = []
+ for (k, v) in d.items():
+ if v.__module__ == module_name:
+ to_delete.append(k)
+ remove_from_subprinter_list(v)
+ for k in to_delete:
+ del d[k]
+
+ clear_dictionary(printers_by_tag)
+ clear_dictionary(ptr_printers_by_tag)
+ clear_dictionary(ref_printers_by_tag)
+ clear_dictionary(template_printers_by_tag)
+
+ # Iterate over printers_by_regexp, deleting entries from the given module.
+ new_list = []
+ for p in printers_by_regexp:
+ if p.__module__ == module_name:
+ remove_from_subprinter_list(p)
+ else:
+ new_list.append(p)
+ printers_by_regexp = new_list
+
+
+# Our subprinters array. The 'subprinters' attributes of all lookup
+# functions returned by lookup_for_objfile point to this array instance,
+# which we mutate as subprinters are added and removed.
+subprinters = []
+
+# Set up the 'name' and 'enabled' attributes on |subprinter|, and add it to our
+# list of all SpiderMonkey subprinters.
+
+
+def add_to_subprinter_list(subprinter, name):
+ subprinter.name = name
+ subprinter.enabled = True
+ subprinters.append(subprinter)
+
+
+# Remove |subprinter| from our list of all SpiderMonkey subprinters.
+
+
+def remove_from_subprinter_list(subprinter):
+ subprinters.remove(subprinter)
+
+
+# An exception class meaning, "This objfile has no SpiderMonkey in it."
+
+
+class NotSpiderMonkeyObjfileError(TypeError):
+ pass
+
+
+# TypeCache: a cache for frequently used information about an objfile.
+#
+# When a new SpiderMonkey objfile is loaded, we construct an instance of
+# this class for it. Then, whenever we construct a pretty-printer for some
+# gdb.Value, we also pass, as a second argument, the TypeCache for the
+# objfile to which that value's type belongs.
+#
+# if objfile doesn't seem to have SpiderMonkey code in it, the constructor
+# raises NotSpiderMonkeyObjfileError.
+#
+# Pretty-printer modules may add attributes to this to hold their own
+# cached values. Such attributes should be named mod_NAME, where the module
+# is named mozilla.NAME; for example, mozilla.JSString should store its
+# metadata in the TypeCache's mod_JSString attribute.
+
+
+class TypeCache(object):
+ def __init__(self, objfile):
+ self.objfile = objfile
+
+ # Unfortunately, the Python interface doesn't allow us to specify
+ # the objfile in whose scope lookups should occur. But simply
+ # knowing that we need to lookup the types afresh is probably
+ # enough.
+ self.void_t = gdb.lookup_type("void")
+ self.void_ptr_t = self.void_t.pointer()
+ self.uintptr_t = gdb.lookup_type("uintptr_t")
+ try:
+ self.JSString_ptr_t = gdb.lookup_type("JSString").pointer()
+ self.JSSymbol_ptr_t = gdb.lookup_type("JS::Symbol").pointer()
+ self.JSObject_ptr_t = gdb.lookup_type("JSObject").pointer()
+ except gdb.error:
+ raise NotSpiderMonkeyObjfileError
+
+ self.mod_GCCellPtr = None
+ self.mod_Interpreter = None
+ self.mod_JSObject = None
+ self.mod_JSOp = None
+ self.mod_JSString = None
+ self.mod_JS_Value = None
+ self.mod_ExecutableAllocator = None
+ self.mod_IonGraph = None
+
+
+# Yield a series of all the types that |t| implements, by following typedefs
+# and iterating over base classes. Specifically:
+# - |t| itself is the first value yielded.
+# - If we yield a typedef, we later yield its definition.
+# - If we yield a type with base classes, we later yield those base classes.
+# - If we yield a type with some base classes that are typedefs,
+# we yield all the type's base classes before following the typedefs.
+#
+# This is a hokey attempt to order the implemented types by meaningfulness when
+# pretty-printed. Perhaps it is entirely misguided, and we should actually
+# collect all applicable pretty-printers, and then use some ordering on the
+# pretty-printers themselves.
+#
+# We may yield a type more than once (say, if it appears more than once in the
+# class hierarchy).
+
+
+def implemented_types(t):
+
+ # Yield all types that follow |t|.
+ def followers(t):
+ if t.code == gdb.TYPE_CODE_TYPEDEF:
+ yield t.target()
+ for t2 in followers(t.target()):
+ yield t2
+ elif is_struct_or_union(t):
+ base_classes = []
+ for f in t.fields():
+ if f.is_base_class:
+ yield f.type
+ base_classes.append(f.type)
+ for b in base_classes:
+ for t2 in followers(b):
+ yield t2
+
+ yield t
+ for t2 in followers(t):
+ yield t2
+
+
+template_regexp = re.compile("([\w_:]+)<")
+
+
+def is_struct_or_union(t):
+ return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION)
+
+
+def is_struct_or_union_or_enum(t):
+ return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION, gdb.TYPE_CODE_ENUM)
+
+
+# Construct and return a pretty-printer lookup function for objfile, or
+# return None if the objfile doesn't contain SpiderMonkey code
+# (specifically, definitions for SpiderMonkey types).
+
+
+def lookup_for_objfile(objfile):
+ # Create a type cache for this objfile.
+ try:
+ cache = TypeCache(objfile)
+ except NotSpiderMonkeyObjfileError:
+ if gdb.parameter("verbose"):
+ gdb.write(
+ "objfile '%s' has no SpiderMonkey code; not registering pretty-printers\n"
+ % (objfile.filename,)
+ )
+ return None
+
+ # Return a pretty-printer for |value|, if we have one. This is the lookup
+ # function object we place in each gdb.Objfile's pretty-printers list, so it
+ # carries |name|, |enabled|, and |subprinters| attributes.
+ def lookup(value):
+ # If |table| has a pretty-printer for |tag|, apply it to |value|.
+ def check_table(table, tag):
+ if tag in table:
+ f = table[tag]
+ if f.enabled:
+ return f(value, cache)
+ return None
+
+ def check_table_by_type_name(table, t):
+ if t.code == gdb.TYPE_CODE_TYPEDEF:
+ return check_table(table, str(t))
+ elif is_struct_or_union_or_enum(t) and t.tag:
+ return check_table(table, t.tag)
+ else:
+ return None
+
+ for t in implemented_types(value.type):
+ if t.code == gdb.TYPE_CODE_PTR:
+ for t2 in implemented_types(t.target()):
+ p = check_table_by_type_name(ptr_printers_by_tag, t2)
+ if p:
+ return p
+ elif t.code == gdb.TYPE_CODE_REF:
+ for t2 in implemented_types(t.target()):
+ p = check_table_by_type_name(ref_printers_by_tag, t2)
+ if p:
+ return p
+ else:
+ p = check_table_by_type_name(printers_by_tag, t)
+ if p:
+ return p
+ if is_struct_or_union(t) and t.tag:
+ m = template_regexp.match(t.tag)
+ if m:
+ p = check_table(template_printers_by_tag, m.group(1))
+ if p:
+ return p
+
+ # Failing that, look for a printer in printers_by_regexp. We have
+ # to scan the whole list, so regexp printers should be used
+ # sparingly.
+ s = str(value.type)
+ for (r, f) in printers_by_regexp:
+ if f.enabled:
+ m = r.match(s)
+ if m:
+ p = f(value, cache)
+ if p:
+ return p
+
+ # No luck.
+ return None
+
+ # Give |lookup| the attributes expected of a pretty-printer with
+ # subprinters, for enabling and disabling.
+ lookup.name = "SpiderMonkey"
+ lookup.enabled = True
+ lookup.subprinters = subprinters
+
+ return lookup
+
+
+# A base class for pretty-printers for pointer values that handles null
+# pointers, by declining to construct a pretty-printer for them at all.
+# Derived classes may simply assume that self.value is non-null.
+#
+# To help share code, this class can also be used with reference types.
+#
+# This class provides the following methods, which subclasses are free to
+# override:
+#
+# __init__(self, value, cache): Save value and cache as properties by those names
+# on the instance.
+#
+# to_string(self): format the type's name and address, as GDB would, and then
+# call a 'summary' method (which the subclass must define) to produce a
+# description of the referent.
+#
+# Note that pretty-printers returning a 'string' display hint must not use
+# this default 'to_string' method, as GDB will take everything it returns,
+# including the type name and address, as string contents.
+
+
+class Pointer(object):
+ def __new__(cls, value, cache):
+ # Don't try to provide pretty-printers for NULL pointers.
+ if value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR and value == 0:
+ return None
+ return super(Pointer, cls).__new__(cls)
+
+ def __init__(self, value, cache):
+ self.value = value
+ self.cache = cache
+
+ def to_string(self):
+ # See comment above.
+ assert not hasattr(self, "display_hint") or self.display_hint() != "string"
+ concrete_type = self.value.type.strip_typedefs()
+ if concrete_type.code == gdb.TYPE_CODE_PTR:
+ address = self.value.cast(self.cache.void_ptr_t)
+ elif concrete_type.code == gdb.TYPE_CODE_REF:
+ address = "@" + str(self.value.address.cast(self.cache.void_ptr_t))
+ else:
+ assert not "mozilla.prettyprinters.Pointer applied to bad value type"
+ try:
+ summary = self.summary()
+ except gdb.MemoryError as r:
+ summary = str(r)
+ v = "(%s) %s %s" % (self.value.type, address, summary)
+ return v
+
+ def summary(self):
+ raise NotImplementedError
+
+
+field_enum_value = None
+
+# Given |t|, a gdb.Type instance representing an enum type, return the
+# numeric value of the enum value named |name|.
+#
+# Pre-2012-4-18 versions of GDB store the value of an enum member on the
+# gdb.Field's 'bitpos' attribute; later versions store it on the 'enumval'
+# attribute. This function retrieves the value from either.
+
+
+def enum_value(t, name):
+ global field_enum_value
+ f = t[name]
+ # Monkey-patching is a-okay in polyfills! Just because.
+ if not field_enum_value:
+ if hasattr(f, "enumval"):
+
+ def field_enum_value(f):
+ return f.enumval
+
+ else:
+
+ def field_enum_value(f):
+ return f.bitpos
+
+ return field_enum_value(f)