Last active
December 11, 2015 07:39
-
-
Save jimblandy/4567915 to your computer and use it in GitHub Desktop.
Patch to SpiderMonkey GDB support: define AggregateMap utility class.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# HG changeset patch | |
# Parent 07b885dd9d132c648f49eac357faa3e90e8741a8 | |
diff --git a/js/src/gdb/mozilla/prettyprinters.py b/js/src/gdb/mozilla/prettyprinters.py | |
--- a/js/src/gdb/mozilla/prettyprinters.py | |
+++ b/js/src/gdb/mozilla/prettyprinters.py | |
@@ -312,6 +312,211 @@ class Pointer(object): | |
def summary(self): | |
raise NotImplementedError | |
+ | |
+# Printing structures containing unions. | |
+# | |
+# Given a structure like this: | |
+# | |
+# struct S { | |
+# int tag; | |
+# union { | |
+# int i; // if tag == 1 | |
+# char *s; // if tag == 2 | |
+# double d; // if tag == 3 | |
+# } u; | |
+# }; | |
+# | |
+# We'd like to be able to write a pretty-printer that prints stuff like this: | |
+# | |
+# $1 = { tag=2, u = { s="hoo boy" } } | |
+# | |
+# That is, when we have a discriminant member like |tag|, we want the printer to | |
+# omit non-live union branches. While limitations of GDB's pretty-printing API | |
+# make this impossible (short of re-implementing a bunch of GDB's aggregate | |
+# printing features, like "set print pretty on"), we can manage something like | |
+# this: | |
+# | |
+# $1 = { tag=2, u.s="hoo boy" } | |
+# | |
+# which flattens the structure, but is serviceable. | |
+# | |
+# We also want our pretty-printers to be robust as the structure types evolve: | |
+# while they can't avoid hard-coding some assumptions about the structure, as | |
+# much as possible they should be guided by the types as they are. | |
+# | |
+# The AggregateMap class helps implement pretty-printers that behave this way. | |
+# | |
+# An AggregateMap instance is immutable, and constructed solely from the | |
+# structure type; it can be computed once per objfile, cached, and used to print | |
+# many values. | |
+# | |
+# With an AggregateMap available, a pretty-printer for a specific value inspects | |
+# the value's discriminants, and names the members it knows to be live to the | |
+# AggregateMap. The AggregateMap infers which union branches must be live in | |
+# order for the given members to be live, and returns a "liveset". Finally, | |
+# given a liveset and a value, the AggregateMap can yield a series of children, | |
+# as a pretty-printer's |children| method would. | |
+# | |
+# Example, for the above type: | |
+# | |
+# @pretty_printer('S') | |
+# class PrintS(object): | |
+# def __init__(self, value, cache): | |
+# self.value = value | |
+# if not self.cache.SMap: | |
+# self.cache.SMap = AggregateMap(value.type) | |
+# self.liveset = set() | |
+# tag = self.value['tag'] | |
+# if tag == 1: | |
+# self.cache.SMap.mark(self.liveset, ['i'] | |
+# elif tag == 2: | |
+# self.cache.SMap.mark(self.liveset, ['s'] | |
+# elif tag == 3: | |
+# self.cache.SMap.mark(self.liveset, ['d'] | |
+# def children(self): | |
+# return self.cache.SMap.children(self.value, self.liveset) | |
+# | |
+# Note that self.cache.SMap, once created, is never modified; rather, the | |
+# liveset is extended by the calls to self.cache.SMap.mark, and then consulted | |
+# by self.cache.SMap.children. | |
+ | |
+# An AggregateMap instance represents a particular member, member of a member, | |
+# etc. of an aggregate type we wish to pretty-print. We build a tree of these | |
+# with a node for every member, member of a member, etc., that the | |
+# pretty-printer might yield as a child. | |
+class AggregateMap(object): | |
+ | |
+ # Construct an AggregateMap for an aggregate type |t|. | |
+ # | |
+ # All arguments after the first are only for internal use by __init__ | |
+ # itself, to build interior nodes of the member tree. In this case, we | |
+ # construct an AggregateMap representing the member of type |t|, named | |
+ # |name|, contained in |parent|, and which is a base class iff | |
+ # |is_base_class| is true. Use |counter| to assign this AggregateMap a | |
+ # unique index in the overall tree of AggregateMaps. | |
+ def __init__(self, t, parent=None, name=None, is_base_class=False, counter=None): | |
+ t = t.strip_typedefs() | |
+ self.type = t | |
+ self.parent = parent | |
+ self.name = name | |
+ self.is_base_class = is_base_class | |
+ | |
+ # The root node must be an aggregate. We only create a counter at the root, | |
+ # so we know we get distinct indices throughout the tree. | |
+ if not parent: | |
+ assert t.code == gdb.TYPE_CODE_STRUCT or t.code == gdb.TYPE_CODE_UNION | |
+ assert not counter | |
+ assert not name | |
+ counter = AggregateMap.counter() | |
+ | |
+ # Assign ourselves an index distinct within the tree, which can be | |
+ # stored in livesets to indicate which members to traverse. | |
+ self.index = counter.next() | |
+ | |
+ # If we have a name, note that we're visible in our parent. | |
+ if self.name: | |
+ parent.immediately_visible[self.name] = self | |
+ | |
+ # Should we recurse into this member? | |
+ # - Yes, if it's the root of our tree. | |
+ # - Yes, if it's a union. | |
+ # - Yes, if it's a struct without a tag. | |
+ if (not parent or | |
+ t.code == gdb.TYPE_CODE_UNION or | |
+ (t.code == gdb.TYPE_CODE_STRUCT and not t.tag)): | |
+ | |
+ # If we're an anonymous field of our parent, then our members are | |
+ # immediately visible in our parent, so share our parent's | |
+ # immediately_visible map. Otherwise, start our own map. | |
+ if not name and parent: | |
+ self.immediately_visible = parent.immediately_visible | |
+ else: | |
+ self.immediately_visible = {} | |
+ | |
+ # Build a list of AggregateMaps for our members. | |
+ self.members = [] | |
+ add = self.members.append | |
+ for f in t.fields(): | |
+ add(AggregateMap(f.type, self, f.name, f.is_base_class, counter)) | |
+ else: | |
+ self.members = None | |
+ | |
+ | |
+ # Find the member reached by |path| from this member. | |
+ def find(self, path): | |
+ if not path: | |
+ return self | |
+ if not path[0] in self.immediately_visible: | |
+ raise KeyError, "%r not found in AggregateMap %r" % (path[0], self.name) | |
+ return self.immediately_visible[path[0]].find(path[1:]) | |
+ | |
+ # Mark the member reached by |path| from this member as live in |liveset|, | |
+ # along with all union branches containing it. | |
+ def mark_live(self, liveset, path): | |
+ try: | |
+ m = self.find(path) | |
+ except KeyError: | |
+ raise KeyError, "path %r not found in AggregateMap for type '%s'" % (path, self.type) | |
+ # Mark all containing members as live. Note that this is a superset | |
+ # (a super-path?) of the set (path) of members visited by the | |
+ # 'find' method: that only touches the root and named members; we | |
+ # touch anonymous members as well. | |
+ while m: | |
+ liveset.add(m.index) | |
+ m = m.parent | |
+ | |
+ # Given a value |v| of the aggregate this AggregateMap represents, and | |
+ # assuming said member is live, yield its value or those of its live | |
+ # children. | |
+ def children(self, v, liveset, prefix=''): | |
+ if self.type.code == gdb.TYPE_CODE_STRUCT: | |
+ for m in self.members: | |
+ for c in m.visit(v, liveset, prefix): yield c | |
+ | |
+ else: | |
+ assert self.type.code == gdb.TYPE_CODE_UNION | |
+ # Are any members marked as live at all? | |
+ if self.index in liveset: | |
+ for m in self.members: | |
+ if m.index in liveset: | |
+ for c in m.visit(v, liveset, prefix): yield c | |
+ # If none were live, yield them all, as plain GDB would. | |
+ else: | |
+ for m in self.members: | |
+ for c in m.visit(v, liveset, prefix): yield c | |
+ | |
+ | |
+ def visit(self, container, liveset, prefix): | |
+ # If we're an anonymous member or a base class, we have no way to fetch | |
+ # our value. However, in those cases, our members can be referenced as | |
+ # if they were members of our containing value. So just let v be our | |
+ # container, and don't fret that it's the wrong type. | |
+ if self.name and not self.is_base_class: | |
+ v = container[self.name] | |
+ path = prefix + self.name | |
+ prefix = path + '.' | |
+ else: | |
+ v = container | |
+ path = None | |
+ | |
+ if self.members: | |
+ for c in self.children(v, liveset, prefix): yield c | |
+ else: | |
+ if self.name: | |
+ if self.is_base_class: | |
+ # For base classes, use the field name as the child name. | |
+ path = '<%s>' % (self.name,) | |
+ yield path, v | |
+ # Just skip nameless, non-aggregate members. They're probably | |
+ # anonymous bitfields. | |
+ | |
+ @classmethod | |
+ def counter(cls): | |
+ i = 1 | |
+ while True: | |
+ yield i | |
+ i += 1 | |
+ | |
field_enum_value = None | |
# Given |t|, a gdb.Type instance representing an enum type, return the | |
diff --git a/js/src/gdb/tests/test-prettyprinters-AggregateMap.py b/js/src/gdb/tests/test-prettyprinters-AggregateMap.py | |
new file mode 100644 | |
--- /dev/null | |
+++ b/js/src/gdb/tests/test-prettyprinters-AggregateMap.py | |
@@ -0,0 +1,79 @@ | |
+import mozilla.prettyprinters as prettyprinters | |
+ | |
+run_fragment('prettyprinters.AggregateMap') | |
+ | |
+def children(expr, live): | |
+ v = gdb.parse_and_eval(expr) | |
+ m = prettyprinters.AggregateMap(v.type) | |
+ liveset = set() | |
+ for p in live: m.mark_live(liveset, p.split('.')) | |
+ return [name for (name, value) in m.children(v, liveset)] | |
+ | |
+assert_eq(children('simple', []), ['a', 'b']) | |
+ | |
+assert_eq(children('simpleunion', []), ['a', 'b']) | |
+assert_eq(children('simpleunion', ['a']), ['a']) | |
+assert_eq(children('simpleunion', ['b']), ['b']) | |
+ | |
+assert_eq(children('structunion', []), ['a', 'b.c', 'b.d']) | |
+assert_eq(children('structunion', ['b.c']), ['a', 'b.c' ]) | |
+assert_eq(children('structunion', ['b.d']), ['a', 'b.d']) | |
+assert_eq(children('structunion', ['b.c', 'b.d']), ['a', 'b.c', 'b.d']) | |
+ | |
+assert_eq(children('sus', []), ['a', 'e.b', 'e.c', 'e.d']) | |
+assert_eq(children('sus', ['e.b']), ['a', 'e.b', 'e.c' ]) | |
+assert_eq(children('sus', ['e.c']), ['a', 'e.b', 'e.c' ]) | |
+assert_eq(children('sus', ['e.d']), ['a', 'e.d']) | |
+assert_eq(children('sus', ['e.b', 'e.d']), ['a', 'e.b', 'e.c', 'e.d']) | |
+ | |
+assert_eq(children('saus', []), ['a', 'b', 'c', 'd']) | |
+assert_eq(children('saus', ['b']), ['a', 'b', 'c' ]) | |
+assert_eq(children('saus', ['c']), ['a', 'b', 'c' ]) | |
+assert_eq(children('saus', ['d']), ['a', 'd']) | |
+assert_eq(children('saus', ['b', 'd']), ['a', 'b', 'c', 'd']) | |
+ | |
+assert_eq(children('sbu', []), ['<Base>', 'a', 'b']) | |
+assert_eq(children('sbu', ['a']), ['<Base>', 'a', ]) | |
+assert_eq(children('sbu', ['b']), ['<Base>', 'b']) | |
+ | |
+assert_eq(children('suuu', []), ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']) | |
+assert_eq(children('suuu', ['a']), ['a', ]) | |
+assert_eq(children('suuu', ['b']), [ 'b', ]) | |
+assert_eq(children('suuu', ['c']), [ 'c', ]) | |
+assert_eq(children('suuu', ['d']), [ 'd', ]) | |
+assert_eq(children('suuu', ['e']), [ 'e', ]) | |
+assert_eq(children('suuu', ['f']), [ 'f', ]) | |
+assert_eq(children('suuu', ['g']), [ 'g', ]) | |
+assert_eq(children('suuu', ['h']), [ 'h']) | |
+ | |
+assert_eq(children('sass', []), ['a', 'd.c', 'f' ]) | |
+ | |
+assert_eq(children('suss', []), ['a', 'b', 'c', 'd']) | |
+assert_eq(children('suss', ['a']), ['a', 'b', ]) | |
+assert_eq(children('suss', ['c']), [ 'c', 'd']) | |
+assert_eq(children('suss', ['a', 'c']), ['a', 'b', 'c', 'd']) | |
+ | |
+""" | |
+ | |
+def visible(type_name): | |
+ t = gdb.lookup_type(type_name) | |
+ v = mozilla.prettyprinters.immediately_visible_fields(t) | |
+ return list((f.name for f in v)) | |
+ | |
+assert_eq(visible('TA'), ['ba', 'bb', 'tag', 'i', 'p', 'cx', 'r']) | |
+ | |
+ta = gdb.parse_and_eval('ta'); | |
+ | |
+def visited(v, live): | |
+ it = mozilla.prettyprinters.aggregate_live_children(v, set(live)) | |
+ return [name for (name, value) in it] | |
+ | |
+assert_eq(visited(ta, []), ['ba', 'bb.bbx', 'tag', 'i', 'p.w', 'p.x', 'cx', 'r.a', 'r.d.b', 'r.d.c']) | |
+assert_eq(visited(ta, ['ba']), ['ba', 'tag', 'i', 'p.w', 'p.x', 'cx', 'r.a', 'r.d.b', 'r.d.c']) | |
+assert_eq(visited(ta, ['bb']), [ 'bb.bbx', 'tag', 'i', 'p.w', 'p.x', 'cx', 'r.a', 'r.d.b', 'r.d.c']) | |
+assert_eq(visited(ta, ['i']), ['ba', 'bb.bbx', 'tag', 'i']) | |
+assert_eq(visited(ta, ['ba', 'i']), ['ba', 'tag', 'i']) | |
+assert_eq(visited(ta, ['i', 'ba']), ['ba', 'tag', 'i']) | |
+ | |
+# assert_eq(visited(tb, | |
+""" | |
diff --git a/js/src/gdb/tests/test-prettyprinters.cpp b/js/src/gdb/tests/test-prettyprinters.cpp | |
--- a/js/src/gdb/tests/test-prettyprinters.cpp | |
+++ b/js/src/gdb/tests/test-prettyprinters.cpp | |
@@ -36,3 +36,113 @@ FRAGMENT(prettyprinters, implemented_typ | |
(void) f; | |
(void) h; | |
} | |
+ | |
+struct Simple { | |
+ int a, b; | |
+}; | |
+ | |
+union SimpleUnion { | |
+ int a, b; | |
+}; | |
+ | |
+struct StructUnion { | |
+ int a; | |
+ union { | |
+ int c, d; | |
+ } b; | |
+}; | |
+ | |
+struct SUS { | |
+ int a; | |
+ union { | |
+ struct { | |
+ int b, c; | |
+ }; | |
+ int d; | |
+ } e; | |
+}; | |
+ | |
+struct SaUS { | |
+ int a; | |
+ union { | |
+ struct { | |
+ int b, c; | |
+ }; | |
+ int d; | |
+ }; | |
+}; | |
+ | |
+struct Base { | |
+ int x; | |
+}; | |
+ | |
+struct SBU : Base { | |
+ union { | |
+ int a, b; | |
+ }; | |
+}; | |
+ | |
+struct SUUU { | |
+ union { | |
+ union { | |
+ union { | |
+ int a; | |
+ int b; | |
+ }; | |
+ union { | |
+ int c; | |
+ int d; | |
+ }; | |
+ }; | |
+ union { | |
+ union { | |
+ int e; | |
+ int f; | |
+ }; | |
+ union { | |
+ int g; | |
+ int h; | |
+ }; | |
+ }; | |
+ }; | |
+}; | |
+ | |
+union SaSS { | |
+ struct { int a; }; // Anonymous; we should recurse into it. | |
+ struct S { int b; }; // Declares no data members of SaSS, only a type. | |
+ struct { int c; } d; // Tagless; we should recurse into it. | |
+ struct Sn { int e; } f; // Has tag; We shouldn't recurse into this one. | |
+}; | |
+ | |
+struct SUSS { | |
+ union { | |
+ struct { | |
+ int a, b; | |
+ }; | |
+ struct { | |
+ int c, d; | |
+ }; | |
+ }; | |
+}; | |
+ | |
+FRAGMENT(prettyprinters, AggregateMap) { | |
+ Simple simple; | |
+ SimpleUnion simpleunion; | |
+ StructUnion structunion; | |
+ SUS sus; | |
+ SaUS saus; | |
+ SBU sbu; | |
+ SUUU suuu; | |
+ SaSS sass; | |
+ SUSS suss; | |
+ breakpoint(); | |
+ (void) simple; | |
+ (void) simpleunion; | |
+ (void) structunion; | |
+ (void) sus; | |
+ (void) saus; | |
+ (void) sbu; | |
+ (void) suuu; | |
+ (void) sass; | |
+ (void) suss; | |
+} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment