зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1592116 - Mark Debuggers with hooks by tracing from the Realm. r=jimb
Differential Revision: https://phabricator.services.mozilla.com/D51226 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
678b770b5c
Коммит
6e176ae4f7
|
@ -105,23 +105,6 @@ class DebugAPI {
|
|||
static inline void traceGeneratorFrame(JSTracer* tracer,
|
||||
AbstractGeneratorObject* generator);
|
||||
|
||||
/*
|
||||
* 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 debugging at least one live compartment, and at least one of the
|
||||
* following is true:
|
||||
* - it has a debugger hook installed
|
||||
* - it has a breakpoint set on a live script
|
||||
* - it has a watchpoint set on a live object.
|
||||
*
|
||||
* DebugAPI::markIteratively handles the last case. If it finds any Debugger
|
||||
* objects that are definitely live but not yet marked, it marks them and
|
||||
* returns true. If not, it returns false.
|
||||
*/
|
||||
static MOZ_MUST_USE bool markIteratively(GCMarker* marker);
|
||||
|
||||
// Trace cross compartment edges in all debuggers relevant to the current GC.
|
||||
static void traceCrossCompartmentEdges(JSTracer* tracer);
|
||||
|
||||
|
@ -131,6 +114,8 @@ class DebugAPI {
|
|||
// Trace debugging information for a JSScript.
|
||||
static void traceDebugScript(JSTracer* trc, JSScript* script);
|
||||
|
||||
static void traceFromRealm(JSTracer* trc, Realm* realm);
|
||||
|
||||
// The garbage collector calls this after everything has been marked, but
|
||||
// before anything has been finalized. We use this to clear Debugger /
|
||||
// debuggee edges at a point where the parties concerned are all still
|
||||
|
|
|
@ -628,11 +628,10 @@ static bool DebuggerExists(
|
|||
// explicitly.
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
Realm::DebuggerVector& debuggers = global->getDebuggers();
|
||||
for (auto p = debuggers.begin(); p != debuggers.end(); p++) {
|
||||
for (Realm::DebuggerVectorEntry& entry : global->getDebuggers()) {
|
||||
// Callbacks should not create new references to the debugger, so don't
|
||||
// use a barrier. This allows this method to be called during GC.
|
||||
if (predicate(p->unbarrieredGet())) {
|
||||
if (predicate(entry.dbg.unbarrieredGet())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -679,7 +678,7 @@ JSObject* Debugger::getHook(Hook hook) const {
|
|||
return v.isUndefined() ? nullptr : &v.toObject();
|
||||
}
|
||||
|
||||
bool Debugger::hasAnyLiveHooks(JSRuntime* rt) const {
|
||||
bool Debugger::hasAnyLiveHooks() const {
|
||||
// A onNewGlobalObject hook does not hold its Debugger live, so its behavior
|
||||
// is nondeterministic. This behavior is not satisfying, but it is at least
|
||||
// documented.
|
||||
|
@ -743,11 +742,11 @@ ResumeMode DebugAPI::slowPathOnResumeFrame(JSContext* cx,
|
|||
// For each debugger, if there is an existing Debugger.Frame object for the
|
||||
// resumed `frame`, update it with the new frame pointer and make sure the
|
||||
// frame is observable.
|
||||
Realm::DebuggerVector& debuggers = frame.global()->getDebuggers();
|
||||
for (Debugger* dbg : debuggers) {
|
||||
if (Debugger::GeneratorWeakMap::Ptr entry =
|
||||
for (Realm::DebuggerVectorEntry& entry : frame.global()->getDebuggers()) {
|
||||
Debugger* dbg = entry.dbg;
|
||||
if (Debugger::GeneratorWeakMap::Ptr generatorEntry =
|
||||
dbg->generatorFrames.lookup(genObj)) {
|
||||
DebuggerFrame* frameObj = &entry->value()->as<DebuggerFrame>();
|
||||
DebuggerFrame* frameObj = &generatorEntry->value()->as<DebuggerFrame>();
|
||||
MOZ_ASSERT(&frameObj->unwrappedGenerator() == genObj);
|
||||
if (!dbg->frames.putNew(frame, frameObj)) {
|
||||
ReportOutOfMemory(cx);
|
||||
|
@ -2218,9 +2217,8 @@ ResumeMode Debugger::dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled,
|
|||
// different compartments--every compartment *except* this one.
|
||||
RootedValueVector triggered(cx);
|
||||
Handle<GlobalObject*> global = cx->global();
|
||||
Realm::DebuggerVector& debuggers = global->getDebuggers();
|
||||
for (auto p = debuggers.begin(); p != debuggers.end(); p++) {
|
||||
Debugger* dbg = *p;
|
||||
for (Realm::DebuggerVectorEntry& entry : global->getDebuggers()) {
|
||||
Debugger* dbg = entry.dbg;
|
||||
if (hookIsEnabled(dbg)) {
|
||||
if (!triggered.append(ObjectValue(*dbg->toJSObject()))) {
|
||||
return ResumeMode::Terminate;
|
||||
|
@ -2495,10 +2493,8 @@ ResumeMode DebugAPI::onSingleStep(JSContext* cx, MutableHandleValue vp) {
|
|||
uint32_t liveStepperCount = 0;
|
||||
uint32_t suspendedStepperCount = 0;
|
||||
JSScript* trappingScript = iter.script();
|
||||
GlobalObject* global = cx->global();
|
||||
Realm::DebuggerVector& debuggers = global->getDebuggers();
|
||||
for (auto p = debuggers.begin(); p != debuggers.end(); p++) {
|
||||
Debugger* dbg = *p;
|
||||
for (Realm::DebuggerVectorEntry& entry : cx->global()->getDebuggers()) {
|
||||
Debugger* dbg = entry.dbg;
|
||||
for (Debugger::FrameMap::Range r = dbg->frames.all(); !r.empty();
|
||||
r.popFront()) {
|
||||
AbstractFramePtr frame = r.front().key();
|
||||
|
@ -2686,7 +2682,8 @@ void DebugAPI::slowPathOnNewGlobalObject(JSContext* cx,
|
|||
void DebugAPI::slowPathNotifyParticipatesInGC(uint64_t majorGCNumber,
|
||||
Realm::DebuggerVector& dbgs) {
|
||||
for (Realm::DebuggerVector::Range r = dbgs.all(); !r.empty(); r.popFront()) {
|
||||
if (!r.front().unbarrieredGet()->debuggeeIsBeingCollected(majorGCNumber)) {
|
||||
if (!r.front().dbg.unbarrieredGet()->debuggeeIsBeingCollected(
|
||||
majorGCNumber)) {
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr,
|
||||
"OOM while notifying observing Debuggers of a GC: The "
|
||||
|
@ -2705,7 +2702,7 @@ Maybe<double> DebugAPI::allocationSamplingProbability(GlobalObject* global) {
|
|||
return Nothing();
|
||||
}
|
||||
|
||||
DebugOnly<WeakHeapPtr<Debugger*>*> begin = dbgs.begin();
|
||||
DebugOnly<Realm::DebuggerVectorEntry*> begin = dbgs.begin();
|
||||
|
||||
double probability = 0;
|
||||
bool foundAnyDebuggers = false;
|
||||
|
@ -2715,7 +2712,7 @@ Maybe<double> DebugAPI::allocationSamplingProbability(GlobalObject* global) {
|
|||
MOZ_ASSERT(dbgs.begin() == begin);
|
||||
// Use unbarrieredGet() to prevent triggering read barrier while collecting,
|
||||
// this is safe as long as dbgp does not escape.
|
||||
Debugger* dbgp = p->unbarrieredGet();
|
||||
Debugger* dbgp = p->dbg.unbarrieredGet();
|
||||
|
||||
if (dbgp->trackingAllocationSites) {
|
||||
foundAnyDebuggers = true;
|
||||
|
@ -2732,7 +2729,7 @@ bool DebugAPI::slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj,
|
|||
mozilla::TimeStamp when,
|
||||
Realm::DebuggerVector& dbgs) {
|
||||
MOZ_ASSERT(!dbgs.empty());
|
||||
mozilla::DebugOnly<WeakHeapPtr<Debugger*>*> begin = dbgs.begin();
|
||||
mozilla::DebugOnly<Realm::DebuggerVectorEntry*> begin = dbgs.begin();
|
||||
|
||||
// Root all the Debuggers while we're iterating over them;
|
||||
// appendAllocationSite calls Compartment::wrap, and thus can GC.
|
||||
|
@ -2742,19 +2739,19 @@ bool DebugAPI::slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj,
|
|||
// Handle), but in this case, we're iterating over a global's list of
|
||||
// Debuggers, and globals only hold their Debuggers weakly.
|
||||
Rooted<GCVector<JSObject*>> activeDebuggers(cx, GCVector<JSObject*>(cx));
|
||||
for (auto dbgp = dbgs.begin(); dbgp < dbgs.end(); dbgp++) {
|
||||
if (!activeDebuggers.append((*dbgp)->object)) {
|
||||
for (auto p = dbgs.begin(); p < dbgs.end(); p++) {
|
||||
if (!activeDebuggers.append(p->dbg->object)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto dbgp = dbgs.begin(); dbgp < dbgs.end(); dbgp++) {
|
||||
for (auto p = dbgs.begin(); p < dbgs.end(); p++) {
|
||||
// The set of debuggers had better not change while we're iterating,
|
||||
// such that the vector gets reallocated.
|
||||
MOZ_ASSERT(dbgs.begin() == begin);
|
||||
|
||||
if ((*dbgp)->trackingAllocationSites &&
|
||||
!(*dbgp)->appendAllocationSite(cx, obj, frame, when)) {
|
||||
if (p->dbg->trackingAllocationSites &&
|
||||
!p->dbg->appendAllocationSite(cx, obj, frame, when)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -3206,12 +3203,10 @@ bool Debugger::updateExecutionObservabilityOfScripts(
|
|||
template <typename FrameFn>
|
||||
/* static */
|
||||
void Debugger::forEachDebuggerFrame(AbstractFramePtr frame, FrameFn fn) {
|
||||
GlobalObject* global = frame.global();
|
||||
Realm::DebuggerVector& debuggers = global->getDebuggers();
|
||||
for (auto p = debuggers.begin(); p != debuggers.end(); p++) {
|
||||
Debugger* dbg = *p;
|
||||
if (FrameMap::Ptr entry = dbg->frames.lookup(frame)) {
|
||||
fn(entry->value());
|
||||
for (Realm::DebuggerVectorEntry& entry : frame.global()->getDebuggers()) {
|
||||
Debugger* dbg = entry.dbg;
|
||||
if (FrameMap::Ptr frameEntry = dbg->frames.lookup(frame)) {
|
||||
fn(frameEntry->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3430,11 +3425,10 @@ bool Debugger::cannotTrackAllocations(const GlobalObject& global) {
|
|||
/* static */
|
||||
bool DebugAPI::isObservedByDebuggerTrackingAllocations(
|
||||
const GlobalObject& debuggee) {
|
||||
auto& v = debuggee.getDebuggers();
|
||||
for (auto p = v.begin(); p != v.end(); p++) {
|
||||
for (Realm::DebuggerVectorEntry& entry : debuggee.getDebuggers()) {
|
||||
// Use unbarrieredGet() to prevent triggering read barrier while
|
||||
// collecting, this is safe as long as dbg does not escape.
|
||||
Debugger* dbg = p->unbarrieredGet();
|
||||
Debugger* dbg = entry.dbg.unbarrieredGet();
|
||||
if (dbg->trackingAllocationSites) {
|
||||
return true;
|
||||
}
|
||||
|
@ -3695,9 +3689,8 @@ void DebugAPI::slowPathTraceGeneratorFrame(JSTracer* tracer,
|
|||
return;
|
||||
}
|
||||
|
||||
Realm::DebuggerVector& debuggers = generator->realm()->getDebuggers();
|
||||
for (js::WeakHeapPtr<Debugger*>& dbgWeak : debuggers) {
|
||||
Debugger* dbg = dbgWeak.unbarrieredGet();
|
||||
for (Realm::DebuggerVectorEntry& entry : generator->realm()->getDebuggers()) {
|
||||
Debugger* dbg = entry.dbg.unbarrieredGet();
|
||||
|
||||
if (Debugger::GeneratorWeakMap::Ptr entry =
|
||||
dbg->generatorFrames.lookupUnbarriered(generator)) {
|
||||
|
@ -3713,59 +3706,6 @@ void DebugAPI::slowPathTraceGeneratorFrame(JSTracer* tracer,
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This method has two tasks:
|
||||
* 1. Mark Debugger objects that are unreachable except for debugger hooks
|
||||
* that may yet be called.
|
||||
* 2. Mark breakpoint handlers.
|
||||
*
|
||||
* This happens during the iterative part of the GC mark phase. This method
|
||||
* returns true if it has to mark anything; GC calls it repeatedly until it
|
||||
* returns false.
|
||||
*/
|
||||
/* static */
|
||||
bool DebugAPI::markIteratively(GCMarker* marker) {
|
||||
MOZ_ASSERT(JS::RuntimeHeapIsCollecting(),
|
||||
"This method should be called during GC.");
|
||||
bool markedAny = false;
|
||||
|
||||
// Find all Debugger objects in danger of GC. This code is a little
|
||||
// convoluted since the easiest way to find them is via their debuggees.
|
||||
JSRuntime* rt = marker->runtime();
|
||||
for (RealmsIter r(rt); !r.done(); r.next()) {
|
||||
if (r->isDebuggee()) {
|
||||
GlobalObject* global = r->unsafeUnbarrieredMaybeGlobal();
|
||||
if (!IsMarkedUnbarriered(rt, &global)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Realm::DebuggerVector& debuggers = global->getDebuggers();
|
||||
for (auto p = debuggers.begin(); p != debuggers.end(); p++) {
|
||||
Debugger* dbg = p->unbarrieredGet();
|
||||
|
||||
// dbg is a Debugger with at least one debuggee. Check three things:
|
||||
// - dbg is actually in a compartment that is being marked
|
||||
// - it isn't already marked
|
||||
// - it actually has hooks that might be called
|
||||
GCPtrNativeObject& dbgobj = dbg->toJSObjectRef();
|
||||
if (!dbgobj->zone()->isGCMarking()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool dbgMarked = IsMarked(rt, &dbgobj);
|
||||
if (!dbgMarked && dbg->hasAnyLiveHooks(rt)) {
|
||||
// obj could be reachable only via its live, enabled
|
||||
// debugger hooks, which may yet be called.
|
||||
TraceEdge(marker, &dbgobj, "Debugger with live hooks");
|
||||
markedAny = true;
|
||||
dbgMarked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return markedAny;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void DebugAPI::traceAllForMovingGC(JSTracer* trc) {
|
||||
JSRuntime* rt = trc->runtime();
|
||||
|
@ -3819,6 +3759,13 @@ void Debugger::trace(JSTracer* trc) {
|
|||
forEachWeakMap([trc](auto& weakMap) { weakMap.trace(trc); });
|
||||
}
|
||||
|
||||
/* static */
|
||||
void DebugAPI::traceFromRealm(JSTracer* trc, Realm* realm) {
|
||||
for (Realm::DebuggerVectorEntry& entry : realm->getDebuggers()) {
|
||||
TraceEdge(trc, &entry.debuggerLink, "realm debugger");
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void DebugAPI::sweepAll(JSFreeOp* fop) {
|
||||
JSRuntime* rt = fop->runtime();
|
||||
|
@ -3873,8 +3820,8 @@ void Debugger::detachAllDebuggersFromGlobal(JSFreeOp* fop,
|
|||
const Realm::DebuggerVector& debuggers = global->getDebuggers();
|
||||
MOZ_ASSERT(!debuggers.empty());
|
||||
while (!debuggers.empty()) {
|
||||
debuggers.back()->removeDebuggeeGlobal(fop, global, nullptr,
|
||||
Debugger::FromSweep::No);
|
||||
debuggers.back().dbg->removeDebuggeeGlobal(fop, global, nullptr,
|
||||
Debugger::FromSweep::No);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4074,6 +4021,14 @@ bool Debugger::setHookImpl(JSContext* cx, const CallArgs& args, Debugger& dbg,
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Rooted<DebuggerDebuggeeLink*> debuggeeLink(cx, dbg.getDebuggeeLink());
|
||||
if (dbg.hasAnyLiveHooks()) {
|
||||
debuggeeLink->setLinkSlot(dbg);
|
||||
} else {
|
||||
debuggeeLink->clearLinkSlot();
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
@ -4499,6 +4454,13 @@ bool Debugger::construct(JSContext* cx, unsigned argc, Value* vp) {
|
|||
}
|
||||
obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, NullValue());
|
||||
|
||||
RootedNativeObject livenessLink(
|
||||
cx, NewObjectWithGivenProto<DebuggerDebuggeeLink>(cx, nullptr));
|
||||
if (!livenessLink) {
|
||||
return false;
|
||||
}
|
||||
obj->setReservedSlot(JSSLOT_DEBUG_DEBUGGEE_LINK, ObjectValue(*livenessLink));
|
||||
|
||||
Debugger* debugger;
|
||||
{
|
||||
// Construct the underlying C++ object.
|
||||
|
@ -4567,9 +4529,8 @@ bool Debugger::addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> global) {
|
|||
// Find all realms containing debuggers debugging realm's global object.
|
||||
// Add those realms to visited.
|
||||
if (realm->isDebuggee()) {
|
||||
Realm::DebuggerVector& v = realm->getDebuggers();
|
||||
for (auto p = v.begin(); p != v.end(); p++) {
|
||||
Realm* next = (*p)->object->realm();
|
||||
for (Realm::DebuggerVectorEntry& entry : realm->getDebuggers()) {
|
||||
Realm* next = entry.dbg->object->realm();
|
||||
if (std::find(visited.begin(), visited.end(), next) == visited.end()) {
|
||||
if (!visited.append(next)) {
|
||||
return false;
|
||||
|
@ -4593,9 +4554,14 @@ bool Debugger::addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> global) {
|
|||
AutoRealm ar(cx, global);
|
||||
Zone* zone = global->zone();
|
||||
|
||||
RootedObject debuggeeLink(cx, getDebuggeeLink());
|
||||
if (!cx->compartment()->wrap(cx, &debuggeeLink)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// (1)
|
||||
auto& globalDebuggers = global->getDebuggers();
|
||||
if (!globalDebuggers.append(this)) {
|
||||
if (!globalDebuggers.append(Realm::DebuggerVectorEntry(this, debuggeeLink))) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
@ -4665,7 +4631,7 @@ template <typename T, typename AP>
|
|||
static T* findDebuggerInVector(Debugger* dbg, Vector<T, 0, AP>* vec) {
|
||||
T* p;
|
||||
for (p = vec->begin(); p != vec->end(); p++) {
|
||||
if (*p == dbg) {
|
||||
if (p->dbg == dbg) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -6409,6 +6375,23 @@ void Debugger::removeFromFrameMapsAndClearBreakpointsIn(JSContext* cx,
|
|||
}
|
||||
}
|
||||
|
||||
DebuggerDebuggeeLink* Debugger::getDebuggeeLink() {
|
||||
return &object->getReservedSlot(JSSLOT_DEBUG_DEBUGGEE_LINK)
|
||||
.toObject()
|
||||
.as<DebuggerDebuggeeLink>();
|
||||
}
|
||||
|
||||
void DebuggerDebuggeeLink::setLinkSlot(Debugger& dbg) {
|
||||
setReservedSlot(DEBUGGER_LINK_SLOT, ObjectValue(*dbg.toJSObject()));
|
||||
}
|
||||
|
||||
void DebuggerDebuggeeLink::clearLinkSlot() {
|
||||
setReservedSlot(DEBUGGER_LINK_SLOT, UndefinedValue());
|
||||
}
|
||||
|
||||
const JSClass DebuggerDebuggeeLink::class_ = {
|
||||
"DebuggerDebuggeeLink", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS)};
|
||||
|
||||
/* static */
|
||||
bool DebugAPI::handleBaselineOsr(JSContext* cx, InterpreterFrame* from,
|
||||
jit::BaselineFrame* to) {
|
||||
|
|
|
@ -92,6 +92,7 @@ class DebuggerSource;
|
|||
class DebuggerMemory;
|
||||
class ScriptedOnStepHandler;
|
||||
class ScriptedOnPopHandler;
|
||||
class DebuggerDebuggeeLink;
|
||||
|
||||
/**
|
||||
* A completion value, describing how some sort of JavaScript evaluation
|
||||
|
@ -483,6 +484,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger> {
|
|||
JSSLOT_DEBUG_HOOK_START = JSSLOT_DEBUG_PROTO_STOP,
|
||||
JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount,
|
||||
JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP,
|
||||
JSSLOT_DEBUG_DEBUGGEE_LINK,
|
||||
JSSLOT_DEBUG_COUNT
|
||||
};
|
||||
|
||||
|
@ -915,7 +917,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger> {
|
|||
void updateObservesAsmJSOnDebuggees(IsObserving observing);
|
||||
|
||||
JSObject* getHook(Hook hook) const;
|
||||
bool hasAnyLiveHooks(JSRuntime* rt) const;
|
||||
bool hasAnyLiveHooks() const;
|
||||
|
||||
static void slowPathPromiseHook(JSContext* cx, Hook hook,
|
||||
Handle<PromiseObject*> promise);
|
||||
|
@ -1149,11 +1151,57 @@ class Debugger : private mozilla::LinkedListElement<Debugger> {
|
|||
DebuggerSource* wrapWasmSource(JSContext* cx,
|
||||
Handle<WasmInstanceObject*> wasmInstance);
|
||||
|
||||
DebuggerDebuggeeLink* getDebuggeeLink();
|
||||
|
||||
private:
|
||||
Debugger(const Debugger&) = delete;
|
||||
Debugger& operator=(const Debugger&) = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class exists for one specific reason. If a given Debugger object is in
|
||||
* a state where:
|
||||
*
|
||||
* a) nothing in the system has a reference to the object
|
||||
* b) the debugger is currently attached to a live debuggee
|
||||
* c) the debugger has hooks like 'onEnterFrame'
|
||||
*
|
||||
* then we don't want the GC to delete the Debugger, because the system could
|
||||
* still call the hooks. This means we need to ensure that, whenever the global
|
||||
* gets marked, the Debugger will get marked as well. Critically, we _only_
|
||||
* want that to happen if the debugger has hooks. If it doesn't, then GCing
|
||||
* the debugger is the right think to do.
|
||||
*
|
||||
* Note that there are _other_ cases where the debugger may be held live, but
|
||||
* those are not addressed by this case.
|
||||
*
|
||||
* To accomplish this, we use a bit of roundabout link approach. Both the
|
||||
* Debugger and the debuggees can reach the link object:
|
||||
*
|
||||
* Debugger -> DebuggerDebuggeeLink <- CCW <- Debuggee Global #1
|
||||
* | | ^ ^---<- CCW <- Debuggee Global #2
|
||||
* \--<<-optional-<<--/ \------<- CCW <- Debuggee Global #3
|
||||
*
|
||||
* and critically, the Debugger is able to conditionally add or remove the link
|
||||
* going from the DebuggerDebuggeeLink _back_ to the Debugger. When this link
|
||||
* exists, the GC can trace all the way from the global to the Debugger,
|
||||
* meaning that any Debugger with this link will be kept alive as long as any
|
||||
* of its debuggees are alive.
|
||||
*/
|
||||
class DebuggerDebuggeeLink : public NativeObject {
|
||||
private:
|
||||
enum {
|
||||
DEBUGGER_LINK_SLOT,
|
||||
RESERVED_SLOTS,
|
||||
};
|
||||
|
||||
public:
|
||||
static const JSClass class_;
|
||||
|
||||
void setLinkSlot(Debugger& dbg);
|
||||
void clearLinkSlot();
|
||||
};
|
||||
|
||||
/*
|
||||
* A Handler represents a Debugger API reflection object's handler function,
|
||||
* like a Debugger.Frame's onStep handler. These handler functions are called by
|
||||
|
|
|
@ -4176,7 +4176,6 @@ void GCRuntime::markWeakReferences(gcstats::PhaseKind phase) {
|
|||
markedAny |= WeakMapBase::markZoneIteratively(zone, &marker);
|
||||
}
|
||||
}
|
||||
markedAny |= DebugAPI::markIteratively(&marker);
|
||||
markedAny |= jit::JitRuntime::MarkJitcodeGlobalTableIteratively(&marker);
|
||||
|
||||
if (!markedAny) {
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
|
||||
using namespace js;
|
||||
|
||||
Realm::DebuggerVectorEntry::DebuggerVectorEntry(js::Debugger* dbg_,
|
||||
JSObject* link)
|
||||
: dbg(dbg_), debuggerLink(link) {}
|
||||
|
||||
ObjectRealm::ObjectRealm(JS::Zone* zone)
|
||||
: innerViews(zone, zone), iteratorCache(zone) {}
|
||||
|
||||
|
@ -269,6 +273,8 @@ void Realm::traceGlobal(JSTracer* trc) {
|
|||
|
||||
savedStacks_.trace(trc);
|
||||
|
||||
DebugAPI::traceFromRealm(trc, this);
|
||||
|
||||
// Atoms are always tenured.
|
||||
if (!JS::RuntimeHeapIsMinorCollecting()) {
|
||||
varNames_.trace(trc);
|
||||
|
|
|
@ -385,8 +385,26 @@ class JS::Realm : public JS::shadow::Realm {
|
|||
unsigned enterRealmDepthIgnoringJit_ = 0;
|
||||
|
||||
public:
|
||||
struct DebuggerVectorEntry {
|
||||
// The debugger relies on iterating through the DebuggerVector to know what
|
||||
// debuggers to notify about certain actions, which it does using this
|
||||
// pointer. We need an explicit Debugger* because the JSObject* from
|
||||
// the DebuggerDebuggeeLink to the Debugger is only set some of the time.
|
||||
// This `Debugger*` pointer itself could also live on the
|
||||
// DebuggerDebuggeeLink itself, but that would then require all of the
|
||||
// places that iterate over the realm's DebuggerVector to also traverse
|
||||
// the CCW which seems like it would be needlessly complicated.
|
||||
js::WeakHeapPtr<js::Debugger*> dbg;
|
||||
|
||||
// This links to the debugger's DebuggerDebuggeeLink object, via a CCW.
|
||||
// Tracing this link from the realm allows the debugger to define
|
||||
// whether pieces of the debugger should be held live by a given realm.
|
||||
js::HeapPtr<JSObject*> debuggerLink;
|
||||
|
||||
DebuggerVectorEntry(js::Debugger* dbg_, JSObject* link);
|
||||
};
|
||||
using DebuggerVector =
|
||||
js::Vector<js::WeakHeapPtr<js::Debugger*>, 0, js::ZoneAllocPolicy>;
|
||||
js::Vector<DebuggerVectorEntry, 0, js::ZoneAllocPolicy>;
|
||||
|
||||
private:
|
||||
DebuggerVector debuggers_;
|
||||
|
|
|
@ -2586,6 +2586,9 @@ GlobalObject* JSRuntime::createSelfHostingGlobal(JSContext* cx) {
|
|||
|
||||
JS::RealmOptions options;
|
||||
options.creationOptions().setNewCompartmentAndZone();
|
||||
// Debugging the selfHosted zone is not supported because CCWs are not
|
||||
// allowed in that zone.
|
||||
options.creationOptions().setInvisibleToDebugger(true);
|
||||
options.behaviors().setDiscardSource(true);
|
||||
|
||||
Realm* realm = NewRealm(cx, nullptr, options);
|
||||
|
|
Загрузка…
Ссылка в новой задаче