Bug 1246804 - Switch to using in-source annotations. Use C++ inheritance information when describing GC types. Add a test suite., r=terrence
authorSteve Fink <sfink@mozilla.com>
Tue, 29 Sep 2015 13:39:33 -0700
changeset 326659 74feb4250db0c7d06d5004e5e7948637ca915aee
parent 326658 d2144af8a3fee962d269a9e744e1933a4d363f28
child 326660 2cecd1a0e9107a2d29e1a485c351a7cd783e96e7
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs1246804
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;
 }
old mode 100755
new mode 100644
--- 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
old mode 100755
new mode 100644
--- 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