Bug 1083456 - Part 2: Provide a mechanism to find all GC roots for a JS::ubi::Node traversal. r=jimb
☠☠ backed out by 3f5f0844f2f4 ☠ ☠
authorNick Fitzgerald <fitzgen@gmail.com>
Wed, 19 Nov 2014 09:57:11 -0800
changeset 216512 73ea3aa3ccec755656cd0f40a5491eca05a492d3
parent 216511 a1e78007197e5759dd41b2f91034b7c2413df8b8
child 216513 577bc6a54c1daa6060c960de2ad083a8d817c877
push idunknown
push userunknown
push dateunknown
reviewersjimb
bugs1083456
milestone36.0a1
Bug 1083456 - Part 2: Provide a mechanism to find all GC roots for a JS::ubi::Node traversal. r=jimb
js/public/Debug.h
js/public/UbiNode.h
js/src/jit-test/tests/debug/Memory-takeCensus-04.js
js/src/jit-test/tests/debug/Memory-takeCensus-05.js
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/DebuggerMemory.cpp
js/src/vm/UbiNode.cpp
--- a/js/public/Debug.h
+++ b/js/public/Debug.h
@@ -245,29 +245,31 @@ class BuilderOrigin : public Builder {
   public:
     BuilderOrigin(JSContext *cx, js::Debugger *debugger_)
       : Builder(cx, debugger_)
     { }
 
     JSObject *unwrap(Object &object) { return unwrapAny(object); }
 };
 
+
 
 // Finding the size of blocks allocated with malloc
 // ------------------------------------------------
 //
 // Debugger.Memory wants to be able to report how many bytes items in memory are
 // consuming. To do this, it needs a function that accepts a pointer to a block,
 // and returns the number of bytes allocated to that block. SpiderMonkey itself
 // doesn't know which function is appropriate to use, but the embedding does.
 
 // Tell Debuggers in |runtime| to use |mallocSizeOf| to find the size of
 // malloc'd blocks.
 void SetDebuggerMallocSizeOf(JSRuntime *runtime, mozilla::MallocSizeOf mallocSizeOf);
 
+
 
 // Handlers for observing Promises
 // -------------------------------
 //
 // The Debugger wants to observe behavior of promises, which are implemented by
 // Gecko with webidl and which SpiderMonkey knows nothing about. On the other
 // hand, Gecko knows nothing about which (if any) debuggers are observing a
 // promise's global. The compromise is that Gecko is responsible for calling
@@ -287,13 +289,19 @@ onNewPromise(JSContext *cx, HandleObject
 // promise, in which case neither promise has settled yet.
 //
 // It is Gecko's responsibility to ensure that this is never called on the same
 // promise more than once (because a promise can only make the transition from
 // unsettled to settled once).
 JS_PUBLIC_API(void)
 onPromiseSettled(JSContext *cx, HandleObject promise);
 
+
+
+// Return true if the given value is a Debugger object, false otherwise.
+JS_PUBLIC_API(bool)
+IsDebugger(JS::Value val);
+
 } // namespace dbg
 } // namespace JS
 
 
 #endif /* js_Debug_h */
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -5,25 +5,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef js_UbiNode_h
 #define js_UbiNode_h
 
 #include "mozilla/Alignment.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 
 #include "jspubtd.h"
 
 #include "js/GCAPI.h"
 #include "js/HashTable.h"
 #include "js/TracingAPI.h"
 #include "js/TypeDecls.h"
+#include "js/Vector.h"
 
 // JS::ubi::Node
 //
 // JS::ubi::Node is a pointer-like type designed for internal use by heap
 // analysis tools. A ubi::Node can refer to:
 //
 // - a JS value, like a string, object, or symbol;
 // - an internal SpiderMonkey structure, like a shape or a scope chain object
