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)