Skip to content

Instantly share code, notes, and snippets.

@jimblandy
Last active December 11, 2015 07:39
Show Gist options
  • Save jimblandy/4567915 to your computer and use it in GitHub Desktop.
Save jimblandy/4567915 to your computer and use it in GitHub Desktop.
Patch to SpiderMonkey GDB support: define AggregateMap utility class.
# 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