diff --git a/js/ductwork/debugger/tests/test_nativewrappers.js b/js/ductwork/debugger/tests/test_nativewrappers.js index ccddb1e84548..094f990ae204 100644 --- a/js/ductwork/debugger/tests/test_nativewrappers.js +++ b/js/ductwork/debugger/tests/test_nativewrappers.js @@ -34,5 +34,5 @@ function run_test() g.stopMe(doc.createEvent("MouseEvent")); } + ")()"); - dbg.removeAllDebuggees(); + dbg.enabled = false; } diff --git a/js/src/debugger/Debugger.cpp b/js/src/debugger/Debugger.cpp index f58bd7de3bdf..d1157a96c522 100644 --- a/js/src/debugger/Debugger.cpp +++ b/js/src/debugger/Debugger.cpp @@ -316,7 +316,9 @@ Breakpoint::Breakpoint(Debugger* debugger, BreakpointSite* site, void Breakpoint::destroy(FreeOp* fop, MayDestroySite mayDestroySite /* true */) { - site->dec(fop); + if (debugger->enabled) { + site->dec(fop); + } debugger->breakpoints.remove(this); site->breakpoints.remove(this); gc::Cell* cell = site->owningCellUnbarriered(); @@ -371,6 +373,7 @@ Debugger::Debugger(JSContext* cx, NativeObject* dbg) : object(dbg), debuggees(cx->zone()), uncaughtExceptionHook(nullptr), + enabled(true), allowUnobservedAsmJS(false), collectCoverageInfo(false), observedGCs(cx->zone()), @@ -623,8 +626,8 @@ static bool DebuggerExists(GlobalObject* global, /* static */ bool Debugger::hasLiveHook(GlobalObject* global, Hook which) { return DebuggerExists(global, [=](Debugger* dbg) { - return dbg->getHook(which); - }); + return dbg->enabled && dbg->getHook(which); + }); } /* static */ @@ -665,6 +668,10 @@ JSObject* Debugger::getHook(Hook hook) const { } bool Debugger::hasAnyLiveHooks(JSRuntime* rt) const { + if (!enabled) { + return false; + } + // 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. @@ -903,7 +910,7 @@ bool DebugAPI::slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, Debugger* dbg = Debugger::fromChildJSObject(frameobj); EnterDebuggeeNoExecute nx(cx, *dbg, adjqi); - if (frameobj->isLive() && frameobj->onPopHandler()) { + if (dbg->enabled && frameobj->isLive() && frameobj->onPopHandler()) { OnPopHandler* handler = frameobj->onPopHandler(); Maybe ar; @@ -2193,7 +2200,7 @@ ResumeMode Debugger::dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled, if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) { for (auto p = debuggers->begin(); p != debuggers->end(); p++) { Debugger* dbg = *p; - if (hookIsEnabled(dbg)) { + if (dbg->enabled && hookIsEnabled(dbg)) { if (!triggered.append(ObjectValue(*dbg->toJSObject()))) { return ResumeMode::Terminate; } @@ -2214,7 +2221,7 @@ ResumeMode Debugger::dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled, for (Value* p = triggered.begin(); p != triggered.end(); p++) { Debugger* dbg = Debugger::fromJSObject(&p->toObject()); EnterDebuggeeNoExecute nx(cx, *dbg, adjqi); - if (dbg->debuggees.has(global) && hookIsEnabled(dbg)) { + if (dbg->debuggees.has(global) && dbg->enabled && hookIsEnabled(dbg)) { ResumeMode resumeMode = fireHook(dbg); adjqi.runJobs(); if (resumeMode != ResumeMode::Continue) { @@ -2326,8 +2333,8 @@ ResumeMode DebugAPI::onTrap(JSContext* cx, MutableHandleValue vp) { continue; } - // There are two reasons we have to check whether dbg is debugging - // global. + // There are two reasons we have to check whether dbg is enabled and + // debugging global. // // One is just that one breakpoint handler can disable other Debuggers // or remove debuggees. @@ -2336,7 +2343,8 @@ ResumeMode DebugAPI::onTrap(JSContext* cx, MutableHandleValue vp) { // specific global--until they are executed. Only now do we know which // global the script is running against. Debugger* dbg = bp->debugger; - if (dbg->debuggees.has(global)) { + bool hasDebuggee = dbg->enabled && dbg->debuggees.has(global); + if (hasDebuggee) { Maybe ar; ar.emplace(cx, dbg->object); EnterDebuggeeNoExecute nx(cx, *dbg, adjqi); @@ -2635,7 +2643,7 @@ Maybe DebugAPI::allocationSamplingProbability(GlobalObject* global) { // this is safe as long as dbgp does not escape. Debugger* dbgp = p->unbarrieredGet(); - if (dbgp->trackingAllocationSites) { + if (dbgp->trackingAllocationSites && dbgp->enabled) { foundAnyDebuggers = true; probability = std::max(dbgp->allocationSamplingProbability, probability); } @@ -2671,7 +2679,7 @@ bool DebugAPI::slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, // such that the vector gets reallocated. MOZ_ASSERT(dbgs.begin() == begin); - if ((*dbgp)->trackingAllocationSites && + if ((*dbgp)->trackingAllocationSites && (*dbgp)->enabled && !(*dbgp)->appendAllocationSite(cx, obj, frame, when)) { return false; } @@ -2689,7 +2697,7 @@ bool Debugger::isDebuggeeUnbarriered(const Realm* realm) const { bool Debugger::appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame, mozilla::TimeStamp when) { - MOZ_ASSERT(trackingAllocationSites); + MOZ_ASSERT(trackingAllocationSites && enabled); AutoRealm ar(cx, object); RootedObject wrappedFrame(cx, frame); @@ -3216,21 +3224,21 @@ bool Debugger::hookObservesAllExecution(Hook which) { } Debugger::IsObserving Debugger::observesAllExecution() const { - if (!!getHook(OnEnterFrame)) { + if (enabled && !!getHook(OnEnterFrame)) { return Observing; } return NotObserving; } Debugger::IsObserving Debugger::observesAsmJS() const { - if (!allowUnobservedAsmJS) { + if (enabled && !allowUnobservedAsmJS) { return Observing; } return NotObserving; } Debugger::IsObserving Debugger::observesCoverage() const { - if (collectCoverageInfo) { + if (enabled && collectCoverageInfo) { return Observing; } return NotObserving; @@ -3347,7 +3355,7 @@ bool DebugAPI::isObservedByDebuggerTrackingAllocations( // Use unbarrieredGet() to prevent triggering read barrier while // collecting, this is safe as long as dbg does not escape. Debugger* dbg = p->unbarrieredGet(); - if (dbg->trackingAllocationSites) { + if (dbg->trackingAllocationSites && dbg->enabled) { return true; } } @@ -3754,6 +3762,71 @@ static Debugger* Debugger_fromThisValue(JSContext* cx, const CallArgs& args, Debugger* dbg = Debugger_fromThisValue(cx, args, fnname); \ if (!dbg) return false +/* static */ +bool Debugger::getEnabled(JSContext* cx, unsigned argc, Value* vp) { + THIS_DEBUGGER(cx, argc, vp, "get enabled", args, dbg); + args.rval().setBoolean(dbg->enabled); + return true; +} + +/* static */ +bool Debugger::setEnabled(JSContext* cx, unsigned argc, Value* vp) { + THIS_DEBUGGER(cx, argc, vp, "set enabled", args, dbg); + if (!args.requireAtLeast(cx, "Debugger.set enabled", 1)) { + return false; + } + + bool wasEnabled = dbg->enabled; + dbg->enabled = ToBoolean(args[0]); + + if (wasEnabled != dbg->enabled) { + if (dbg->trackingAllocationSites) { + if (wasEnabled) { + dbg->removeAllocationsTrackingForAllDebuggees(); + } else { + if (!dbg->addAllocationsTrackingForAllDebuggees(cx)) { + dbg->enabled = false; + return false; + } + } + } + + for (Breakpoint* bp = dbg->firstBreakpoint(); bp; + bp = bp->nextInDebugger()) { + if (!wasEnabled) { + bp->site->inc(cx->runtime()->defaultFreeOp()); + } else { + bp->site->dec(cx->runtime()->defaultFreeOp()); + } + } + + // Add or remove ourselves from the runtime's list of Debuggers + // that care about new globals. + if (dbg->getHook(OnNewGlobalObject)) { + if (!wasEnabled) { + cx->runtime()->onNewGlobalObjectWatchers().pushBack(dbg); + } else { + cx->runtime()->onNewGlobalObjectWatchers().remove(dbg); + } + } + + // Ensure the compartment is observable if we are re-enabling a + // Debugger with hooks that observe all execution. + if (!dbg->updateObservesAllExecutionOnDebuggees( + cx, dbg->observesAllExecution())) { + return false; + } + + // Note: To toogle code coverage, we currently need to have no live + // stack frame, thus the coverage does not depend on the enabled flag. + + dbg->updateObservesAsmJSOnDebuggees(dbg->observesAsmJS()); + } + + args.rval().setUndefined(); + return true; +} + /* static */ bool Debugger::getHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which) { @@ -3881,11 +3954,13 @@ bool Debugger::setOnNewGlobalObject(JSContext* cx, unsigned argc, Value* vp) { // Add or remove ourselves from the runtime's list of Debuggers that care // about new globals. - JSObject* newHook = dbg->getHook(OnNewGlobalObject); - if (!oldHook && newHook) { - cx->runtime()->onNewGlobalObjectWatchers().pushBack(dbg); - } else if (oldHook && !newHook) { - cx->runtime()->onNewGlobalObjectWatchers().remove(dbg); + if (dbg->enabled) { + JSObject* newHook = dbg->getHook(OnNewGlobalObject); + if (!oldHook && newHook) { + cx->runtime()->onNewGlobalObjectWatchers().pushBack(dbg); + } else if (oldHook && !newHook) { + cx->runtime()->onNewGlobalObjectWatchers().remove(dbg); + } } return true; @@ -4413,13 +4488,13 @@ bool Debugger::addDebuggeeGlobal(JSContext* cx, Handle global) { }); // (5) - if (trackingAllocationSites && + if (trackingAllocationSites && enabled && !Debugger::addAllocationsTracking(cx, global)) { return false; } auto allocationsTrackingGuard = MakeScopeExit([&] { - if (trackingAllocationSites) { + if (trackingAllocationSites && enabled) { Debugger::removeAllocationsTracking(*global); } }); @@ -5846,6 +5921,7 @@ bool Debugger::adoptSource(JSContext* cx, unsigned argc, Value* vp) { } const JSPropertySpec Debugger::properties[] = { + JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0), JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement, Debugger::setOnDebuggerStatement, 0), JS_PSGS("onExceptionUnwind", Debugger::getOnExceptionUnwind, @@ -6038,13 +6114,16 @@ bool Debugger::observesFrame(const FrameIter& iter) const { } bool Debugger::observesScript(JSScript* script) const { + if (!enabled) { + return false; + } // Don't ever observe self-hosted scripts: the Debugger API can break // self-hosted invariants. return observesGlobal(&script->global()) && !script->selfHosted(); } bool Debugger::observesWasm(wasm::Instance* instance) const { - if (!instance->debugEnabled()) { + if (!enabled || !instance->debugEnabled()) { return false; } return observesGlobal(&instance->object()->global()); @@ -7157,7 +7236,7 @@ JS_PUBLIC_API bool FireOnGarbageCollectionHookRequired(JSContext* cx) { AutoCheckCannotGC noGC; for (Debugger* dbg : cx->runtime()->debuggerList()) { - if (dbg->observedGC(cx->runtime()->gc.majorGCCount()) && + if (dbg->enabled && dbg->observedGC(cx->runtime()->gc.majorGCCount()) && dbg->getHook(Debugger::OnGarbageCollection)) { return true; } @@ -7177,7 +7256,7 @@ JS_PUBLIC_API bool FireOnGarbageCollectionHook( AutoCheckCannotGC noGC; for (Debugger* dbg : cx->runtime()->debuggerList()) { - if (dbg->observedGC(data->majorGCNumber()) && + if (dbg->enabled && dbg->observedGC(data->majorGCNumber()) && dbg->getHook(Debugger::OnGarbageCollection)) { if (!triggered.append(dbg->object)) { JS_ReportOutOfMemory(cx); diff --git a/js/src/debugger/Debugger.h b/js/src/debugger/Debugger.h index f25cde4da518..4f3edd0e3bb6 100644 --- a/js/src/debugger/Debugger.h +++ b/js/src/debugger/Debugger.h @@ -243,8 +243,8 @@ extern void CheckDebuggeeThing(JSObject* obj, bool invisibleOk); * not, because they are not deleted when a compartment is no longer a * debuggee: the values need to maintain object identity across add/remove/add * transitions. (Frames are an exception to the rule. Existing Debugger.Frame - * objects are killed if their realm is removed as a debugger; if the realm - * beacomes a debuggee again later, new Frame objects are created.) + * objects are killed when debugging is disabled for their compartment, and if + * it's re-enabled later, new Frame objects are created.) */ template class DebuggerWeakMap @@ -445,6 +445,8 @@ class Debugger : private mozilla::LinkedListElement { return observedGCs.put(majorGCNumber); } + bool isEnabled() const { return enabled; } + static SavedFrame* getObjectAllocationSite(JSObject& obj); struct AllocationsLogEntry { @@ -490,6 +492,7 @@ class Debugger : private mozilla::LinkedListElement { debuggees; /* Debuggee globals. Cross-compartment weak references. */ JS::ZoneSet debuggeeZones; /* Set of zones that we have debuggees in. */ js::GCPtrObject uncaughtExceptionHook; /* Strong reference. */ + bool enabled; bool allowUnobservedAsmJS; // Whether to enable code coverage on the Debuggee. @@ -543,7 +546,7 @@ class Debugger : private mozilla::LinkedListElement { /* * Add allocations tracking for objects allocated within the given * debuggee's compartment. The given debuggee global must be observed by at - * least one Debugger that is tracking allocations. + * least one Debugger that is enabled and tracking allocations. */ static MOZ_MUST_USE bool addAllocationsTracking( JSContext* cx, Handle debuggee); @@ -562,7 +565,7 @@ class Debugger : private mozilla::LinkedListElement { void removeAllocationsTrackingForAllDebuggees(); /* - * If this Debugger has a onNewGlobalObject handler, then + * If this Debugger is enabled, and has a onNewGlobalObject handler, then * this link is inserted into the list headed by * JSRuntime::onNewGlobalObjectWatchers. */ @@ -599,10 +602,9 @@ class Debugger : private mozilla::LinkedListElement { * onEnterFrame handler on resume, and to retain onStep and onPop hooks. * * An entry is present in this table when: - * - both the debuggee generator object and the Debugger.Frame object exists - * - the debuggee generator object belongs to a relam that is a debuggee of - * the Debugger.Frame's owner. - * + * - both the debuggee generator object and the Debugger.Frame object exist + * - the Debugger.Frame's owner is still an enabled debugger of + * the debuggee compartment * regardless of whether the frame is currently suspended. (This list is * meant to explain why we update the table in the particular places where * we do so.) @@ -773,6 +775,8 @@ class Debugger : private mozilla::LinkedListElement { static MOZ_MUST_USE bool setHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which); + static bool getEnabled(JSContext* cx, unsigned argc, Value* vp); + static bool setEnabled(JSContext* cx, unsigned argc, Value* vp); static bool getOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp); static bool setOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp); static bool getOnExceptionUnwind(JSContext* cx, unsigned argc, Value* vp); @@ -1225,7 +1229,7 @@ class BreakpointSite { * site's list. * * GC rules: - * - script is live and breakpoint exists + * - script is live, breakpoint exists, and debugger is enabled * ==> debugger is live * - script is live, breakpoint exists, and debugger is live * ==> retain the breakpoint and the handler object is live @@ -1344,12 +1348,16 @@ js::GCPtrNativeObject& Debugger::toJSObjectRef() { return object; } -bool Debugger::observesEnterFrame() const { return getHook(OnEnterFrame); } +bool Debugger::observesEnterFrame() const { + return enabled && getHook(OnEnterFrame); +} -bool Debugger::observesNewScript() const { return getHook(OnNewScript); } +bool Debugger::observesNewScript() const { + return enabled && getHook(OnNewScript); +} bool Debugger::observesNewGlobalObject() const { - return getHook(OnNewGlobalObject); + return enabled && getHook(OnNewGlobalObject); } bool Debugger::observesGlobal(GlobalObject* global) const { diff --git a/js/src/debugger/DebuggerMemory.cpp b/js/src/debugger/DebuggerMemory.cpp index 1a955feaf8ab..79f6fc1858e3 100644 --- a/js/src/debugger/DebuggerMemory.cpp +++ b/js/src/debugger/DebuggerMemory.cpp @@ -145,6 +145,10 @@ bool DebuggerMemory::setTrackingAllocationSites(JSContext* cx, unsigned argc, dbg->trackingAllocationSites = enabling; + if (!dbg->enabled) { + return undefined(args); + } + if (enabling) { if (!dbg->addAllocationsTrackingForAllDebuggees(cx)) { dbg->trackingAllocationSites = false; @@ -333,7 +337,7 @@ bool DebuggerMemory::setAllocationSamplingProbability(JSContext* cx, // If this is a change any debuggees would observe, have all debuggee // realms recompute their sampling probabilities. - if (dbg->trackingAllocationSites) { + if (dbg->enabled && dbg->trackingAllocationSites) { for (auto r = dbg->debuggees.all(); !r.empty(); r.popFront()) { r.front()->realm()->chooseAllocationSamplingProbability(); } diff --git a/js/src/debugger/NoExecute.cpp b/js/src/debugger/NoExecute.cpp index 39295a1a0abf..026d6aa25cba 100644 --- a/js/src/debugger/NoExecute.cpp +++ b/js/src/debugger/NoExecute.cpp @@ -40,7 +40,8 @@ EnterDebuggeeNoExecute* EnterDebuggeeNoExecute::findInStack(JSContext* cx) { for (EnterDebuggeeNoExecute* it = cx->noExecuteDebuggerTop; it; it = it->prev_) { Debugger& dbg = it->debugger(); - if (!it->unlocked_ && dbg.observesGlobal(debuggee->maybeGlobal())) { + if (!it->unlocked_ && dbg.isEnabled() && + dbg.observesGlobal(debuggee->maybeGlobal())) { return it; } } diff --git a/js/src/debugger/NoExecute.h b/js/src/debugger/NoExecute.h index a88fc7b5b4f8..fbdf91a56430 100644 --- a/js/src/debugger/NoExecute.h +++ b/js/src/debugger/NoExecute.h @@ -19,8 +19,8 @@ namespace js { class LeaveDebuggeeNoExecute; -// Prevents all the debuggeee compartments of a given Debugger from executing -// scripts. Attempts to run script will throw an +// Given a Debugger instance dbg, if it is enabled, prevents all its debuggee +// compartments from executing scripts. Attempts to run script will throw an // instance of Debugger.DebuggeeWouldRun from the topmost locked Debugger's // compartment. class MOZ_RAII EnterDebuggeeNoExecute { diff --git a/js/src/jit-test/tests/binast/lazy/debug/Debugger-allowUnobservedAsmJS-01.binjs b/js/src/jit-test/tests/binast/lazy/debug/Debugger-allowUnobservedAsmJS-01.binjs index 253946f4f018..dfa600300073 100644 Binary files a/js/src/jit-test/tests/binast/lazy/debug/Debugger-allowUnobservedAsmJS-01.binjs and b/js/src/jit-test/tests/binast/lazy/debug/Debugger-allowUnobservedAsmJS-01.binjs differ diff --git a/js/src/jit-test/tests/binast/lazy/debug/Debugger-enabled-01.binjs b/js/src/jit-test/tests/binast/lazy/debug/Debugger-enabled-01.binjs new file mode 100644 index 000000000000..374ac5ee20f2 Binary files /dev/null and b/js/src/jit-test/tests/binast/lazy/debug/Debugger-enabled-01.binjs differ diff --git a/js/src/jit-test/tests/binast/lazy/debug/Debugger-onNewGlobalObject-04.binjs b/js/src/jit-test/tests/binast/lazy/debug/Debugger-onNewGlobalObject-04.binjs index d09b375ecb8e..42d988d31017 100644 Binary files a/js/src/jit-test/tests/binast/lazy/debug/Debugger-onNewGlobalObject-04.binjs and b/js/src/jit-test/tests/binast/lazy/debug/Debugger-onNewGlobalObject-04.binjs differ diff --git a/js/src/jit-test/tests/binast/lazy/debug/Debugger-onNewGlobalObject-07.binjs b/js/src/jit-test/tests/binast/lazy/debug/Debugger-onNewGlobalObject-07.binjs index 934ff55d6fe7..5ab941ed7992 100644 Binary files a/js/src/jit-test/tests/binast/lazy/debug/Debugger-onNewGlobalObject-07.binjs and b/js/src/jit-test/tests/binast/lazy/debug/Debugger-onNewGlobalObject-07.binjs differ diff --git a/js/src/jit-test/tests/binast/lazy/debug/Frame-onPop-disabled.binjs b/js/src/jit-test/tests/binast/lazy/debug/Frame-onPop-disabled.binjs new file mode 100644 index 000000000000..aadce10917bc Binary files /dev/null and b/js/src/jit-test/tests/binast/lazy/debug/Frame-onPop-disabled.binjs differ diff --git a/js/src/jit-test/tests/binast/lazy/debug/Frame-onPop-multiple-03.binjs b/js/src/jit-test/tests/binast/lazy/debug/Frame-onPop-multiple-03.binjs new file mode 100644 index 000000000000..1645664ab6d7 Binary files /dev/null and b/js/src/jit-test/tests/binast/lazy/debug/Frame-onPop-multiple-03.binjs differ diff --git a/js/src/jit-test/tests/binast/lazy/debug/dispatch-02.binjs b/js/src/jit-test/tests/binast/lazy/debug/dispatch-02.binjs new file mode 100644 index 000000000000..733e0988b673 Binary files /dev/null and b/js/src/jit-test/tests/binast/lazy/debug/dispatch-02.binjs differ diff --git a/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-allowUnobservedAsmJS-01.binjs b/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-allowUnobservedAsmJS-01.binjs index 253946f4f018..dfa600300073 100644 Binary files a/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-allowUnobservedAsmJS-01.binjs and b/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-allowUnobservedAsmJS-01.binjs differ diff --git a/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-enabled-01.binjs b/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-enabled-01.binjs new file mode 100644 index 000000000000..90b0ddce81e2 Binary files /dev/null and b/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-enabled-01.binjs differ diff --git a/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-onNewGlobalObject-04.binjs b/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-onNewGlobalObject-04.binjs index b36332f3580d..ff2e0458b48d 100644 Binary files a/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-onNewGlobalObject-04.binjs and b/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-onNewGlobalObject-04.binjs differ diff --git a/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-onNewGlobalObject-07.binjs b/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-onNewGlobalObject-07.binjs index 0bc2e77c4ed2..8167f0b4d0f9 100644 Binary files a/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-onNewGlobalObject-07.binjs and b/js/src/jit-test/tests/binast/nonlazy/debug/Debugger-onNewGlobalObject-07.binjs differ diff --git a/js/src/jit-test/tests/binast/nonlazy/debug/Frame-onPop-disabled.binjs b/js/src/jit-test/tests/binast/nonlazy/debug/Frame-onPop-disabled.binjs new file mode 100644 index 000000000000..6f0e5ebbf1d5 Binary files /dev/null and b/js/src/jit-test/tests/binast/nonlazy/debug/Frame-onPop-disabled.binjs differ diff --git a/js/src/jit-test/tests/binast/nonlazy/debug/Frame-onPop-multiple-03.binjs b/js/src/jit-test/tests/binast/nonlazy/debug/Frame-onPop-multiple-03.binjs new file mode 100644 index 000000000000..55e8af81639b Binary files /dev/null and b/js/src/jit-test/tests/binast/nonlazy/debug/Frame-onPop-multiple-03.binjs differ diff --git a/js/src/jit-test/tests/binast/nonlazy/debug/dispatch-02.binjs b/js/src/jit-test/tests/binast/nonlazy/debug/dispatch-02.binjs new file mode 100644 index 000000000000..57729afb0b32 Binary files /dev/null and b/js/src/jit-test/tests/binast/nonlazy/debug/dispatch-02.binjs differ diff --git a/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js index 51f9d033d50c..afa2589ef48d 100644 --- a/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js +++ b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js @@ -21,6 +21,14 @@ assertEq(asmLink(asmCompile(asmFunStr))(), undefined); g.dbg.allowUnobservedAsmJS = false; assertAsmTypeFail(asmFunStr); +// Disabling the debugger should uninhibit. +g.dbg.enabled = false; +assertEq(asmLink(asmCompile(asmFunStr))(), undefined); + +// Enabling it should inhibit again. +g.dbg.enabled = true; +assertAsmTypeFail(asmFunStr); + // Removing the global should lift the inhibition. g.dbg.removeDebuggee(this); assertEq(asmLink(asmCompile(asmFunStr))(), undefined); diff --git a/js/src/jit-test/tests/debug/Debugger-enabled-01.js b/js/src/jit-test/tests/debug/Debugger-enabled-01.js new file mode 100644 index 000000000000..e542c02a51fd --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-enabled-01.js @@ -0,0 +1,18 @@ +var desc = Object.getOwnPropertyDescriptor(Debugger.prototype, "enabled"); +assertEq(typeof desc.get, 'function'); +assertEq(typeof desc.set, 'function'); + +var g = newGlobal({newCompartment: true}); +var hits; +var dbg = new Debugger(g); +assertEq(dbg.enabled, true); +dbg.onDebuggerStatement = function () { hits++; }; + +var vals = [true, false, null, undefined, NaN, "blah", {}]; +for (var i = 0; i < vals.length; i++) { + dbg.enabled = vals[i]; + assertEq(dbg.enabled, !!vals[i]); + hits = 0; + g.eval("debugger;"); + assertEq(hits, vals[i] ? 1 : 0); +} diff --git a/js/src/jit-test/tests/debug/Debugger-enabled-02.js b/js/src/jit-test/tests/debug/Debugger-enabled-02.js new file mode 100644 index 000000000000..35f073781736 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-enabled-02.js @@ -0,0 +1,17 @@ +// Tests that hooks work if set while the Debugger is disabled. + +var g = newGlobal({newCompartment: true}); +var dbg = new Debugger(g); +var log = ""; + +g.eval("" + function f() { return 42; }); + +dbg.enabled = false; +dbg.onEnterFrame = function (frame) { + log += "1"; +}; +dbg.enabled = true; + +g.f(); + +assertEq(log, "1"); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js index 6e02d19bdce7..096a2deadfe0 100644 --- a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js @@ -12,3 +12,13 @@ dbg.onNewGlobalObject = function (global) { log = ''; newGlobal(); assertEq(log, 'n'); + +log = ''; +dbg.enabled = false; +newGlobal(); +assertEq(log, ''); + +log = ''; +dbg.enabled = true; +newGlobal(); +assertEq(log, 'n'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js index eb79ea4fcce9..15c8568e0209 100644 --- a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js @@ -9,10 +9,12 @@ var hit; function handler(global) { hit++; log += hit; + if (hit == 2) + dbg1.enabled = dbg2.enabled = dbg3.enabled = false; }; log = ''; hit = 0; dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = handler; newGlobal(); -assertEq(log, '123'); +assertEq(log, '12'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js index 9788e027c9e7..cf3626ece035 100644 --- a/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js +++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js @@ -10,4 +10,8 @@ let promisesFound = []; dbg.onNewPromise = p => { promisesFound.push(p); }; let p1 = new g.Promise(function (){}); +dbg.enabled = false; +let p2 = new g.Promise(function (){}); + assertEq(promisesFound.indexOf(gw.makeDebuggeeValue(p1)) != -1, true); +assertEq(promisesFound.indexOf(gw.makeDebuggeeValue(p2)) == -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js index 84f832399796..afb22f4bc405 100644 --- a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js +++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js @@ -16,3 +16,10 @@ g.settlePromiseNow(p); assertEq(log, "s"); assertEq(pw, gw.makeDebuggeeValue(p)); + +log = ""; +dbg.enabled = false; +p = new g.Promise(function (){}); +g.settlePromiseNow(p); + +assertEq(log, ""); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-disabled.js b/js/src/jit-test/tests/debug/Frame-onPop-disabled.js new file mode 100644 index 000000000000..59d6d5903f3d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-disabled.js @@ -0,0 +1,44 @@ +// An onPop handler in a disabled Debugger's frame shouldn't fire. + +var g = newGlobal({newCompartment: true}); +var dbg = new Debugger(g); +g.eval('function f() { debugger; }'); +var log; +dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.callee.name, 'f'); + f.onPop = function handlePop(c) { + log += ')'; + assertEq(dbg.enabled, true); + }; +}; + +var enable; +dbg.onDebuggerStatement = function handleDebugger(f) { + dbg.enabled = enable; +} + + +// This should fire the onEnterFrame and onPop handlers. +log = 'a'; +enable = true; +g.f(); + +// This should fire the onEnterFrame handler, but not the onPop. +log += 'b'; +enable = false; +g.f(); + +// This should fire neither. +log += 'c'; +dbg.enabled = false; +enable = false; +g.f(); + +// This should fire both again. +log += 'd'; +dbg.enabled = true; +enable = true; +g.f(); + +assertEq(log, 'a()b(cd()'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-multiple-03.js b/js/src/jit-test/tests/debug/Frame-onPop-multiple-03.js new file mode 100644 index 000000000000..1bc2dea0bb0b --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-03.js @@ -0,0 +1,36 @@ +// One Debugger's onPop handler can disable another Debugger. +var g = newGlobal({newCompartment: true}); +var dbg1 = new Debugger(g); +var dbg2 = new Debugger(g); + +var log; +var frames = []; +var firstPop = true; + +function handleEnter(frame) { + log += '('; + frames.push(frame); + frame.debugger = this; + frame.onPop = function handlePop(completion) { + log += ')'; + assertEq(completion.return, 42); + if (firstPop) { + // We can't say which frame's onPop handler will get called first. + if (this == frames[0]) + frames[1].debugger.enabled = false; + else + frames[0].debugger.enabled = false; + } else { + assertEq("second pop handler was called", + "second pop handler should not be called"); + } + firstPop = false; + }; +}; + +dbg1.onEnterFrame = handleEnter; +dbg2.onEnterFrame = handleEnter; + +log = ''; +assertEq(g.eval('40 + 2'), 42); +assertEq(log, '(()'); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-12.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-12.js new file mode 100644 index 000000000000..4049f5f96a18 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-12.js @@ -0,0 +1,17 @@ +// Test that disabling the debugger disables allocation tracking. + +load(libdir + "asserts.js"); + +const dbg = new Debugger(); +const root = newGlobal({newCompartment: true}); +dbg.addDebuggee(root); + +dbg.memory.trackingAllocationSites = true; +dbg.enabled = false; + +root.eval("this.alloc = {}"); + +// We shouldn't accumulate allocations in our log while the debugger is +// disabled. +let allocs = dbg.memory.drainAllocationsLog(); +assertEq(allocs.length, 0); diff --git a/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js index 62205b82499e..d29fe6404510 100644 --- a/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js +++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js @@ -74,3 +74,32 @@ test("Setting trackingAllocationSites to true should throw if the debugger " + assertEq(isTrackingAllocations(root1, d1r1), false); assertEq(isTrackingAllocations(root2, d1r2), false); }); + +test("A Debugger isn't tracking allocation sites when disabled.", + () => { + dbg1.memory.trackingAllocationSites = true; + let d1r1 = dbg1.addDebuggee(root1); + + assertEq(isTrackingAllocations(root1, d1r1), true); + dbg1.enabled = false; + assertEq(isTrackingAllocations(root1, d1r1), false); + }); + +test("Re-enabling throws an error if we can't reinstall allocations tracking " + + "for all debuggees.", + () => { + dbg1.enabled = false + dbg1.memory.trackingAllocationSites = true; + let d1r1 = dbg1.addDebuggee(root1); + let d1r2 = dbg1.addDebuggee(root2); + + // Can't install allocation hooks for root2 with this set. + root2.enableShellAllocationMetadataBuilder(); + + assertThrowsInstanceOf(() => dbg1.enabled = true, + Error); + + assertEq(dbg1.enabled, false); + assertEq(isTrackingAllocations(root1, d1r1), false); + assertEq(isTrackingAllocations(root2, d1r2), false); + }); diff --git a/js/src/jit-test/tests/debug/dispatch-02.js b/js/src/jit-test/tests/debug/dispatch-02.js new file mode 100644 index 000000000000..05ebfd6c7edd --- /dev/null +++ b/js/src/jit-test/tests/debug/dispatch-02.js @@ -0,0 +1,21 @@ +// Disabling a Debugger object causes events to stop being delivered to it +// immediately, even if we're in the middle of dispatching. + +var g = newGlobal({newCompartment: true}); +var log; + +var arr = []; +for (var i = 0; i < 4; i++) { + arr[i] = new Debugger(g); + arr[i].num = i; + arr[i].onDebuggerStatement = function () { + log += this.num; + // Disable them all. + for (var j = 0; j < arr.length; j++) + arr[j].enabled = false; + }; +} + +log = ''; +g.eval("debugger; debugger;"); +assertEq(log, '0'); diff --git a/js/src/jit-test/tests/debug/noExecute-04.js b/js/src/jit-test/tests/debug/noExecute-04.js index 628a425bad76..69770d9b22a1 100644 --- a/js/src/jit-test/tests/debug/noExecute-04.js +++ b/js/src/jit-test/tests/debug/noExecute-04.js @@ -22,6 +22,10 @@ var handlers = [() => { g.f(); }, function testHookEnabled(hookName, trigger) { for (var h of handlers) { assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun); + dbg.enabled = false; + h(); + dbg.enabled = true; + assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun); } } diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-04.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-04.js index 00f9d0f3b4fa..6fcc1c6a9895 100644 --- a/js/src/jit-test/tests/debug/onEnterFrame-generator-04.js +++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-04.js @@ -34,7 +34,7 @@ function test(mode, expected) { log += x; `); assertEq(g.log, expected); - dbg.removeDebuggee(g); + dbg.enabled = false; } // We fire onEnterFrame for the initial activation when a generator is first diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-04.js b/js/xpconnect/tests/unit/test_onGarbageCollection-04.js index 72e6d3228497..6cb532d9773c 100644 --- a/js/xpconnect/tests/unit/test_onGarbageCollection-04.js +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-04.js @@ -65,7 +65,7 @@ function run_test() { ok(debuggeree.fired >= 1); ok(fired >= 1); - debuggeree.dbg.removeAllDebuggees(); + debuggeree.dbg.enabled = dbg.enabled = false; do_test_finished(); }); });