Bug 1182653 - Use JSRuntime* instead of JSContext* in ubi::Node infrastructure; r=sfink

This enables the use of ubi::Node in situations where a JSContext* is not
available, and paves the way for debugging utilities to dump shortest paths from
GC roots to a given ubi::Node that can be used while paused in a debugger.
This commit is contained in:
Nick Fitzgerald 2015-09-23 09:39:56 -07:00
parent 407f41a2b0
commit 54a8708a3a
18 changed files with 118 additions and 91 deletions

View File

@ -108,8 +108,8 @@ class DeserializedEdgeRange : public EdgeRange
}
public:
explicit DeserializedEdgeRange(JSContext* cx)
: edges(cx)
explicit DeserializedEdgeRange()
: edges()
, i(0)
{
settle();
@ -160,10 +160,10 @@ Concrete<DeserializedNode>::allocationStack() const
UniquePtr<EdgeRange>
Concrete<DeserializedNode>::edges(JSContext* cx, bool) const
Concrete<DeserializedNode>::edges(JSRuntime* rt, bool) const
{
UniquePtr<DeserializedEdgeRange, JS::DeletePolicy<DeserializedEdgeRange>> range(
js_new<DeserializedEdgeRange>(cx));
js_new<DeserializedEdgeRange>());
if (!range || !range->init(get()))
return nullptr;

View File

@ -265,7 +265,7 @@ public:
// We ignore the `bool wantNames` parameter because we can't control whether
// the core dump was serialized with edge names or not.
UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override;
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool) const override;
};
template<>

View File

