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