Bug 1246804 - Switch to using in-source annotations. Use C++ inheritance information when describing GC types. Add a test suite., r=terrence
☠☠ backed out by 45a405814365 ☠ ☠
authorSteve Fink <sfink@mozilla.com>
Tue, 29 Sep 2015 13:39:33 -0700
changeset 288750 901b1c651c982ccbf42604231c723d168e1cde69
parent 288749 f5bd3c829834e59e3c87e89992cc6dd64318a842
child 288751 c73617bdfdfa52f98c33b46586eec0ad2e695193
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs1246804
milestone48.0a1
Bug 1246804 - Switch to using in-source annotations. Use C++ inheritance information when describing GC types. Add a test suite., r=terrence MozReview-Commit-ID: HCcG2k8Wyb9
dom/bindings/ErrorResult.h
js/public/GCAPI.h
js/public/GCAnnotations.h
js/public/Id.h
js/public/RootingAPI.h
js/public/Value.h
js/src/devtools/rootAnalysis/CFG.js
js/src/devtools/rootAnalysis/analyze.py
js/src/devtools/rootAnalysis/analyzeRoots.js
js/src/devtools/rootAnalysis/annotations.js
js/src/devtools/rootAnalysis/computeCallgraph.js
js/src/devtools/rootAnalysis/computeGCTypes.js
js/src/devtools/rootAnalysis/loadCallgraph.js
js/src/devtools/rootAnalysis/run-test.py
js/src/devtools/rootAnalysis/test/source.cpp
js/src/devtools/rootAnalysis/test/test.py
js/src/gc/GCRuntime.h
js/src/gc/Heap.h
js/src/moz.build
js/src/vm/TypeInference.h
js/xpconnect/src/xpcprivate.h
testing/mozharness/scripts/spidermonkey/build.shell
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -19,16 +19,17 @@
  *    with) via dom::ToJSValue.
  */
 
 #ifndef mozilla_ErrorResult_h
 #define mozilla_ErrorResult_h
 
 #include <stdarg.h>
 
