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