@ -384,7 +384,7 @@ HeapSnapshot::TakeCensus(JSContext* cx, JS::HandleObject options,
{
JS::AutoCheckCannotGC nogc;
JS::ubi::CensusTraversal traversal(cx, handler, nogc);
JS::ubi::CensusTraversal traversal(JS_GetRuntime(cx), handler, nogc);
if (NS_WARN_IF(!traversal.init())) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
@ -684,7 +684,7 @@ public:
}
if (includeEdges) {
auto edges = ubiNode.edges(cx, wantNames);
auto edges = ubiNode.edges(JS_GetRuntime(cx), wantNames);
if (NS_WARN_IF(!edges))
return false;
@ -795,7 +795,7 @@ WriteHeapGraph(JSContext* cx,
// core dump.
HeapSnapshotHandler handler(writer, zones);
HeapSnapshotHandler::Traversal traversal(cx, handler, noGC);
HeapSnapshotHandler::Traversal traversal(JS_GetRuntime(cx), handler, noGC);
if (!traversal.init())
return false;
traversal.wantNames = wantNames;
@ -960,7 +960,7 @@ ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global,
{
Maybe<AutoCheckCannotGC> maybeNoGC;
ubi::RootList rootList(cx, maybeNoGC, wantNames);
ubi::RootList rootList(JS_GetRuntime(cx), maybeNoGC, wantNames);
if (!EstablishBoundaries(cx, rv, boundaries, rootList, zones))
return;

View File

@ -97,5 +97,5 @@ DEF_TEST(DeserializedNodeUbiNodes, {
.Times(1)
.WillOnce(Return(JS::ubi::Node(referent3.get())));
ubi.edges(cx);
ubi.edges(JS_GetRuntime(cx));
});

View File

@ -162,8 +162,8 @@ public:
JS::Zone* zone;
size_t size;
explicit FakeNode(JSContext* cx)
: edges(cx),
explicit FakeNode()
: edges(),
compartment(nullptr),
zone(nullptr),
size(1)
@ -182,8 +182,8 @@ class Concrete<FakeNode> : public Base
return concreteTypeName;
}
UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override {
return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(cx, get().edges));
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override {
return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
}
Size size(mozilla::MallocSizeOf) const override {
@ -233,8 +233,8 @@ void AddEdge(FakeNode& node, FakeNode& referent, const char16_t* edgeName = null
namespace testing {
// Ensure that given node has the expected number of edges.
MATCHER_P2(EdgesLength, cx, expectedLength, "") {
auto edges = arg.edges(cx);
MATCHER_P2(EdgesLength, rt, expectedLength, "") {
auto edges = arg.edges(rt);
if (!edges)
return false;
@ -247,8 +247,8 @@ MATCHER_P2(EdgesLength, cx, expectedLength, "") {
}
// Get the nth edge and match it with the given matcher.
MATCHER_P3(Edge, cx, n, matcher, "") {
auto edges = arg.edges(cx);
MATCHER_P3(Edge, rt, n, matcher, "") {
auto edges = arg.edges(rt);
if (!edges)
return false;

View File

@ -29,10 +29,10 @@ DEF_TEST(DoesCrossZoneBoundaries, {
ASSERT_TRUE(targetZones.put(zone));
ASSERT_TRUE(targetZones.put(newZone));
FakeNode nodeA(cx);
FakeNode nodeB(cx);
FakeNode nodeC(cx);
FakeNode nodeD(cx);
FakeNode nodeA;
FakeNode nodeB;
FakeNode nodeC;
FakeNode nodeD;
nodeA.zone = zone;
nodeB.zone = nullptr;

View File

@ -29,9 +29,9 @@ DEF_TEST(DoesntCrossZoneBoundaries, {
ASSERT_TRUE(targetZones.init());
ASSERT_TRUE(targetZones.put(zone));
FakeNode nodeA(cx);
FakeNode nodeB(cx);
FakeNode nodeC(cx);
FakeNode nodeA;
FakeNode nodeB;
FakeNode nodeC;
nodeA.zone = zone;
nodeB.zone = nullptr;

View File

@ -13,8 +13,8 @@ using testing::Property;
using testing::Return;
DEF_TEST(SerializesEdgeNames, {
FakeNode node(cx);
FakeNode referent(cx);
FakeNode node;
FakeNode referent;
const char16_t edgeName[] = MOZ_UTF16("edge name");
const char16_t emptyStr[] = MOZ_UTF16("");
@ -28,12 +28,12 @@ DEF_TEST(SerializesEdgeNames, {
// Should get the node with edges once.
EXPECT_CALL(
writer,
writeNode(AllOf(EdgesLength(cx, 3),
Edge(cx, 0, Field(&JS::ubi::Edge::name,
writeNode(AllOf(EdgesLength(rt, 3),
Edge(rt, 0, Field(&JS::ubi::Edge::name,
UTF16StrEq(edgeName))),
Edge(cx, 1, Field(&JS::ubi::Edge::name,
Edge(rt, 1, Field(&JS::ubi::Edge::name,
UTF16StrEq(emptyStr))),
Edge(cx, 2, Field(&JS::ubi::Edge::name,
Edge(rt, 2, Field(&JS::ubi::Edge::name,
IsNull()))),
_)
)

View File

@ -8,10 +8,10 @@
#include "DevTools.h"
DEF_TEST(SerializesEverythingInHeapGraphOnce, {
FakeNode nodeA(cx);
FakeNode nodeB(cx);
FakeNode nodeC(cx);
FakeNode nodeD(cx);
FakeNode nodeA;
FakeNode nodeB;
FakeNode nodeC;
FakeNode nodeD;
AddEdge(nodeA, nodeB);
AddEdge(nodeB, nodeC);

View File

@ -11,7 +11,7 @@ using testing::Property;
using testing::Return;
DEF_TEST(SerializesTypeNames, {
FakeNode node(cx);
FakeNode node;
::testing::NiceMock<MockWriter> writer;
EXPECT_CALL(writer, writeNode(Property(&JS::ubi::Node::typeName,

View File

@ -569,7 +569,7 @@ class Base {
//
// If wantNames is true, compute names for edges. Doing so can be expensive
// in time and memory.
virtual UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const = 0;
virtual UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const = 0;
// Return the Zone to which this node's referent belongs, or nullptr if the
// referent is not of a type allocated in SpiderMonkey Zones.
@ -756,8 +756,8 @@ class Node {
return base()->size(mallocSizeof);
}
UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames = true) const {
return base()->edges(cx, wantNames);
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames = true) const {
return base()->edges(rt, wantNames);
}
bool hasAllocationStack() const { return base()->hasAllocationStack(); }
@ -868,7 +868,7 @@ class EdgeRange {
};
typedef mozilla::Vector<Edge, 8, js::TempAllocPolicy> EdgeVector;
typedef mozilla::Vector<Edge, 8, js::SystemAllocPolicy> EdgeVector;
// An EdgeRange concrete class that holds a pre-existing vector of
// Edges. A PreComputedEdgeRange does not take ownership of its
@ -883,7 +883,7 @@ class PreComputedEdgeRange : public EdgeRange {
}
public:
explicit PreComputedEdgeRange(JSContext* cx, EdgeVector& edges)
explicit PreComputedEdgeRange(EdgeVector& edges)
: edges(edges),
i(0)
{
@ -916,7 +916,7 @@ class PreComputedEdgeRange : public EdgeRange {
//
// {
// mozilla::Maybe<JS::AutoCheckCannotGC> maybeNoGC;
// JS::ubi::RootList rootList(cx, maybeNoGC);
// JS::ubi::RootList rootList(rt, maybeNoGC);
// if (!rootList.init())
// return false;
//
@ -929,13 +929,13 @@ class PreComputedEdgeRange : public EdgeRange {
// }
class MOZ_STACK_CLASS RootList {
Maybe<AutoCheckCannotGC>& noGC;
JSContext* cx;
public:
JSRuntime* rt;
EdgeVector edges;
bool wantNames;
RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false);
RootList(JSRuntime* rt, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false);
// Find all GC roots.
bool init();
@ -959,7 +959,7 @@ class MOZ_STACK_CLASS RootList {
template<>
struct Concrete<RootList> : public Base {
UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override;
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override;
const char16_t* typeName() const override { return concreteTypeName; }
protected:
@ -976,7 +976,7 @@ struct Concrete<RootList> : public Base {
template<typename Referent>
class TracerConcrete : public Base {
const char16_t* typeName() const override { return concreteTypeName; }
UniquePtr<EdgeRange> edges(JSContext*, bool wantNames) const override;
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override;
JS::Zone* zone() const override;
protected:
@ -1069,7 +1069,7 @@ template<>
class Concrete<void> : public Base {
const char16_t* typeName() const override;
Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override;
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override;
JS::Zone* zone() const override;
JSCompartment* compartment() const override;
CoarseType coarseType() const final;

View File

@ -78,13 +78,13 @@ template<typename Handler>
struct BreadthFirst {
// Construct a breadth-first traversal object that reports the nodes it
// reaches to |handler|. The traversal object reports OOM on |cx|, and
// asserts that no GC happens in |cx|'s runtime during its lifetime.
// reaches to |handler|. The traversal asserts that no GC happens in its
// runtime during its lifetime.
//
// We do nothing with noGC, other than require it to exist, with a lifetime
// that encloses our own.
BreadthFirst(JSContext* cx, Handler& handler, const JS::AutoCheckCannotGC& noGC)
: wantNames(true), cx(cx), visited(cx), handler(handler), pending(cx),
BreadthFirst(JSRuntime* rt, Handler& handler, const JS::AutoCheckCannotGC& noGC)
: wantNames(true), rt(rt), visited(), handler(handler), pending(),
traversalBegun(false), stopRequested(false), abandonRequested(false)
{ }
@ -126,7 +126,7 @@ struct BreadthFirst {
pending.popFront();
// Get a range containing all origin's outgoing edges.
auto range = origin.edges(cx, wantNames);
auto range = origin.edges(rt, wantNames);
if (!range)
return false;
@ -181,13 +181,14 @@ struct BreadthFirst {
// Other edges *to* that referent will still be traversed.
void abandonReferent() { abandonRequested = true; }
// The context with which we were constructed.
JSContext* cx;
// The runtime with which we were constructed.
JSRuntime* rt;
// A map associating each node N that we have reached with a
// Handler::NodeData, for |handler|'s use. This is public, so that
// |handler| can access it to see the traversal thus far.
typedef js::HashMap<Node, typename Handler::NodeData> NodeMap;
using NodeMap = js::HashMap<Node, typename Handler::NodeData, js::DefaultHasher<Node>,
js::SystemAllocPolicy>;
NodeMap visited;
private:
@ -199,10 +200,10 @@ struct BreadthFirst {
// current population.
template <typename T>
class Queue {
js::Vector<T, 0> head, tail;
js::Vector<T, 0, js::SystemAllocPolicy> head, tail;
size_t frontIndex;
public:
explicit Queue(JSContext* cx) : head(cx), tail(cx), frontIndex(0) { }
Queue() : head(), tail(), frontIndex(0) { }
bool empty() { return frontIndex >= head.length(); }
T& front() {
MOZ_ASSERT(!empty());

View File

@ -2127,9 +2127,9 @@ struct FindPathHandler {
typedef BackEdge NodeData;
typedef JS::ubi::BreadthFirst<FindPathHandler> Traversal;
FindPathHandler(JS::ubi::Node start, JS::ubi::Node target,
FindPathHandler(JSContext*cx, JS::ubi::Node start, JS::ubi::Node target,
AutoValueVector& nodes, Vector<EdgeName>& edges)
: start(start), target(target), foundPath(false),
: cx(cx), start(start), target(target), foundPath(false),
nodes(nodes), edges(edges) { }
bool
@ -2143,7 +2143,7 @@ struct FindPathHandler {
// Record how we reached this node. This is the last edge on a
// shortest path to this node.
EdgeName edgeName = DuplicateString(traversal.cx, edge.name);
EdgeName edgeName = DuplicateString(cx, edge.name);
if (!edgeName)
return false;
*backEdge = mozilla::Move(BackEdge(origin, Move(edgeName)));
@ -2180,6 +2180,8 @@ struct FindPathHandler {
return true;
}
JSContext* cx;
// The node we're starting from.
JS::ubi::Node start;
@ -2238,8 +2240,8 @@ FindPath(JSContext* cx, unsigned argc, Value* vp)
JS::ubi::Node start(args[0]), target(args[1]);
heaptools::FindPathHandler handler(start, target, nodes, edges);
heaptools::FindPathHandler::Traversal traversal(cx, handler, autoCannotGC);
heaptools::FindPathHandler handler(cx, start, target, nodes, edges);
heaptools::FindPathHandler::Traversal traversal(cx->runtime(), handler, autoCannotGC);
if (!traversal.init() || !traversal.addStart(start))
return false;

View File

@ -4543,6 +4543,17 @@ js::DuplicateString(js::ExclusiveContext* cx, const char16_t* s)
return ret;
}
UniquePtr<char16_t[], JS::FreePolicy>
js::DuplicateString(const char16_t* s)
{
size_t n = js_strlen(s) + 1;
UniquePtr<char16_t[], JS::FreePolicy> ret(js_pod_malloc<char16_t>(n));
if (!ret)
return nullptr;
PodCopy(ret.get(), s, n);
return ret;
}
template <typename CharT>
const CharT*
js_strchr_limit(const CharT* s, char16_t c, const CharT* limit)

View File

@ -129,6 +129,11 @@ DuplicateString(ExclusiveContext* cx, const char* s);
extern mozilla::UniquePtr<char16_t[], JS::FreePolicy>
DuplicateString(ExclusiveContext* cx, const char16_t* s);
// This variant does not report OOMs, you must arrange for OOMs to be reported
// yourself.
extern mozilla::UniquePtr<char16_t[], JS::FreePolicy>
DuplicateString(const char16_t* s);
/*
* Convert a non-string value to a string, returning null after reporting an
* error, otherwise returning a new string reference.

View File

@ -4049,13 +4049,17 @@ class MOZ_STACK_CLASS Debugger::ObjectQuery
*/
Maybe<JS::AutoCheckCannotGC> maybeNoGC;
RootedObject dbgObj(cx, dbg->object);
JS::ubi::RootList rootList(cx, maybeNoGC);
if (!rootList.init(dbgObj))
JS::ubi::RootList rootList(cx->runtime(), maybeNoGC);
if (!rootList.init(dbgObj)) {
ReportOutOfMemory(cx);
return false;
}
Traversal traversal(cx, *this, maybeNoGC.ref());
if (!traversal.init())
Traversal traversal(cx->runtime(), *this, maybeNoGC.ref());
if (!traversal.init()) {
ReportOutOfMemory(cx);
return false;
}
traversal.wantNames = false;
return traversal.addStart(JS::ubi::Node(&rootList)) &&

View File

@ -540,18 +540,23 @@ DebuggerMemory::takeCensus(JSContext* cx, unsigned argc, Value* vp)
{
Maybe<JS::AutoCheckCannotGC> maybeNoGC;
JS::ubi::RootList rootList(cx, maybeNoGC);
if (!rootList.init(dbgObj))
JS::ubi::RootList rootList(cx->runtime(), maybeNoGC);
if (!rootList.init(dbgObj)) {
ReportOutOfMemory(cx);
return false;
}
JS::ubi::CensusTraversal traversal(cx, handler, maybeNoGC.ref());
if (!traversal.init())
JS::ubi::CensusTraversal traversal(cx->runtime(), handler, maybeNoGC.ref());
if (!traversal.init()) {
ReportOutOfMemory(cx);
return false;
}
traversal.wantNames = false;
if (!traversal.addStart(JS::ubi::Node(&rootList)) ||
!traversal.traverse())
{
ReportOutOfMemory(cx);
return false;
}
}

View File

@ -150,7 +150,7 @@ JS::Zone* Concrete<void>::zone() const { MOZ_CRASH("null ubi::Node")
JSCompartment* Concrete<void>::compartment() const { MOZ_CRASH("null ubi::Node"); }
UniquePtr<EdgeRange>
Concrete<void>::edges(JSContext*, bool) const {
Concrete<void>::edges(JSRuntime*, bool) const {
MOZ_CRASH("null ubi::Node");
}
@ -255,8 +255,8 @@ class EdgeVectorTracer : public JS::CallbackTracer {
// True if no errors (OOM, say) have yet occurred.
bool okay;
EdgeVectorTracer(JSContext* cx, EdgeVector* vec, bool wantNames)
: JS::CallbackTracer(JS_GetRuntime(cx)),
EdgeVectorTracer(JSRuntime* rt, EdgeVector* vec, bool wantNames)
: JS::CallbackTracer(rt),
vec(vec),
wantNames(wantNames),
okay(true)
@ -275,10 +275,10 @@ class SimpleEdgeRange : public EdgeRange {
}
public:
explicit SimpleEdgeRange(JSContext* cx) : edges(cx), i(0) { }
explicit SimpleEdgeRange() : edges(), i(0) { }
bool init(JSContext* cx, void* thing, JS::TraceKind kind, bool wantNames = true) {
EdgeVectorTracer tracer(cx, &edges, wantNames);
bool init(JSRuntime* rt, void* thing, JS::TraceKind kind, bool wantNames = true) {
EdgeVectorTracer tracer(rt, &edges, wantNames);
js::TraceChildren(&tracer, thing, kind);
settle();
return tracer.okay;
@ -297,13 +297,12 @@ TracerConcrete<Referent>::zone() const
template<typename Referent>
UniquePtr<EdgeRange>
TracerConcrete<Referent>::edges(JSContext* cx, bool wantNames) const {
UniquePtr<SimpleEdgeRange, JS::DeletePolicy<SimpleEdgeRange>> range(
cx->new_<SimpleEdgeRange>(cx));
TracerConcrete<Referent>::edges(JSRuntime* rt, bool wantNames) const {
UniquePtr<SimpleEdgeRange, JS::DeletePolicy<SimpleEdgeRange>> range(js_new<SimpleEdgeRange>());
if (!range)
return nullptr;
if (!range->init(cx, ptr, JS::MapTypeToTraceKind<Referent>::kind, wantNames))
if (!range->init(rt, ptr, JS::MapTypeToTraceKind<Referent>::kind, wantNames))
return nullptr;
return UniquePtr<EdgeRange>(range.release());
@ -395,10 +394,10 @@ template class TracerConcrete<js::ObjectGroup>;
namespace JS {
namespace ubi {
RootList::RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames /* = false */)
RootList::RootList(JSRuntime* rt, Maybe<AutoCheckCannotGC>& noGC, bool wantNames /* = false */)
: noGC(noGC),
cx(cx),
edges(cx),
rt(rt),
edges(),
wantNames(wantNames)
{ }
@ -406,19 +405,19 @@ RootList::RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames
bool
RootList::init()
{
EdgeVectorTracer tracer(cx, &edges, wantNames);
EdgeVectorTracer tracer(rt, &edges, wantNames);
JS_TraceRuntime(&tracer);
if (!tracer.okay)
return false;
noGC.emplace(cx->runtime());
noGC.emplace(rt);
return true;
}
bool
RootList::init(ZoneSet& debuggees)
{
EdgeVector allRootEdges(cx);
EdgeVectorTracer tracer(cx, &allRootEdges, wantNames);
EdgeVector allRootEdges;
EdgeVectorTracer tracer(rt, &allRootEdges, wantNames);
JS_TraceRuntime(&tracer);
if (!tracer.okay)
@ -436,7 +435,7 @@ RootList::init(ZoneSet& debuggees)
return false;
}
noGC.emplace(cx->runtime());
noGC.emplace(rt);
return true;
}
@ -478,7 +477,7 @@ RootList::addRoot(Node node, const char16_t* edgeName)
UniquePtr<char16_t[], JS::FreePolicy> name;
if (edgeName) {
name = DuplicateString(cx, edgeName);
name = DuplicateString(edgeName);
if (!name)
return false;
}
@ -489,9 +488,9 @@ RootList::addRoot(Node node, const char16_t* edgeName)
const char16_t Concrete<RootList>::concreteTypeName[] = MOZ_UTF16("RootList");
UniquePtr<EdgeRange>
Concrete<RootList>::edges(JSContext* cx, bool wantNames) const {
Concrete<RootList>::edges(JSRuntime* rt, bool wantNames) const {
MOZ_ASSERT_IF(wantNames, get().wantNames);
return UniquePtr<EdgeRange>(cx->new_<PreComputedEdgeRange>(cx, get().edges));
return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
}
} // namespace ubi