@@ -134,16 +136,18 @@
 //
 // If this restriction prevents us from implementing interesting tools, we may
 // teach the GC how to root ubi::Nodes, fix up hash tables that use them as
 // keys, etc.
 
 namespace JS {
 namespace ubi {
 
+using mozilla::Maybe;
+
 class Edge;
 class EdgeRange;
 
 // The base class implemented by each ubi::Node referent type. Subclasses must
 // not add data members to this class.
 class Base {
     friend class Node;
 
@@ -417,18 +421,114 @@ class EdgeRange {
     virtual void popFront() = 0;
 
   private:
     EdgeRange(const EdgeRange &) MOZ_DELETE;
     EdgeRange &operator=(const EdgeRange &) MOZ_DELETE;
 };
 
 
+// A dumb Edge concrete class. All but the most essential members have the
+// default behavior.
+class SimpleEdge : public Edge {
+    SimpleEdge(SimpleEdge &) MOZ_DELETE;
+    SimpleEdge &operator=(const SimpleEdge &) MOZ_DELETE;
+
+  public:
+    SimpleEdge() : Edge() { }
+
+    // Construct an initialized SimpleEdge, taking ownership of |name|.
+    SimpleEdge(char16_t *name, const Node &referent) {
+        this->name = name;
+        this->referent = referent;
+    }
+    ~SimpleEdge() {
+        js_free(const_cast<char16_t *>(name));
+    }
+
+    // Move construction and assignment.
+    SimpleEdge(SimpleEdge &&rhs) {
+        name = rhs.name;
+        referent = rhs.referent;
+
+        rhs.name = nullptr;
+    }
+    SimpleEdge &operator=(SimpleEdge &&rhs) {
+        MOZ_ASSERT(&rhs != this);
+        this->~SimpleEdge();
+        new(this) SimpleEdge(mozilla::Move(rhs));
+        return *this;
+    }
+};
+
+typedef mozilla::Vector<SimpleEdge, 8, js::TempAllocPolicy> SimpleEdgeVector;
+
+
+// RootList is a class that can be pointed to by a |ubi::Node|, creating a
+// fictional root-of-roots which has edges to every GC root in the JS
+// runtime. Having a single root |ubi::Node| is useful for algorithms written
+// with the assumption that there aren't multiple roots (such as computing
+// dominator trees) and you want a single point of entry. It also ensures that
+// the roots themselves get visited by |ubi::BreadthFirst| (they would otherwise
+// only be used as starting points).
+//
+// RootList::init itself causes a minor collection, but once the list of roots
+// has been created, GC must not occur, as the referent ubi::Nodes are not
+// stable across GC. The init calls emplace |gcp|'s AutoCheckCannotGC, whose
+// lifetime must extend at least as long as the RootList itself.
+//
+// Example usage:
+//
+//    {
+//        mozilla::Maybe<JS::AutoCheckCannotGC> maybeNoGC;
+//        JS::ubi::RootList rootList(cx, maybeNoGC);
+//        if (!rootList.init(cx))
+//            return false;
+//
+//        // The AutoCheckCannotGC is guaranteed to exist if init returned true.
+//        MOZ_ASSERT(maybeNoGC.isSome());
+//
+//        JS::ubi::Node root(&rootList);
+//
+//        ...
+//    }
+class MOZ_STACK_CLASS RootList {
+    Maybe<AutoCheckCannotGC> &noGC;
+
+  public:
+    SimpleEdgeVector edges;
+    bool             wantNames;
+
+    RootList(JSContext *cx, Maybe<AutoCheckCannotGC> &noGC, bool wantNames = false);
+
+    // Find all GC roots.
+    bool init(JSContext *cx);
+    // Find only GC roots in the provided set of |Zone|s.
+    bool init(JSContext *cx, ZoneSet &debuggees);
+    // Find only GC roots in the given Debugger object's set of debuggee zones.
+    bool init(JSContext *cx, HandleObject debuggees);
+};
+
+
 // Concrete classes for ubi::Node referent types.
 
+template<>
+struct Concrete<RootList> : public Base {
+    EdgeRange *edges(JSContext *cx, bool wantNames) const MOZ_OVERRIDE;
+    const char16_t *typeName() const MOZ_OVERRIDE { return concreteTypeName; }
+
+  protected:
+    explicit Concrete(RootList *ptr) : Base(ptr) { }
+    RootList &get() const { return *static_cast<RootList *>(ptr); }
+
+  public:
+    static const char16_t concreteTypeName[];
+    static void construct(void *storage, RootList *ptr) { new (storage) Concrete(ptr); }
+};
+
 // A reusable ubi::Concrete specialization base class for types supported by
 // JS_TraceChildren.
 template<typename Referent>
 class TracerConcrete : public Base {
     const char16_t *typeName() const MOZ_OVERRIDE { return concreteTypeName; }
     EdgeRange *edges(JSContext *, bool wantNames) const MOZ_OVERRIDE;
     JS::Zone *zone() const MOZ_OVERRIDE;
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-04.js
@@ -0,0 +1,26 @@
+// Test that Debugger.Memory.prototype.takeCensus finds GC roots that are on the
+// stack.
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+
+g.eval(`
+  function withTypedArrayOnStack(f) {
+    (function () {
+      var onStack = new Int8Array();
+      f();
+    }())
+  }
+`);
+
+assertEq("Int8Array" in dbg.memory.takeCensus().objects, false,
+         "There shouldn't exist any typed arrays in the census.");
+
+var typedArrayCount;
+g.withTypedArrayOnStack(() => {
+  typedArrayCount = dbg.memory.takeCensus().objects.Int8Array.count;
+});
+
+assertEq(typedArrayCount, 1,
+         "Should have one typed array in the census, because there " +
+         "was one on the stack.");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-05.js
@@ -0,0 +1,14 @@
+// Test that Debugger.Memory.prototype.takeCensus finds cross compartment
+// wrapper GC roots.
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+
+assertEq("Int8Array" in dbg.memory.takeCensus().objects, false,
+         "There shouldn't exist any typed arrays in the census.");
+
+this.ccw = g.eval("new Int8Array()");
+
+assertEq(dbg.memory.takeCensus().objects.Int8Array.count, 1,
+         "Should have one typed array in the census, because there " +
+         "is one cross-compartment wrapper.");
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -7249,9 +7249,21 @@ JS::dbg::onNewPromise(JSContext *cx, Han
     Debugger::slowPathPromiseHook(cx, Debugger::OnNewPromise, promise);
 }
 
 JS_PUBLIC_API(void)
 JS::dbg::onPromiseSettled(JSContext *cx, HandleObject promise)
 {
     AssertIsPromise(cx, promise);
     Debugger::slowPathPromiseHook(cx, Debugger::OnPromiseSettled, promise);
-}
+
+JS_PUBLIC_API(bool)
+JS::dbg::IsDebugger(JS::Value val)
+{
+    if (!val.isObject())
+        return false;
+
+    JSObject &obj = val.toObject();
+    if (obj.getClass() != &Debugger::jsclass)
+        return false;
+
+    return js::Debugger::fromJSObject(&obj) != nullptr;
+}
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -172,16 +172,17 @@ typedef JSObject Env;
 
 class Debugger : private mozilla::LinkedListElement<Debugger>
 {
     friend class Breakpoint;
     friend class DebuggerMemory;
     friend class SavedStacks;
     friend class mozilla::LinkedListElement<Debugger>;
     friend bool (::JS_DefineDebuggerObject)(JSContext *cx, JS::HandleObject obj);
+    friend bool (::JS::dbg::IsDebugger)(JS::Value val);
     friend bool SavedStacksMetadataCallback(JSContext *cx, JSObject **pmetadata);
     friend void JS::dbg::onNewPromise(JSContext *cx, HandleObject promise);
     friend void JS::dbg::onPromiseSettled(JSContext *cx, HandleObject promise);
 
   public:
     enum Hook {
         OnDebuggerStatement,
         OnExceptionUnwind,
@@ -481,16 +482,18 @@ class Debugger : private mozilla::Linked
     inline const js::HeapPtrNativeObject &toJSObject() const;
     inline js::HeapPtrNativeObject &toJSObjectRef();
     static inline Debugger *fromJSObject(JSObject *obj);
     static Debugger *fromChildJSObject(JSObject *obj);
 
     bool hasMemory() const;
     DebuggerMemory &memory() const;
 
+    GlobalObjectSet::Range allDebuggees() const { return debuggees.all(); }
+
     /*********************************** Methods for interaction with the GC. */
 
     /*
      * A Debugger object is live if:
      *   * the Debugger JSObject is live (Debugger::trace handles this case); OR
      *   * it is in the middle of dispatching an event (the event dispatching
      *     code roots it in this case); OR
      *   * it is enabled, and it is debugging at least one live compartment,
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -29,16 +29,17 @@
 using namespace js;
 
 using JS::ubi::BreadthFirst;
 using JS::ubi::Edge;
 using JS::ubi::Node;
 
 using mozilla::Maybe;
 using mozilla::Move;
+using mozilla::Nothing;
 
 /* static */ DebuggerMemory *
 DebuggerMemory::create(JSContext *cx, Debugger *dbg)
 {
 
     Value memoryProto = dbg->object->getReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO);
     RootedNativeObject memory(cx, NewNativeObjectWithGivenProto(cx, &class_,
                                                                 &memoryProto.toObject(), nullptr));
@@ -749,44 +750,54 @@ typedef BreadthFirst<DefaultCensusHandle
 
 } // namespace dbg
 } // namespace js
 
 bool
 DebuggerMemory::takeCensus(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER_MEMORY(cx, argc, vp, "Debugger.Memory.prototype.census", args, memory);
-    Debugger *debugger = memory->getDebugger();
 
     dbg::Census census(cx);
     if (!census.init())
         return false;
+
     dbg::DefaultCensusHandler handler(census);
     if (!handler.init(census))
         return false;
 
+    Debugger *dbg = memory->getDebugger();
+
+    // Populate census.debuggeeZones and ensure that all of our debuggee globals
+    // are rooted so that they are visible in the RootList.
+    JS::AutoObjectVector debuggees(cx);
+    for (GlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
+        if (!census.debuggeeZones.put(r.front()->zone()) ||
+            !debuggees.append(static_cast<JSObject *>(r.front())))
+        {
+            return false;
+        }
+    }
+
     {
-        JS::AutoCheckCannotGC noGC;
+        Maybe<JS::AutoCheckCannotGC> maybeNoGC;
+        JS::ubi::RootList rootList(cx, maybeNoGC);
+        if (!rootList.init(cx, census.debuggeeZones))
+            return false;
 
-        dbg::DefaultCensusTraversal traversal(cx, handler, noGC);
+        dbg::DefaultCensusTraversal traversal(cx, handler, maybeNoGC.ref());
         if (!traversal.init())
             return false;
         traversal.wantNames = false;
 
-        // Walk the debuggee compartments, using it to set the starting points
-        // (the debuggee globals) for the traversal, and to populate
-        // census.debuggeeZones.
-        for (GlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) {
-            if (!census.debuggeeZones.put(r.front()->zone()) ||
-                !traversal.addStart(static_cast<JSObject *>(r.front())))
-                return false;
+        if (!traversal.addStart(JS::ubi::Node(&rootList)) ||
+            !traversal.traverse())
+        {
+            return false;
         }
-
-        if (!traversal.traverse())
-            return false;
     }
 
     return handler.report(census, args.rval());
 }
 
 
 
 /* Debugger.Memory property and method tables. */
--- a/js/src/vm/UbiNode.cpp
+++ b/js/src/vm/UbiNode.cpp
@@ -11,33 +11,40 @@
 #include "mozilla/Scoped.h"
 
 #include "jscntxt.h"
 #include "jsinfer.h"
 #include "jsobj.h"
 #include "jsscript.h"
 
 #include "jit/IonCode.h"
+#include "js/Debug.h"
 #include "js/TracingAPI.h"
 #include "js/TypeDecls.h"
 #include "js/Utility.h"
 #include "js/Vector.h"
+#include "vm/GlobalObject.h"
 #include "vm/ScopeObject.h"
 #include "vm/Shape.h"
 #include "vm/String.h"
 #include "vm/Symbol.h"
 
 #include "jsobjinlines.h"
+#include "vm/Debugger-inl.h"
 
+using mozilla::Some;
 using JS::HandleValue;
 using JS::Value;
+using JS::ZoneSet;
 using JS::ubi::Concrete;
 using JS::ubi::Edge;
 using JS::ubi::EdgeRange;
 using JS::ubi::Node;
+using JS::ubi::SimpleEdge;
+using JS::ubi::SimpleEdgeVector;
 using JS::ubi::TracerConcrete;
 using JS::ubi::TracerConcreteWithCompartment;
 
 // All operations on null ubi::Nodes crash.
 const char16_t *Concrete<void>::typeName() const          { MOZ_CRASH("null ubi::Node"); }
 EdgeRange *Concrete<void>::edges(JSContext *, bool) const { MOZ_CRASH("null ubi::Node"); }
 JS::Zone *Concrete<void>::zone() const                    { MOZ_CRASH("null ubi::Node"); }
 JSCompartment *Concrete<void>::compartment() const        { MOZ_CRASH("null ubi::Node"); }
@@ -98,52 +105,16 @@ Node::exposeToJS() const
         v.setSymbol(as<JS::Symbol>());
     } else {
         v.setUndefined();
     }
 
     return v;
 }
 
-// A dumb Edge concrete class. All but the most essential members have the
-// default behavior.
-class SimpleEdge : public Edge {
-    SimpleEdge(SimpleEdge &) MOZ_DELETE;
-    SimpleEdge &operator=(const SimpleEdge &) MOZ_DELETE;
-
-  public:
-    SimpleEdge() : Edge() { }
-
-    // Construct an initialized SimpleEdge, taking ownership of |name|.
-    SimpleEdge(char16_t *name, const Node &referent) {
-        this->name = name;
-        this->referent = referent;
-    }
-    ~SimpleEdge() {
-        js_free(const_cast<char16_t *>(name));
-    }
-
-    // Move construction and assignment.
-    SimpleEdge(SimpleEdge &&rhs) {
-        name = rhs.name;
-        referent = rhs.referent;
-
-        rhs.name = nullptr;
-    }
-    SimpleEdge &operator=(SimpleEdge &&rhs) {
-        MOZ_ASSERT(&rhs != this);
-        this->~SimpleEdge();
-        new(this) SimpleEdge(mozilla::Move(rhs));
-        return *this;
-    }
-};
-
-
-typedef mozilla::Vector<SimpleEdge, 8, js::TempAllocPolicy> SimpleEdgeVector;
-
 
 // A JSTracer subclass that adds a SimpleEdge to a Vector for each edge on
 // which it is invoked.
 class SimpleEdgeVectorTracer : public JSTracer {
     // The vector to which we add SimpleEdges.
     SimpleEdgeVector *vec;
 
     // True if we should populate the edge's names.
@@ -280,8 +251,105 @@ template class TracerConcrete<JS::Symbol
 template class TracerConcreteWithCompartment<JSScript>;
 template class TracerConcrete<js::LazyScript>;
 template class TracerConcrete<js::jit::JitCode>;
 template class TracerConcreteWithCompartment<js::Shape>;
 template class TracerConcreteWithCompartment<js::BaseShape>;
 template class TracerConcrete<js::types::TypeObject>;
 }
 }
+
+
+namespace JS {
+namespace ubi {
+
+RootList::RootList(JSContext *cx, Maybe<AutoCheckCannotGC> &noGC, bool wantNames /* = false */)
+  : noGC(noGC),
+    edges(cx),
+    wantNames(wantNames)
+{ }
+
+
+bool
+RootList::init(JSContext *cx)
+{
+    SimpleEdgeVectorTracer tracer(cx, &edges, wantNames);
+    JS_TraceRuntime(&tracer);
+    if (!tracer.okay)
+        return false;
+    noGC.emplace(cx->runtime());
+    return true;
+}
+
+bool
+RootList::init(JSContext *cx, ZoneSet &debuggees)
+{
+    SimpleEdgeVector allRootEdges(cx);
+    SimpleEdgeVectorTracer tracer(cx, &allRootEdges, wantNames);
+
+    JS_TraceRuntime(&tracer);
+    if (!tracer.okay)
+        return false;
+    JS_TraceIncomingCCWs(&tracer, debuggees);
+    if (!tracer.okay)
+        return false;
+
+    for (SimpleEdgeVector::Range r = allRootEdges.all(); !r.empty(); r.popFront()) {
+        SimpleEdge &edge = r.front();
+        Zone *zone = edge.referent.zone();
+        if (zone && !debuggees.has(zone))
+            continue;
+        if (!edges.append(mozilla::Move(edge)))
+            return false;
+    }
+
+    noGC.emplace(cx->runtime());
+    return true;
+}
+
+bool
+RootList::init(JSContext *cx, HandleObject debuggees)
+{
+    MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(ObjectValue(*debuggees)));
+    js::Debugger *dbg = js::Debugger::fromJSObject(debuggees);
+
+    ZoneSet debuggeeZones;
+    if (!debuggeeZones.init())
+        return false;
+
+    for (js::GlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
+        if (!debuggeeZones.put(r.front()->zone()))
+            return false;
+    }
+
+    return init(cx, debuggeeZones);
+}
+
+// An EdgeRange concrete class that holds a pre-existing vector of SimpleEdges.
+class PreComputedEdgeRange : public EdgeRange {
+    SimpleEdgeVector &edges;
+    size_t           i;
+
+    void settle() {
+        front_ = i < edges.length() ? &edges[i] : nullptr;
+    }
+
+  public:
+    explicit PreComputedEdgeRange(JSContext *cx, SimpleEdgeVector &edges)
+      : edges(edges),
+        i(0)
+    {
+        settle();
+    }
+
+    void popFront() MOZ_OVERRIDE { i++; settle(); }
+};
+
+const char16_t Concrete<RootList>::concreteTypeName[] = MOZ_UTF16("RootList");
+
+EdgeRange *
+Concrete<RootList>::edges(JSContext *cx, bool wantNames) const {
+    MOZ_ASSERT_IF(wantNames, get().wantNames);
+    return js_new<PreComputedEdgeRange>(cx, get().edges);
+}
+
+} // namespace ubi
+} // namespace JS