Bug 1083456 - Part 2: Provide a mechanism to find all GC roots for a JS::ubi::Node traversal. r=jimb

This commit is contained in:
Nick Fitzgerald 2014-11-19 09:57:11 -08:00
parent 557b64e31d
commit 400c4d17a5
8 changed files with 292 additions and 50 deletions

View File

@ -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

View File

@ -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<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>

View File

@ -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.");

View File

@ -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.");

View File

@ -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;
}

View File

@ -177,6 +177,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
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);
@ -486,6 +487,8 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
bool hasMemory() const;
DebuggerMemory &memory() const;
GlobalObjectSet::Range allDebuggees() const { return debuggees.all(); }
/*********************************** Methods for interaction with the GC. */
/*

View File

@ -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<JSObject *>(r.front())))
{
return false;
}
}
{
Maybe<JS::AutoCheckCannotGC> 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<JSObject *>(r.front())))
return false;
}
if (!traversal.traverse())
if (!traversal.addStart(JS::ubi::Node(&rootList)) ||
!traversal.traverse())
{
return false;
}
}
return handler.report(census, args.rval());

View File

@ -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<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.
@ -285,3 +256,100 @@ 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