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