+#include "js/GCAnnotations.h"
 #include "js/Value.h"
 #include "nscore.h"
 #include "nsStringGlue.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Move.h"
 #include "nsTArray.h"
 
 namespace IPC {
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef js_GCAPI_h
 #define js_GCAPI_h
 
 #include "mozilla/Vector.h"
 
+#include "js/GCAnnotations.h"
 #include "js/HeapAPI.h"
 #include "js/UniquePtr.h"
 
 namespace js {
 namespace gc {
 class GCRuntime;
 } // namespace gc
 namespace gcstats {
@@ -576,17 +577,17 @@ class JS_PUBLIC_API(AutoAssertGCCallback
  * for the static analysis: e.g. acquiring inline chars from a JSString* on the
  * heap.
  */
 class JS_PUBLIC_API(AutoCheckCannotGC) : public AutoAssertOnGC
 {
   public:
     AutoCheckCannotGC() : AutoAssertOnGC() {}
     explicit AutoCheckCannotGC(JSRuntime* rt) : AutoAssertOnGC(rt) {}
-};
+} JS_HAZ_GC_INVALIDATED;
 
 /**
  * Unsets the gray bit for anything reachable from |thing|. |kind| should not be
  * JS::TraceKind::Shape. |thing| should be non-null.
  */
 extern JS_FRIEND_API(bool)
 UnmarkGrayGCThingRecursively(GCCellPtr thing);
 
new file mode 100644
--- /dev/null
+++ b/js/public/GCAnnotations.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef js_GCAnnotations_h
+#define js_GCAnnotations_h
+
+// Set of annotations for the rooting hazard analysis, used to categorize types
+// and functions.
+#ifdef XGILL_PLUGIN
+
+// Mark a type as being a GC thing (eg js::gc::Cell has this annotation).
+# define JS_HAZ_GC_THING __attribute__((tag("GC Thing")))
+
+// Mark a type as holding a pointer to a GC thing (eg JS::Value has this
+// annotation.)
+# define JS_HAZ_GC_POINTER __attribute__((tag("GC Pointer")))
+
+// Mark a type as a rooted pointer, suitable for use on the stack (eg all
+// Rooted<T> instantiations should have this.)
+# define JS_HAZ_ROOTED __attribute__((tag("Rooted Pointer")))
+
+// Mark a type as something that should not be held live across a GC, but which
+// is itself not a GC pointer.
+# define JS_HAZ_GC_INVALIDATED __attribute__((tag("Invalidated by GC")))
+
+// Mark a type that would otherwise be considered a GC Pointer (eg because it
+// contains a JS::Value field) as a non-GC pointer. It is handled almost the
+// same in the analysis as a rooted pointer, except it will not be reported as
+// an unnecessary root if used across a GC call. This should rarely be used,
+// but makes sense for something like ErrorResult, which only contains a GC
+// pointer when it holds an exception (and it does its own rooting,
+// conditionally.)
+# define JS_HAZ_NON_GC_POINTER __attribute__((tag("Suppressed GC Pointer")))
+
+// Mark a function as something that runs a garbage collection, potentially
+// invalidating GC pointers.
+# define JS_HAZ_GC_CALL __attribute__((tag("GC Call")))
+
+#else
+
+# define JS_HAZ_GC_THING
+# define JS_HAZ_GC_POINTER
+# define JS_HAZ_ROOTED
+# define JS_HAZ_GC_INVALIDATED
+# define JS_HAZ_NON_GC_POINTER
+# define JS_HAZ_GC_CALL
+
+#endif
+
+#endif /* js_GCAnnotations_h */
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -27,17 +27,17 @@
 #include "js/TypeDecls.h"
 #include "js/Utility.h"
 
 struct jsid
 {
     size_t asBits;
     bool operator==(jsid rhs) const { return asBits == rhs.asBits; }
     bool operator!=(jsid rhs) const { return asBits != rhs.asBits; }
-};
+} JS_HAZ_GC_POINTER;
 #define JSID_BITS(id) (id.asBits)
 
 #define JSID_TYPE_STRING                 0x0
 #define JSID_TYPE_INT                    0x1
 #define JSID_TYPE_VOID                   0x2
 #define JSID_TYPE_SYMBOL                 0x4
 #define JSID_TYPE_MASK                   0x7
 
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -11,16 +11,17 @@
 #include "mozilla/DebugOnly.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Move.h"
 #include "mozilla/TypeTraits.h"
 
 #include "jspubtd.h"
 
+#include "js/GCAnnotations.h"
 #include "js/GCAPI.h"
 #include "js/GCPolicyAPI.h"
 #include "js/HeapAPI.h"
 #include "js/TypeDecls.h"
 #include "js/Utility.h"
 
 /*
  * Moving GC Stack Rooting
@@ -702,17 +703,17 @@ class MOZ_RAII Rooted : public js::Roote
      */
     using MaybeWrapped = typename mozilla::Conditional<
         MapTypeToRootKind<T>::kind == JS::RootKind::Traceable,
         js::DispatchWrapper<T>,
         T>::Type;
     MaybeWrapped ptr;
 
     Rooted(const Rooted&) = delete;
-};
+} JS_HAZ_ROOTED;
 
 } /* namespace JS */
 
 namespace js {
 
 /**
  * Augment the generic Rooted<T> interface when T = JSObject* with
  * class-querying and downcasting operations.
@@ -1048,17 +1049,17 @@ class PersistentRooted : public js::Pers
 
     // See the comment above Rooted::ptr.
     using MaybeWrapped = typename mozilla::Conditional<
         MapTypeToRootKind<T>::kind == JS::RootKind::Traceable,
         js::DispatchWrapper<T>,
         T>::Type;
 
     MaybeWrapped ptr;
-};
+} JS_HAZ_ROOTED;
 
 class JS_PUBLIC_API(ObjectPtr)
 {
     Heap<JSObject*> value;
 
   public:
     ObjectPtr() : value(nullptr) {}
 
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1390,17 +1390,17 @@ class Value
         JS_STATIC_ASSERT(sizeof(JSValueTag) == 4);
         JS_STATIC_ASSERT(sizeof(JSWhyMagic) <= 4);
         JS_STATIC_ASSERT(sizeof(Value) == 8);
     }
 
     friend jsval_layout (::JSVAL_TO_IMPL)(Value);
     friend Value JS_VALUE_CONSTEXPR (::IMPL_TO_JSVAL)(jsval_layout l);
     friend Value JS_VALUE_CONSTEXPR (JS::UndefinedValue)();
-};
+} JS_HAZ_GC_POINTER;
 
 inline bool
 IsOptimizedPlaceholderMagicValue(const Value& v)
 {
     if (v.isMagic()) {
         MOZ_ASSERT(v.whyMagic() == JS_OPTIMIZED_ARGUMENTS || v.whyMagic() == JS_OPTIMIZED_OUT);
         return true;
     }
--- a/js/src/devtools/rootAnalysis/CFG.js
+++ b/js/src/devtools/rootAnalysis/CFG.js
@@ -1,33 +1,33 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 
 "use strict";
 
 var functionBodies;
 
-function findAllPoints(blockId)
+function findAllPoints(bodies, blockId)
 {
     var points = [];
     var body;
 
-    for (var xbody of functionBodies) {
+    for (var xbody of bodies) {
         if (sameBlockId(xbody.BlockId, blockId)) {
             assert(!body);
             body = xbody;
         }
     }
     assert(body);
 
     if (!("PEdge" in body))
         return;
     for (var edge of body.PEdge) {
         points.push([body, edge.Index[0]]);
         if (edge.Kind == "Loop")
-            Array.prototype.push.apply(points, findAllPoints(edge.BlockId));
+            Array.prototype.push.apply(points, findAllPoints(bodies, edge.BlockId));
     }
 
     return points;
 }
 
 function isMatchingDestructor(constructor, edge)
 {
     if (edge.Kind != "Call")
@@ -50,17 +50,17 @@ function isMatchingDestructor(constructo
     return sameVariable(constructExp.Variable, destructExp.Variable);
 }
 
 // Return all calls within the RAII scope of any constructor matched by
 // isConstructor(). (Note that this would be insufficient if you needed to
 // treat each instance separately, such as when different regions of a function
 // body were guarded by these constructors and you needed to do something
 // different with each.)
-function allRAIIGuardedCallPoints(body, isConstructor)
+function allRAIIGuardedCallPoints(bodies, body, isConstructor)
 {
     if (!("PEdge" in body))
         return [];
 
     var points = [];
 
     for (var edge of body.PEdge) {
         if (edge.Kind != "Call")
@@ -72,17 +72,17 @@ function allRAIIGuardedCallPoints(body, 
         assert(variable.Kind == "Func");
         if (!isConstructor(variable.Name))
             continue;
         if (!("PEdgeCallInstance" in edge))
             continue;
         if (edge.PEdgeCallInstance.Exp.Kind != "Var")
             continue;
 
-        Array.prototype.push.apply(points, pointsInRAIIScope(body, edge));
+        Array.prototype.push.apply(points, pointsInRAIIScope(bodies, body, edge));
     }
 
     return points;
 }
 
 // Test whether the given edge is the constructor corresponding to the given
 // destructor edge
 function isMatchingConstructor(destructor, edge)
@@ -128,32 +128,32 @@ function findMatchingConstructor(destruc
             for (var e of predecessors[edge.Index[0]])
                 worklist.push(e);
         }
     }
     printErr("Could not find matching constructor!");
     debugger;
 }
 
-function pointsInRAIIScope(body, constructorEdge) {
+function pointsInRAIIScope(bodies, body, constructorEdge) {
     var seen = {};
     var worklist = [constructorEdge.Index[1]];
     var points = [];
     while (worklist.length) {
         var point = worklist.pop();
         if (point in seen)
             continue;
         seen[point] = true;
         points.push([body, point]);
         var successors = getSuccessors(body);
         if (!(point in successors))
             continue;
         for (var nedge of successors[point]) {
             if (isMatchingDestructor(constructorEdge, nedge))
                 continue;
             if (nedge.Kind == "Loop")
-                Array.prototype.push.apply(points, findAllPoints(nedge.BlockId));
+                Array.prototype.push.apply(points, findAllPoints(bodies, nedge.BlockId));
             worklist.push(nedge.Index[1]);
         }
     }
 
     return points;
 }
--- a/js/src/devtools/rootAnalysis/analyze.py
+++ b/js/src/devtools/rootAnalysis/analyze.py
@@ -195,16 +195,20 @@ for default in defaults:
 
 data = config.copy()
 
 parser = argparse.ArgumentParser(description='Statically analyze build tree for rooting hazards.')
 parser.add_argument('step', metavar='STEP', type=str, nargs='?',
                     help='run starting from this step')
 parser.add_argument('--source', metavar='SOURCE', type=str, nargs='?',
                     help='source code to analyze')
+parser.add_argument('--objdir', metavar='DIR', type=str, nargs='?',
+                    help='object directory of compiled files')
+parser.add_argument('--js', metavar='JSSHELL', type=str, nargs='?',
+                    help='full path to ctypes-capable JS shell')
 parser.add_argument('--upto', metavar='UPTO', type=str, nargs='?',
                     help='last step to execute')
 parser.add_argument('--jobs', '-j', default=None, metavar='JOBS', type=int,
                     help='number of simultaneous analyzeRoots.js jobs')
 parser.add_argument('--list', const=True, nargs='?', type=bool,
                     help='display available steps')
 parser.add_argument('--buildcommand', '--build', '-b', type=str, nargs='?',
                     help='command to build the tree being analyzed')
--- a/js/src/devtools/rootAnalysis/analyzeRoots.js
+++ b/js/src/devtools/rootAnalysis/analyzeRoots.js
@@ -689,17 +689,17 @@ var end = Math.min(minStream + each * ba
 
 function process(name, json) {
     functionName = name;
     functionBodies = JSON.parse(json);
 
     for (var body of functionBodies)
         body.suppressed = [];
     for (var body of functionBodies) {
-        for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
+        for (var [pbody, id] of allRAIIGuardedCallPoints(functionBodies, body, isSuppressConstructor))
             pbody.suppressed[id] = true;
     }
     processBodies(functionName);
 }
 
 if (theFunctionNameToFind) {
     var data = xdb.read_entry(theFunctionNameToFind);
     var json = data.readString();
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -346,62 +346,14 @@ function isOverridableField(initialCSU, 
         return false;
     if (initialCSU == 'nsIScriptContext') {
         if (field == 'GetWindowProxy' || field == 'GetWindowProxyPreserveColor')
             return false;
     }
     return true;
 }
 
-function listGCTypes() {
+function listNonGCPointers() {
     return [
-        'js::gc::Cell',
-        'JSObject',
-        'JSString',
-        'JSFatInlineString',
-        'JSExternalString',
-        'js::Shape',
-        'js::AccessorShape',
-        'js::BaseShape',
-        'JSScript',
-        'js::ObjectGroup',
-        'js::LazyScript',
-        'js::jit::JitCode',
-        'JS::Symbol',
+        // Safe only because jsids are currently only made from pinned strings.
+        'NPIdentifier',
     ];
 }
-
-function listGCPointers() {
-    return [
-        'JS::Value',
-        'jsid',
-
-        'js::TypeSet',
-        'js::TypeSet::ObjectKey',
-        'js::TypeSet::Type',
-
-        // AutoCheckCannotGC should also not be held live across a GC function.
-        'JS::AutoCheckCannotGC',
-    ];
-}
-
-function listNonGCTypes() {
-    return [
-    ];
-}
-
-function listNonGCPointers() {
-    return [
-        // Both of these are safe only because jsids are currently only made
-        // from "interned" (pinned) strings. Once that changes, both should be
-        // removed from the list.
-        'NPIdentifier',
-        'XPCNativeMember',
-    ];
-}
-
-// Flexible mechanism for deciding an arbitrary type is a GCPointer. Its one
-// use turned out to be unnecessary due to another change, but the mechanism
-// seems useful for something like /Vector.*Something/.
-function isGCPointer(typeName)
-{
-    return false;
-}
--- a/js/src/devtools/rootAnalysis/computeCallgraph.js
+++ b/js/src/devtools/rootAnalysis/computeCallgraph.js
@@ -191,34 +191,72 @@ var lastline;
 function printOnce(line)
 {
     if (line != lastline) {
         print(line);
         lastline = line;
     }
 }
 
-function processBody(caller, body)
+// Returns a table mapping function name to lists of [annotation-name,
+// annotation-value] pairs: { function-name => [ [annotation-name, annotation-value] ] }
+function getAnnotations(body)
+{
+    var all_annotations = {};
+    for (var v of (body.DefineVariable || [])) {
+        if (v.Variable.Kind != 'Func')
+            continue;
+        var name = v.Variable.Name[0];
+        var annotations = all_annotations[name] = [];
+
+        for (var ann of (v.Type.Annotation || [])) {
+            annotations.push(ann.Name);
+        }
+    }
+
+    return all_annotations;
+}
+
+function getTags(functionName, body) {
+    var tags = new Set();
+    var annotations = getAnnotations(body);
+    print(functionName);
+    print(JSON.stringify(annotations));
+    if (functionName in annotations) {
+        print("crawling through");
+        for (var [ annName, annValue ] of annotations[functionName]) {
+            print(`  got ${annName}: ${annValue}`);
+            if (annName == 'Tag')
+                tags.add(annValue);
+        }
+    }
+    return tags;
+}
+
+function processBody(functionName, body)
 {
     if (!('PEdge' in body))
         return;
 
+    for (var tag of getTags(functionName, body).values())
+        print("T " + memo(functionName) + " " + tag);
+
     lastline = null;
     for (var edge of body.PEdge) {
         if (edge.Kind != "Call")
             continue;
         var edgeSuppressed = false;
         var seen = seenCallees;
         if (edge.Index[0] in body.suppressed) {
             edgeSuppressed = true;
             seen = seenSuppressedCallees;
         }
         for (var callee of getCallees(edge)) {
             var prologue = (edgeSuppressed || callee.suppressed) ? "SUPPRESS_GC " : "";
-            prologue += memo(caller) + " ";
+            prologue += memo(functionName) + " ";
             if (callee.kind == 'direct') {
                 if (!(callee.name in seen)) {
                     seen[callee.name] = true;
                     printOnce("D " + prologue + memo(callee.name));
                 }
             } else if (callee.kind == 'field') {
                 var { csu, field } = callee;
                 printOnce("F " + prologue + "CLASS " + csu + " FIELD " + field);
@@ -278,31 +316,28 @@ if (theFunctionNameToFind) {
     var index = xdb.lookup_key(theFunctionNameToFind);
     if (!index) {
         printErr("Function not found");
         quit(1);
     }
     minStream = maxStream = index;
 }
 
-for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
-    var name = xdb.read_key(nameIndex);
-    var data = xdb.read_entry(name);
-    functionBodies = JSON.parse(data.readString());
+function process(functionName, functionBodies)
+{
     for (var body of functionBodies)
         body.suppressed = [];
     for (var body of functionBodies) {
-        for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
+        for (var [pbody, id] of allRAIIGuardedCallPoints(functionBodies, body, isSuppressConstructor))
             pbody.suppressed[id] = true;
     }
 
     seenCallees = {};
     seenSuppressedCallees = {};
 
-    var functionName = name.readString();
     for (var body of functionBodies)
         processBody(functionName, body);
 
     // GCC generates multiple constructors and destructors ("in-charge" and
     // "not-in-charge") to handle virtual base classes. They are normally
     // identical, and it appears that GCC does some magic to alias them to the
     // same thing. But this aliasing is not visible to the analysis. So we'll
     // add a dummy call edge from "foo" -> "foo *INTERNAL* ", since only "foo"
@@ -359,12 +394,17 @@ for (var nameIndex = minStream; nameInde
             var C1 = mangled.replace("C4E", "C1E");
             var C2 = mangled.replace("C4E", "C2E");
             var C3 = mangled.replace("C4E", "C3E");
             print("D " + memo(C1) + " " + memo(mangled));
             print("D " + memo(C2) + " " + memo(mangled));
             print("D " + memo(C3) + " " + memo(mangled));
         }
     }
+}
 
+for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
+    var name = xdb.read_key(nameIndex);
+    var data = xdb.read_entry(name);
+    process(name.readString(), JSON.parse(data.readString()));
     xdb.free_string(name);
     xdb.free_string(data);
 }
--- a/js/src/devtools/rootAnalysis/computeGCTypes.js
+++ b/js/src/devtools/rootAnalysis/computeGCTypes.js
@@ -1,22 +1,39 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 
 "use strict";
 
 loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
 
-var annotatedGCPointers = [];
+var annotations = {
+    'GCPointers': [],
+    'GCThings': [],
+    'NonGCTypes': {}, // unused
+    'NonGCPointers': {},
+    'RootedPointers': {},
+};
+
+var structureParents = {}; // Map from field => list of <parent, fieldName>
+var pointerParents = {}; // Map from field => list of <parent, fieldName>
+var baseClasses = {}; // Map from struct name => list of base class name strings
+
+var gcTypes = {}; // map from parent struct => Set of GC typed children
+var gcPointers = {}; // map from parent struct => Set of GC typed children
+var gcFields = new Map;
+
+var rootedPointers = {};
 
 function processCSU(csu, body)
 {
-    if (!("DataField" in body))
-        return;
-    for (var field of body.DataField) {
+    for (let { 'Base': base } of (body.CSUBaseClass || []))
+        addBaseClass(csu, base);
+
+    for (let field of (body.DataField || [])) {
         var type = field.Field.Type;
         var fieldName = field.Field.Name[0];
         if (type.Kind == "Pointer") {
             var target = type.Type;
             if (target.Kind == "CSU")
                 addNestedPointer(csu, target.Name, fieldName);
         }
         if (type.Kind == "Array") {
@@ -27,30 +44,54 @@ function processCSU(csu, body)
         if (type.Kind == "CSU") {
             // Ignore nesting in classes which are AutoGCRooters. We only consider
             // types with fields that may not be properly rooted.
             if (type.Name == "JS::AutoGCRooter" || type.Name == "JS::CustomAutoRooter")
                 return;
             addNestedStructure(csu, type.Name, fieldName);
         }
     }
-    if (isGCPointer(csu))
-        annotatedGCPointers.push(csu);
+
+    for (let { 'Name': [ annType, tag ] } of (body.Annotation || [])) {
+        if (annType != 'Tag')
+            continue;
+
+        if (tag == 'GC Pointer')
+            annotations.GCPointers.push(csu);
+        else if (tag == 'Invalidated by GC')
+            annotations.GCPointers.push(csu);
+        else if (tag == 'GC Thing')
+            annotations.GCThings.push(csu);
+        else if (tag == 'Suppressed GC Pointer')
+            annotations.NonGCPointers[csu] = true;
+        else if (tag == 'Rooted Pointer')
+            annotations.RootedPointers[csu] = true;
+    }
 }
 
-var structureParents = {}; // Map from field => list of <parent, fieldName>
-var pointerParents = {}; // Map from field => list of <parent, fieldName>
-
+// csu.field is of type inner
 function addNestedStructure(csu, inner, field)
 {
     if (!(inner in structureParents))
         structureParents[inner] = [];
+
+    if (field.match(/^field:\d+$/) && (csu in baseClasses) && (baseClasses[csu].indexOf(inner) != -1))
+        return;
+
     structureParents[inner].push([ csu, field ]);
 }
 
+function addBaseClass(csu, base) {
+    if (!(csu in baseClasses))
+        baseClasses[csu] = [];
+    baseClasses[csu].push(base);
+    var k = baseClasses[csu].length;
+    addNestedStructure(csu, base, `<base-${k}>`);
+}
+
 function addNestedPointer(csu, inner, field)
 {
     if (!(inner in pointerParents))
         pointerParents[inner] = [];
     pointerParents[inner].push([ csu, field ]);
 }
 
 var xdb = xdbLibrary();
@@ -65,21 +106,22 @@ for (var csuIndex = minStream; csuIndex 
     var json = JSON.parse(data.readString());
     assert(json.length == 1);
     processCSU(csu.readString(), json[0]);
 
     xdb.free_string(csu);
     xdb.free_string(data);
 }
 
-var gcTypes = {}; // map from parent struct => Set of GC typed children
-var gcPointers = {}; // map from parent struct => Set of GC typed children
-var nonGCTypes = {}; // set of types that would ordinarily be GC types but we are suppressing
-var nonGCPointers = {}; // set of types that would ordinarily be GC pointers but we are suppressing
-var gcFields = new Map;
+// Now that we have the whole hierarchy set up, add all the types and propagate
+// info.
+for (let csu of annotations.GCThings)
+    addGCType(csu);
+for (let csu of annotations.GCPointers)
+    addGCPointer(csu);
 
 function stars(n) { return n ? '*' + stars(n-1) : '' };
 
 // "typeName is a (pointer to a)^'typePtrLevel' GC type because it contains a field
 // named 'child' of type 'why' (or pointer to 'why' if fieldPtrLevel == 1), which is
 // itself a GCThing or GCPointer."
 function markGCType(typeName, child, why, typePtrLevel, fieldPtrLevel, indent)
 {
@@ -117,23 +159,23 @@ function markGCType(typeName, child, why
         return;
 
     if (ptrLevel == 0 && isRootedGCTypeName(typeName))
         return;
     if (ptrLevel == 1 && isRootedGCPointerTypeName(typeName))
         return;
 
     if (ptrLevel == 0) {
-        if (typeName in nonGCTypes)
+        if (typeName in annotations.NonGCTypes)
             return;
         if (!(typeName in gcTypes))
             gcTypes[typeName] = new Set();
         gcTypes[typeName].add(why);
     } else if (ptrLevel == 1) {
-        if (typeName in nonGCPointers)
+        if (typeName in annotations.NonGCPointers)
             return;
         if (!(typeName in gcPointers))
             gcPointers[typeName] = new Set();
         gcPointers[typeName].add(why);
     }
 
     if (ptrLevel < 2) {
         if (!gcFields.has(typeName))
@@ -160,56 +202,49 @@ function addGCType(typeName, child, why,
     markGCType(typeName, '<annotation>', '(annotation)', 0, 0, "");
 }
 
 function addGCPointer(typeName)
 {
     markGCType(typeName, '<pointer-annotation>', '(annotation)', 1, 0, "");
 }
 
-for (var type of listNonGCTypes())
-    nonGCTypes[type] = true;
-for (var type of listNonGCPointers())
-    nonGCPointers[type] = true;
-for (var type of listGCTypes())
-    addGCType(type);
-for (var type of listGCPointers())
-    addGCPointer(type);
-
-for (var typeName of annotatedGCPointers)
-    addGCPointer(typeName);
+//for (var type of listNonGCPointers())
+//    annotations.NonGCPointers[type] = true;
 
 function explain(csu, indent, seen) {
     if (!seen)
         seen = new Set();
     seen.add(csu);
     if (!gcFields.has(csu))
         return;
     var fields = gcFields.get(csu);
 
     if (fields.has('<annotation>')) {
-        print(indent + "which is a GCThing because I said so");
+        print(indent + "which is annotated as a GCThing");
         return;
     }
     if (fields.has('<pointer-annotation>')) {
-        print(indent + "which is a GCPointer because I said so");
+        print(indent + "which is annotated as a GCPointer");
         return;
     }
     for (var [ field, [ child, ptrdness ] ] of fields) {
-        var inherit = "";
-        if (field == "field:0")
-            inherit = " (probably via inheritance)";
-        var msg = indent + "contains field '" + field + "' ";
-        if (ptrdness == -1)
-            msg += "(with a pointer to unsafe storage) holding a ";
-        else if (ptrdness == 0)
-            msg += "of type ";
-        else
-            msg += "pointing to type ";
-        msg += child + inherit;
+        var msg = indent;
+        if (field[0] == '<')
+            msg += "inherits from ";
+        else {
+            msg += "contains field '" + field + "' ";
+            if (ptrdness == -1)
+                msg += "(with a pointer to unsafe storage) holding a ";
+            else if (ptrdness == 0)
+                msg += "of type ";
+            else
+                msg += "pointing to type ";
+        }
+        msg += child;
         print(msg);
         if (!seen.has(child))
             explain(child, indent + "  ", seen);
     }
 }
 
 for (var csu in gcTypes) {
     print("GCThing: " + csu);
--- a/js/src/devtools/rootAnalysis/loadCallgraph.js
+++ b/js/src/devtools/rootAnalysis/loadCallgraph.js
@@ -69,16 +69,18 @@ var functionNames = [""];
 // Map from identifier to mangled name (or to a Class.Field)
 var idToMangled = [""];
 
 function loadCallgraph(file)
 {
     var suppressedFieldCalls = {};
     var resolvedFunctions = {};
 
+    var numGCCalls = 0;
+
     for (var line of readFileLines_gen(file)) {
         line = line.replace(/\n/, "");
 
         var match;
         if (match = line.charAt(0) == "#" && /^\#(\d+) (.*)/.exec(line)) {
             assert(functionNames.length == match[1]);
             functionNames.push(match[2]);
             var [ mangled, readable ] = splitFunction(match[2]);
@@ -114,16 +116,23 @@ function loadCallgraph(file)
             var caller = idToMangled[match[1]];
             var callee = idToMangled[match[2]];
             addCallEdge(caller, callee, suppressed);
         } else if (match = tag == 'R' && /^R (\d+) (\d+)/.exec(line)) {
             var callerField = idToMangled[match[1]];
             var callee = idToMangled[match[2]];
             addCallEdge(callerField, callee, false);
             resolvedFunctions[callerField] = true;
+        } else if (match = tag == 'T' && /^T (\d+) (.*)/.exec(line)) {
+            var mangled = idToMangled[match[1]];
+            var tag = match[2];
+            if (tag == 'GC Call') {
+                addGCFunction(mangled, "GC");
+                numGCCalls++;
+            }
         }
     }
 
     // Initialize suppressedFunctions to the set of all functions, and the
     // worklist to all toplevel callers.
     var worklist = [];
     for (var callee in callerGraph)
         suppressedFunctions[callee] = true;
@@ -156,23 +165,21 @@ function loadCallgraph(file)
         if (name in suppressedFunctions)
             delete gcFunctions[name];
     }
 
     for (var name in suppressedFieldCalls) {
         suppressedFunctions[name] = true;
     }
 
-    for (var gcName of [ 'void js::gc::GCRuntime::collect(uint8, js::SliceBudget, uint32)',
-                         'void js::gc::GCRuntime::minorGC(uint32)',
-                         'void js::gc::GCRuntime::minorGC(uint32)' ])
-    {
-        assert(gcName in mangledName, "GC function not found: " + gcName);
-        addGCFunction(mangledName[gcName], "GC");
-    }
+    // Sanity check to make sure the callgraph has some functions annotated as
+    // GC Calls. This is mostly a check to be sure the earlier processing
+    // succeeded (as opposed to, say, running on empty xdb files because you
+    // didn't actually compile anything interesting.)
+    assert(numGCCalls > 0, "No GC functions found!");
 
     // Initialize the worklist to all known gcFunctions.
     var worklist = [];
     for (var name in gcFunctions)
         worklist.push(name);
 
     // Recursively find all callers and add them to the set of gcFunctions.
     while (worklist.length) {
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/run-test.py
@@ -0,0 +1,147 @@
+import sys
+import os
+import re
+import json
+import subprocess
+
+testdir = os.path.abspath(os.path.dirname(__file__))
+
+cfg = {}
+cfg['SIXGILL_ROOT']   = os.environ.get('SIXGILL',
+                                       os.path.join(testdir, "sixgill"))
+cfg['SIXGILL_BIN']    = os.environ.get('SIXGILL_BIN',
+                                       os.path.join(cfg['SIXGILL_ROOT'], "usr", "bin"))
+cfg['SIXGILL_PLUGIN'] = os.environ.get('SIXGILL_PLUGIN',
+                                       os.path.join(cfg['SIXGILL_ROOT'], "usr", "libexec", "sixgill", "gcc", "xgill.so"))
+cfg['CC']             = os.environ.get("CC",
+                                       "gcc")
+cfg['CXX']            = os.environ.get("CXX",
+                                       cfg.get('CC', 'g++'))
+cfg['JS_BIN']         = os.environ["JS"]
+
+def binpath(prog):
+    return os.path.join(cfg['SIXGILL_BIN'], prog)
+
+if not os.path.exists("test-output"):
+    os.mkdir("test-output")
+
+# Simplified version of the body info.
+class Body(dict):
+    def __init__(self, body):
+        self['BlockIdKind'] = body['BlockId']['Kind']
+        if 'Variable' in body['BlockId']:
+            self['BlockName'] = body['BlockId']['Variable']['Name'][0]
+        self['LineRange'] = [ body['Location'][0]['Line'], body['Location'][1]['Line'] ]
+        self['Filename'] = body['Location'][0]['CacheString']
+        self['Edges'] = body.get('PEdge', [])
+        self['Points'] = { i+1: body['PPoint'][i]['Location']['Line'] for i in range(len(body['PPoint'])) }
+        self['Index'] = body['Index']
+        self['Variables'] = { x['Variable']['Name'][0]: x['Type'] for x in body['DefineVariable'] }
+
+        # Indexes
+        self['Line2Points'] = {}
+        for point, line in self['Points'].items():
+            self['Line2Points'].setdefault(line, []).append(point)
+        self['SrcPoint2Edges'] = {}
+        for edge in self['Edges']:
+            (src, dst) = edge['Index']
+            self['SrcPoint2Edges'].setdefault(src, []).append(edge)
+        self['Line2Edges'] = {}
+        for (src, edges) in self['SrcPoint2Edges'].items():
+            line = self['Points'][src]
+            self['Line2Edges'].setdefault(line, []).extend(edges)
+
+    def edges_from_line(self, line):
+        return self['Line2Edges'][line]
+
+    def edge_from_line(self, line):
+        edges = self.edges_from_line(line)
+        assert(len(edges) == 1)
+        return edges[0]
+
+    def edges_from_point(self, point):
+        return self['SrcPoint2Edges'][point]
+
+    def edge_from_point(self, point):
+        edges = self.edges_from_point(point)
+        assert(len(edges) == 1)
+        return edges[0]
+
+    def assignment_point(self, varname):
+        for edge in self['Edges']:
+            if edge['Kind'] != 'Assign':
+                continue
+            dst = edge['Exp'][0]
+            if dst['Kind'] != 'Var':
+                continue
+            if dst['Variable']['Name'][0] == varname:
+                return edge['Index'][0]
+        raise Exception("assignment to variable %s not found" % varname)
+
+    def assignment_line(self, varname):
+        return self['Points'][self.assignment_point(varname)]
+
+tests = ['test']
+for name in tests:
+    indir = os.path.join(testdir, name)
+    outdir = os.path.join(testdir, "test-output", name)
+    if not os.path.exists(outdir):
+        os.mkdir(outdir)
+
+    def compile(source):
+        cmd = "{CXX} -c {source} -fplugin={sixgill}".format(source=os.path.join(indir, source),
+                                                            CXX=cfg['CXX'], sixgill=cfg['SIXGILL_PLUGIN'])
+        print("Running %s" % cmd)
+        subprocess.check_call(["sh", "-c", cmd])
+
+    def load_db_entry(dbname, pattern):
+        if not isinstance(pattern, basestring):
+            output = subprocess.check_output([binpath("xdbkeys"), dbname + ".xdb"])
+            entries = output.splitlines()
+            matches = [f for f in entries if re.search(pattern, f)]
+            if len(matches) == 0:
+                raise Exception("entry not found")
+            if len(matches) > 1:
+                raise Exception("multiple entries found")
+            pattern = matches[0]
+
+        output = subprocess.check_output([binpath("xdbfind"), "-json", dbname + ".xdb", pattern])
+        return json.loads(output)
+
+    def computeGCTypes():
+        file("defaults.py", "w").write('''\
+analysis_scriptdir = '{testdir}'
+sixgill_bin = '{bindir}'
+'''.format(testdir=testdir, bindir=cfg['SIXGILL_BIN']))
+        cmd = [
+            os.path.join(testdir, "analyze.py"),
+            "gcTypes", "--upto", "gcTypes",
+            "--source=%s" % indir,
+            "--objdir=%s" % outdir,
+            "--js=%s" % cfg['JS_BIN'],
+        ]
+        print("Running " + " ".join(cmd))
+        output = subprocess.check_call(cmd)
+
+    def loadGCTypes():
+        gctypes = {'GCThings': [], 'GCPointers': []}
+        for line in file(os.path.join(outdir, "gcTypes.txt")):
+            m = re.match(r'^(GC\w+): (.*)', line)
+            if m:
+                gctypes[m.group(1) + 's'].append(m.group(2))
+        return gctypes
+
+    def process_body(body):
+        return Body(body)
+
+    def process_bodies(bodies):
+        return [ process_body(b) for b in bodies ]
+
+    def equal(got, expected):
+        if got != expected:
+            print("Got '%s', expected '%s'" % (got, expected))
+
+    os.chdir(outdir)
+    subprocess.call(["sh", "-c", "rm *.xdb"])
+    execfile(os.path.join(indir, "test.py"))
+    print("TEST-PASSED: %s" % name)
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/test/source.cpp
@@ -0,0 +1,70 @@
+#define ANNOTATE(property) __attribute__((tag(property)))
+
+namespace js {
+namespace gc {
+struct Cell { int f; } ANNOTATE("GC Thing");
+}
+}
+
+struct Bogon {
+};
+
+struct JustACell : public js::gc::Cell {
+    bool iHaveNoDataMembers() { return true; }
+};
+
+struct JSObject : public js::gc::Cell, public Bogon {
+    int g;
+};
+
+struct SpecialObject : public JSObject {
+    int z;
+};
+
+struct ErrorResult {
+    bool hasObj;
+    JSObject *obj;
+    void trace() {}
+} ANNOTATE("Suppressed GC Pointer");
+
+struct OkContainer {
+    ErrorResult res;
+    bool happy;
+};
+
+struct UnrootedPointer {
+    JSObject *obj;
+};
+
+template <typename T>
+class Rooted {
+    T data;
+} ANNOTATE("Rooted Pointer");
+
+extern void js_GC() ANNOTATE("GC Call") ANNOTATE("Slow");
+
+void js_GC() {}
+
+void root_arg(JSObject *obj, JSObject *random)
+{
+  // Use all these types so they get included in the output.
+  SpecialObject so;
+  UnrootedPointer up;
+  Bogon b;
+  OkContainer okc;
+  Rooted<JSObject*> ro;
+  Rooted<SpecialObject*> rso;
+
+  obj = random;
+
+  JSObject *other1 = obj;
+  js_GC();
+
+  float MARKER1 = 0;
+  JSObject *other2 = obj;
+  other1->f = 1;
+  other2->f = -1;
+
+  unsigned int u1 = 1;
+  unsigned int u2 = -1;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/test/test.py
@@ -0,0 +1,58 @@
+compile("source.cpp")
+computeGCTypes()
+body = process_body(load_db_entry("src_body", re.compile(r'root_arg'))[0])
+
+# Rendering positive and negative integers
+marker1 = body.assignment_line('MARKER1')
+equal(body.edge_from_line(marker1 + 2)['Exp'][1]['String'], '1')
+equal(body.edge_from_line(marker1 + 3)['Exp'][1]['String'], '-1')
+
+equal(body.edge_from_point(body.assignment_point('u1'))['Exp'][1]['String'], '1')
+equal(body.edge_from_point(body.assignment_point('u2'))['Exp'][1]['String'], '4294967295')
+
+assert('obj' in body['Variables'])
+assert('random' in body['Variables'])
+assert('other1' in body['Variables'])
+assert('other2' in body['Variables'])
+
+# Test function annotations
+js_GC = process_body(load_db_entry("src_body", re.compile(r'js_GC'))[0])
+annotations = js_GC['Variables']['void js_GC()']['Annotation']
+assert(annotations)
+found_call_tag = False
+for annotation in annotations:
+    (annType, value) = annotation['Name']
+    if annType == 'Tag' and value == 'GC Call':
+        found_call_tag = True
+assert(found_call_tag)
+
+# Test type annotations
+
+# js::gc::Cell first
+cell = load_db_entry("src_comp", 'js::gc::Cell')[0]
+assert(cell['Kind'] == 'Struct')
+annotations = cell['Annotation']
+assert(len(annotations) == 1)
+(tag, value) = annotations[0]['Name']
+assert(tag == 'Tag')
+assert(value == 'GC Thing')
+
+# Check JSObject inheritance.
+JSObject = load_db_entry("src_comp", 'JSObject')[0]
+bases = [ b['Base'] for b in JSObject['CSUBaseClass'] ]
+assert('js::gc::Cell' in bases)
+assert('Bogon' in bases)
+assert(len(bases) == 2)
+
+# Check type analysis
+gctypes = loadGCTypes()
+assert('js::gc::Cell' in gctypes['GCThings'])
+assert('JustACell' in gctypes['GCThings'])
+assert('JSObject' in gctypes['GCThings'])
+assert('SpecialObject' in gctypes['GCThings'])
+assert('UnrootedPointer' in gctypes['GCPointers'])
+assert('Bogon' not in gctypes['GCThings'])
+assert('Bogon' not in gctypes['GCPointers'])
+assert('ErrorResult' not in gctypes['GCPointers'])
+assert('OkContainer' not in gctypes['GCPointers'])
+assert('class Rooted<JSObject*>' not in gctypes['GCPointers'])
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -12,16 +12,17 @@
 #include "jsfriendapi.h"
 #include "jsgc.h"
 
 #include "gc/Heap.h"
 #include "gc/Nursery.h"
 #include "gc/Statistics.h"
 #include "gc/StoreBuffer.h"
 #include "gc/Tracer.h"
+#include "js/GCAnnotations.h"
 
 namespace js {
 
 class AutoLockGC;
 class VerifyPreTracer;
 
 namespace gc {
 
@@ -865,17 +866,17 @@ class GCRuntime
 
   private:
     enum IncrementalProgress
     {
         NotFinished = 0,
         Finished
     };
 
-    void minorGCImpl(JS::gcreason::Reason reason, Nursery::ObjectGroupList* pretenureGroups);
+    void minorGCImpl(JS::gcreason::Reason reason, Nursery::ObjectGroupList* pretenureGroups) JS_HAZ_GC_CALL;
 
     // For ArenaLists::allocateFromArena()
     friend class ArenaLists;
     Chunk* pickChunk(const AutoLockGC& lock,
                      AutoMaybeStartBackgroundAllocation& maybeStartBGAlloc);
     Arena* allocateArena(Chunk* chunk, Zone* zone, AllocKind kind, const AutoLockGC& lock);
     void arenaAllocatedDuringGC(JS::Zone* zone, Arena* arena);
 
@@ -911,17 +912,17 @@ class GCRuntime
     // receive a request to do GC work.
     void checkCanCallAPI();
 
     // Check if the system state is such that GC has been supressed
     // or otherwise delayed.
     bool checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason);
 
     gcstats::ZoneGCStats scanZonesBeforeGC();
-    void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason);
+    void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason) JS_HAZ_GC_CALL;
     bool gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::Reason reason);
     void incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason);
 
     void pushZealSelectedObjects();
     void purgeRuntime();
     bool beginMarkPhase(JS::gcreason::Reason reason);
     bool shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime,
                                JS::gcreason::Reason reason);
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -22,16 +22,17 @@
 #include "jspubtd.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "ds/BitArray.h"
 #include "gc/Memory.h"
 #include "js/GCAPI.h"
 #include "js/HeapAPI.h"
+#include "js/RootingAPI.h"
 #include "js/TracingAPI.h"
 
 struct JSRuntime;
 
 namespace JS {
 namespace shadow {
 struct Runtime;
 } // namespace shadow
@@ -242,17 +243,17 @@ struct Cell
 
 #ifdef DEBUG
     inline bool isAligned() const;
 #endif
 
   protected:
     inline uintptr_t address() const;
     inline Chunk* chunk() const;
-};
+} JS_HAZ_GC_THING;
 
 // A GC TenuredCell gets behaviors that are valid for things in the Tenured
 // heap, such as access to the arena and mark bits.
 class TenuredCell : public Cell
 {
   public:
     // Construct a TenuredCell from a void*, making various sanity assertions.
     static MOZ_ALWAYS_INLINE TenuredCell* fromPointer(void* ptr);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -28,17 +28,17 @@ with Files('gc/**'):
     BUG_COMPONENT = component_gc
 with Files('jit/**'):
     BUG_COMPONENT = component_jit
 
 # File-specific metadata
 for gcfile in ['jsgc*', 'devtools/rootAnalysis', 'devtools/gc-ubench', 'devtools/gctrace']:
     with Files(gcfile):
         BUG_COMPONENT = component_gc
-for header in ('GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
+for header in ('GCAnnotations.h', 'GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
     with Files('../public/' + header):
         BUG_COMPONENT = component_gc
 
 for stlfile in ['jsarray.*', 'jsbool*', 'jsdate.*', 'jsnum.*', 'json.*', 'jsstr.*']:
     with Files(stlfile):
         BUG_COMPONENT = component_stl
 
 with Files('builtin/Intl*'):
@@ -103,16 +103,17 @@ EXPORTS += [
 EXPORTS.js += [
     '../public/CallArgs.h',
     '../public/CallNonGenericMethod.h',
     '../public/CharacterEncoding.h',
     '../public/Class.h',
     '../public/Conversions.h',
     '../public/Date.h',
     '../public/Debug.h',
+    '../public/GCAnnotations.h',
     '../public/GCAPI.h',
     '../public/GCHashTable.h',
     '../public/GCPolicyAPI.h',
     '../public/GCVariant.h',
     '../public/GCVector.h',
     '../public/HashTable.h',
     '../public/HeapAPI.h',
     '../public/Id.h',
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -262,17 +262,17 @@ class TypeSet
         bool hasStableClassAndProto(CompilerConstraintList* constraints);
         void watchStateChangeForInlinedCall(CompilerConstraintList* constraints);
         void watchStateChangeForTypedArrayData(CompilerConstraintList* constraints);
         void watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints);
         HeapTypeSetKey property(jsid id);
         void ensureTrackedProperty(JSContext* cx, jsid id);
 
         ObjectGroup* maybeGroup();
-    };
+    } JS_HAZ_GC_POINTER;
 
     // Information about a single concrete type. We pack this into one word,
     // where small values are particular primitive or other singleton types and
     // larger values are either specific JS objects or object groups.
     class Type
     {
         friend class TypeSet;
 
@@ -351,17 +351,17 @@ class TypeSet
         inline ObjectGroup* groupNoBarrier() const;
 
         void trace(JSTracer* trc) {
             MarkTypeUnbarriered(trc, this, "TypeSet::Type");
         }
 
         bool operator == (Type o) const { return data == o.data; }
         bool operator != (Type o) const { return data != o.data; }
-    };
+    } JS_HAZ_GC_POINTER;
 
     static inline Type UndefinedType() { return Type(JSVAL_TYPE_UNDEFINED); }
     static inline Type NullType()      { return Type(JSVAL_TYPE_NULL); }
     static inline Type BooleanType()   { return Type(JSVAL_TYPE_BOOLEAN); }
     static inline Type Int32Type()     { return Type(JSVAL_TYPE_INT32); }
     static inline Type DoubleType()    { return Type(JSVAL_TYPE_DOUBLE); }
     static inline Type StringType()    { return Type(JSVAL_TYPE_STRING); }
     static inline Type SymbolType()    { return Type(JSVAL_TYPE_SYMBOL); }
@@ -530,17 +530,17 @@ class TypeSet
     // out of Ion, such as for argument and local types.
     static inline Type GetMaybeUntrackedValueType(const Value& val);
 
     static void MarkTypeRoot(JSTracer* trc, Type* v, const char* name);
     static void MarkTypeUnbarriered(JSTracer* trc, Type* v, const char* name);
     static bool IsTypeMarked(Type* v);
     static bool IsTypeAllocatedDuringIncremental(Type v);
     static bool IsTypeAboutToBeFinalized(Type* v);
-};
+} JS_HAZ_GC_POINTER;
 
 /*
  * A constraint which listens to additions to a type set and propagates those
  * changes to other type sets.
  */
 class TypeConstraint
 {
 public:
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1339,17 +1339,17 @@ private:
     // mFlags needs to be wide enogh to hold the flags in the above enum.
     uint16_t mFlags : 4;
     // mIndexInInterface is the index of this in our XPCNativeInterface's
     // mMembers.  In theory our XPCNativeInterface could have as many as 2^15-1
     // members (since mMemberCount is 15-bit) but in practice we prevent
     // creation of XPCNativeInterfaces which have more than 2^12 members.
     // If the width of this field changes, update GetMaxIndexInInterface.
     uint16_t mIndexInInterface : 12;
-};
+} JS_HAZ_NON_GC_POINTER; // Only stores a pinned string
 
 /***************************************************************************/
 // XPCNativeInterface represents a single idl declared interface. This is
 // primarily the set of XPCNativeMembers.
 
 // Tight. No virtual methods.
 
 class XPCNativeInterface
--- a/testing/mozharness/scripts/spidermonkey/build.shell
+++ b/testing/mozharness/scripts/spidermonkey/build.shell
@@ -1,9 +1,9 @@
 #!/bin/sh
 
 set -e
 set -x
 
 [ -d $ANALYZED_OBJDIR ] || mkdir $ANALYZED_OBJDIR
 cd $ANALYZED_OBJDIR
-$SOURCE/js/src/configure --enable-debug --enable-optimize --enable-stdcxx-compat --enable-ctypes --enable-exact-rooting --enable-gcgenerational --with-system-nspr
+$SOURCE/js/src/configure --enable-debug --enable-optimize --enable-stdcxx-compat --enable-ctypes --with-system-nspr
 make -j12 -s