mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
76cce7097f
commit
4168d842af
@ -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
|
||||
|
||||
|
@ -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>
|
||||
|
26
js/src/jit-test/tests/debug/Memory-takeCensus-04.js
Normal file
26
js/src/jit-test/tests/debug/Memory-takeCensus-04.js
Normal 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.");
|
14
js/src/jit-test/tests/debug/Memory-takeCensus-05.js
Normal file
14
js/src/jit-test/tests/debug/Memory-takeCensus-05.js
Normal 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.");
|
@ -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;
|
||||
}
|
||||
|
@ -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. */
|
||||
|
||||
/*
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user