diff --git a/js/public/Debug.h b/js/public/Debug.h index 537b760f261..afa164f7d56 100644 --- a/js/public/Debug.h +++ b/js/public/Debug.h @@ -250,6 +250,7 @@ class BuilderOrigin : public Builder { JSObject *unwrap(Object &object) { return unwrapAny(object); } }; + // Finding the size of blocks allocated with malloc // ------------------------------------------------ @@ -263,6 +264,7 @@ class BuilderOrigin : public Builder { // malloc'd blocks. void SetDebuggerMallocSizeOf(JSRuntime *runtime, mozilla::MallocSizeOf mallocSizeOf); + // Handlers for observing Promises // ------------------------------- @@ -292,6 +294,12 @@ onNewPromise(JSContext *cx, HandleObject promise); 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 diff --git a/js/public/UbiNode.h b/js/public/UbiNode.h index ee90b0137d6..589ecbf2ce4 100644 --- a/js/public/UbiNode.h +++ b/js/public/UbiNode.h @@ -10,6 +10,7 @@ #include "mozilla/Alignment.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Move.h" @@ -19,6 +20,7 @@ #include "js/HashTable.h" #include "js/TracingAPI.h" #include "js/TypeDecls.h" +#include "js/Vector.h" // JS::ubi::Node // @@ -139,6 +141,8 @@ namespace JS { namespace ubi { +using mozilla::Maybe; + class Edge; class EdgeRange; @@ -422,8 +426,104 @@ class EdgeRange { }; +// 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(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 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 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 &noGC; + + public: + SimpleEdgeVector edges; + bool wantNames; + + RootList(JSContext *cx, Maybe &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 : 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(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 diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-04.js b/js/src/jit-test/tests/debug/Memory-takeCensus-04.js new file mode 100644 index 00000000000..ac6593f56ac --- /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."); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-05.js b/js/src/jit-test/tests/debug/Memory-takeCensus-05.js new file mode 100644 index 00000000000..c03cae182e2 --- /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."); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index a9e071b20b2..32a4802cc3e 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -7254,4 +7254,16 @@ 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; } diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index 7b4f0c3c0ba..32a9ee930b4 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -177,6 +177,7 @@ class Debugger : private mozilla::LinkedListElement friend class SavedStacks; friend class mozilla::LinkedListElement; 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); @@ -486,6 +487,8 @@ class Debugger : private mozilla::LinkedListElement bool hasMemory() const; DebuggerMemory &memory() const; + GlobalObjectSet::Range allDebuggees() const { return debuggees.all(); } + /*********************************** Methods for interaction with the GC. */ /* diff --git a/js/src/vm/DebuggerMemory.cpp b/js/src/vm/DebuggerMemory.cpp index 35dcaaed75f..1b0ef7a6117 100644 --- a/js/src/vm/DebuggerMemory.cpp +++ b/js/src/vm/DebuggerMemory.cpp @@ -34,6 +34,7 @@ using JS::ubi::Node; using mozilla::Maybe; using mozilla::Move; +using mozilla::Nothing; /* static */ DebuggerMemory * DebuggerMemory::create(JSContext *cx, Debugger *dbg) @@ -754,34 +755,44 @@ 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; - { - JS::AutoCheckCannotGC noGC; + Debugger *dbg = memory->getDebugger(); - dbg::DefaultCensusTraversal traversal(cx, handler, noGC); + // 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(r.front()))) + { + return false; + } + } + + { + Maybe maybeNoGC; + JS::ubi::RootList rootList(cx, maybeNoGC); + if (!rootList.init(cx, census.debuggeeZones)) + return false; + + 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(r.front()))) - return false; - } - - if (!traversal.traverse()) + if (!traversal.addStart(JS::ubi::Node(&rootList)) || + !traversal.traverse()) + { return false; + } } return handler.report(census, args.rval()); diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp index 1b5cbce65bf..992555cba80 100644 --- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -16,23 +16,30 @@ #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; @@ -103,42 +110,6 @@ Node::exposeToJS() const 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(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 SimpleEdgeVector; - // A JSTracer subclass that adds a SimpleEdge to a Vector for each edge on // which it is invoked. @@ -285,3 +256,100 @@ template class TracerConcreteWithCompartment; template class TracerConcrete; } } + + +namespace JS { +namespace ubi { + +RootList::RootList(JSContext *cx, Maybe &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::concreteTypeName[] = MOZ_UTF16("RootList"); + +EdgeRange * +Concrete::edges(JSContext *cx, bool wantNames) const { + MOZ_ASSERT_IF(wantNames, get().wantNames); + return js_new(cx, get().edges); +} + +} // namespace ubi +} // namespace JS