diff --git a/js/src/debugger/DebugAPI-inl.h b/js/src/debugger/DebugAPI-inl.h index 7bdffbebea05..ce7b3464fdfb 100644 --- a/js/src/debugger/DebugAPI-inl.h +++ b/js/src/debugger/DebugAPI-inl.h @@ -13,6 +13,21 @@ namespace js { +/* static */ +bool DebugAPI::stepModeEnabled(JSScript* script) { + return script->hasDebugScript() && stepModeEnabledSlow(script); +} + +/* static */ +bool DebugAPI::hasBreakpointsAt(JSScript* script, jsbytecode* pc) { + return script->hasDebugScript() && hasBreakpointsAtSlow(script, pc); +} + +/* static */ +bool DebugAPI::hasAnyBreakpointsOrStepMode(JSScript* script) { + return script->hasDebugScript(); +} + /* static */ void DebugAPI::onNewScript(JSContext* cx, HandleScript script) { // We early return in slowPathOnNewScript for self-hosted scripts, so we can @@ -43,6 +58,15 @@ void DebugAPI::onNewGlobalObject(JSContext* cx, } } +/* static */ +void DebugAPI::notifyParticipatesInGC(GlobalObject* global, + uint64_t majorGCNumber) { + GlobalObject::DebuggerVector* dbgs = global->getDebuggers(); + if (dbgs && !dbgs->empty()) { + slowPathNotifyParticipatesInGC(majorGCNumber, *dbgs); + } +} + /* static */ bool DebugAPI::onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame, @@ -64,7 +88,7 @@ bool DebugAPI::onLeaveFrame(JSContext* cx, AbstractFramePtr frame, frame.isDebuggee()); /* Traps must be cleared from eval frames, see slowPathOnLeaveFrame. */ mozilla::DebugOnly evalTraps = - frame.isEvalFrame() && frame.script()->hasAnyBreakpointsOrStepMode(); + frame.isEvalFrame() && frame.script()->hasDebugScript(); MOZ_ASSERT_IF(evalTraps, frame.isDebuggee()); if (frame.isDebuggee()) { ok = slowPathOnLeaveFrame(cx, frame, pc, ok); @@ -149,6 +173,13 @@ void DebugAPI::onPromiseSettled(JSContext* cx, Handle promise) { } } +/* static */ +void DebugAPI::sweepBreakpoints(FreeOp* fop, JSScript* script) { + if (script->hasDebugScript()) { + sweepBreakpointsSlow(fop, script); + } +} + } // namespace js #endif /* debugger_DebugAPI_inl_h */ diff --git a/js/src/debugger/DebugAPI.h b/js/src/debugger/DebugAPI.h index eec66617e82c..0ce01f7e3c10 100644 --- a/js/src/debugger/DebugAPI.h +++ b/js/src/debugger/DebugAPI.h @@ -65,6 +65,9 @@ enum class ResumeMode { Return, }; +class DebugScript; +class DebuggerVector; + class DebugAPI { public: friend class Debugger; @@ -91,6 +94,19 @@ class DebugAPI { static void traceAllForMovingGC(JSTracer* trc); static void sweepAll(FreeOp* fop); static MOZ_MUST_USE bool findSweepGroupEdges(JSRuntime* rt); + static inline void sweepBreakpoints(FreeOp* fop, JSScript* script); + static void destroyDebugScript(FreeOp* fop, JSScript* script); +#ifdef JSGC_HASH_TABLE_CHECKS + static void checkDebugScriptAfterMovingGC(DebugScript* ds); +#endif + + /*** Methods for querying script breakpoint state. **************************/ + + static inline bool stepModeEnabled(JSScript* script); + + static inline bool hasBreakpointsAt(JSScript* script, jsbytecode* pc); + + static inline bool hasAnyBreakpointsOrStepMode(JSScript* script); /*** Methods for interacting with the runtime. ******************************/ @@ -213,6 +229,15 @@ class DebugAPI { static inline void onPromiseSettled(JSContext* cx, Handle promise); + // Whether any debugger is observing execution in a global. + static bool debuggerObservesAllExecution(GlobalObject* global); + + // Whether any debugger is observing JS execution coverage in a global. + static bool debuggerObservesCoverage(GlobalObject* global); + + // Whether any Debugger is observing asm.js execution in a global. + static bool debuggerObservesAsmJS(GlobalObject* global); + /* * Announce to the debugger that a generator object has been created, * via JSOP_GENERATOR. @@ -238,10 +263,36 @@ class DebugAPI { static bool isObservedByDebuggerTrackingAllocations( const GlobalObject& debuggee); + // Announce to the debugger that a global object is being collected by the + // specified major GC. + static inline void notifyParticipatesInGC(GlobalObject* global, + uint64_t majorGCNumber); + + static mozilla::Maybe allocationSamplingProbability( + GlobalObject* global); + + // Allocate an object which holds a GlobalObject::DebuggerVector. + static JSObject* newGlobalDebuggersHolder(JSContext* cx); + + // Get the GlobalObject::DebuggerVector for an object allocated by + // newGlobalDebuggersObject. + static GlobalObject::DebuggerVector* getGlobalDebuggers(JSObject* holder); + + // Whether any debugger is observing exception unwinds in a realm. + static bool hasExceptionUnwindHook(GlobalObject* global); + + // Whether any debugger is observing debugger statements in a realm. + static bool hasDebuggerStatementHook(GlobalObject* global); + private: + static bool stepModeEnabledSlow(JSScript* script); + static bool hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc); + static void sweepBreakpointsSlow(FreeOp* fop, JSScript* script); static void slowPathOnNewScript(JSContext* cx, HandleScript script); static void slowPathOnNewGlobalObject(JSContext* cx, Handle global); + static void slowPathNotifyParticipatesInGC( + uint64_t majorGCNumber, GlobalObject::DebuggerVector& dbgs); static MOZ_MUST_USE bool slowPathOnLogAllocationSite( JSContext* cx, HandleObject obj, HandleSavedFrame frame, mozilla::TimeStamp when, GlobalObject::DebuggerVector& dbgs); diff --git a/js/src/debugger/DebugScript.cpp b/js/src/debugger/DebugScript.cpp new file mode 100644 index 000000000000..f1defeebe788 --- /dev/null +++ b/js/src/debugger/DebugScript.cpp @@ -0,0 +1,321 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "debugger/DebugScript.h" + +#include "jit/BaselineJIT.h" + +#include "gc/FreeOp-inl.h" +#include "gc/GC-inl.h" +#include "gc/Marking-inl.h" +#include "vm/JSContext-inl.h" +#include "vm/Realm-inl.h" + +namespace js { + +/* static */ +DebugScript* DebugScript::get(JSScript* script) { + MOZ_ASSERT(script->hasDebugScript()); + DebugScriptMap* map = script->realm()->debugScriptMap.get(); + MOZ_ASSERT(map); + DebugScriptMap::Ptr p = map->lookup(script); + MOZ_ASSERT(p); + return p->value().get(); +} + +/* static */ +DebugScript* DebugScript::getOrCreate(JSContext* cx, JSScript* script) { + if (script->hasDebugScript()) { + return get(script); + } + + size_t nbytes = allocSize(script->length()); + UniqueDebugScript debug( + reinterpret_cast(cx->pod_calloc(nbytes))); + if (!debug) { + return nullptr; + } + + /* Create realm's debugScriptMap if necessary. */ + if (!script->realm()->debugScriptMap) { + auto map = cx->make_unique(); + if (!map) { + return nullptr; + } + + script->realm()->debugScriptMap = std::move(map); + } + + DebugScript* borrowed = debug.get(); + if (!script->realm()->debugScriptMap->putNew(script, std::move(debug))) { + ReportOutOfMemory(cx); + return nullptr; + } + + // It is safe to set this: we can't fail after this point. + script->setHasDebugScript(true); + AddCellMemory(script, nbytes, MemoryUse::ScriptDebugScript); + + /* + * Ensure that any Interpret() instances running on this script have + * interrupts enabled. The interrupts must stay enabled until the + * debug state is destroyed. + */ + for (ActivationIterator iter(cx); !iter.done(); ++iter) { + if (iter->isInterpreter()) { + iter->asInterpreter()->enableInterruptsIfRunning(script); + } + } + + return borrowed; +} + +/* static */ +BreakpointSite* DebugScript::getBreakpointSite(JSScript* script, + jsbytecode* pc) { + uint32_t offset = script->pcToOffset(pc); + return script->hasDebugScript() ? get(script)->breakpoints[offset] : nullptr; +} + +/* static */ +BreakpointSite* DebugScript::getOrCreateBreakpointSite(JSContext* cx, + JSScript* script, + jsbytecode* pc) { + AutoRealm ar(cx, script); + + DebugScript* debug = getOrCreate(cx, script); + if (!debug) { + return nullptr; + } + + BreakpointSite*& site = debug->breakpoints[script->pcToOffset(pc)]; + + if (!site) { + site = cx->new_(script, pc); + if (!site) { + return nullptr; + } + debug->numSites++; + AddCellMemory(script, sizeof(JSBreakpointSite), MemoryUse::BreakpointSite); + } + + return site; +} + +/* static */ +void DebugScript::destroyBreakpointSite(FreeOp* fop, JSScript* script, + jsbytecode* pc) { + DebugScript* debug = get(script); + BreakpointSite*& site = debug->breakpoints[script->pcToOffset(pc)]; + MOZ_ASSERT(site); + + size_t size = site->type() == BreakpointSite::Type::JS + ? sizeof(JSBreakpointSite) + : sizeof(WasmBreakpointSite); + fop->delete_(script, site, size, MemoryUse::BreakpointSite); + site = nullptr; + + debug->numSites--; + if (!debug->needed()) { + DebugAPI::destroyDebugScript(fop, script); + } +} + +/* static */ +void DebugScript::clearBreakpointsIn(FreeOp* fop, Realm* realm, + Debugger* dbg, JSObject* handler) { + for (auto script = realm->zone()->cellIter(); !script.done(); + script.next()) { + if (script->realm() == realm && script->hasDebugScript()) { + clearBreakpointsIn(fop, script, dbg, handler); + } + } +} + +/* static */ +void DebugScript::clearBreakpointsIn(FreeOp* fop, JSScript* script, + Debugger* dbg, JSObject* handler) { + if (!script->hasDebugScript()) { + return; + } + + for (jsbytecode* pc = script->code(); pc < script->codeEnd(); pc++) { + BreakpointSite* site = getBreakpointSite(script, pc); + if (site) { + Breakpoint* nextbp; + for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) { + nextbp = bp->nextInSite(); + if ((!dbg || bp->debugger == dbg) && + (!handler || bp->getHandler() == handler)) { + bp->destroy(fop); + } + } + } + } +} + +#ifdef DEBUG +/* static */ +uint32_t DebugScript::getStepperCount(JSScript* script) { + return script->hasDebugScript() ? get(script)->stepperCount : 0; +} +#endif // DEBUG + +/* static */ +bool DebugScript::incrementStepperCount(JSContext* cx, JSScript* script) { + cx->check(script); + MOZ_ASSERT(cx->realm()->isDebuggee()); + + AutoRealm ar(cx, script); + + DebugScript* debug = getOrCreate(cx, script); + if (!debug) { + return false; + } + + debug->stepperCount++; + + if (debug->stepperCount == 1) { + if (script->hasBaselineScript()) { + script->baselineScript()->toggleDebugTraps(script, nullptr); + } + } + + return true; +} + +/* static */ +void DebugScript::decrementStepperCount(FreeOp* fop, JSScript* script) { + DebugScript* debug = get(script); + MOZ_ASSERT(debug); + MOZ_ASSERT(debug->stepperCount > 0); + + debug->stepperCount--; + + if (debug->stepperCount == 0) { + if (script->hasBaselineScript()) { + script->baselineScript()->toggleDebugTraps(script, nullptr); + } + + if (!debug->needed()) { + DebugAPI::destroyDebugScript(fop, script); + } + } +} + +/* static */ +bool DebugScript::incrementGeneratorObserverCount(JSContext* cx, + JSScript* script) { + cx->check(script); + MOZ_ASSERT(cx->realm()->isDebuggee()); + + AutoRealm ar(cx, script); + + DebugScript* debug = getOrCreate(cx, script); + if (!debug) { + return false; + } + + debug->generatorObserverCount++; + + // It is our caller's responsibility, before bumping the generator observer + // count, to make sure that the baseline code includes the necessary + // JS_AFTERYIELD instrumentation by calling + // {ensure,update}ExecutionObservabilityOfScript. + MOZ_ASSERT_IF(script->hasBaselineScript(), + script->baselineScript()->hasDebugInstrumentation()); + + return true; +} + +/* static */ +void DebugScript::decrementGeneratorObserverCount(FreeOp* fop, + JSScript* script) { + DebugScript* debug = get(script); + MOZ_ASSERT(debug); + MOZ_ASSERT(debug->generatorObserverCount > 0); + + debug->generatorObserverCount--; + + if (!debug->needed()) { + DebugAPI::destroyDebugScript(fop, script); + } +} + +/* static */ +void DebugAPI::destroyDebugScript(FreeOp* fop, JSScript* script) { + if (script->hasDebugScript()) { + DebugScriptMap* map = script->realm()->debugScriptMap.get(); + MOZ_ASSERT(map); + DebugScriptMap::Ptr p = map->lookup(script); + MOZ_ASSERT(p); + DebugScript* debug = p->value().release(); + map->remove(p); + script->setHasDebugScript(false); + + fop->free_(script, debug, DebugScript::allocSize(script->length()), + MemoryUse::ScriptDebugScript); + } +} + +#ifdef JSGC_HASH_TABLE_CHECKS +/* static */ +void DebugAPI::checkDebugScriptAfterMovingGC(DebugScript* ds) { + for (uint32_t i = 0; i < ds->numSites; i++) { + BreakpointSite* site = ds->breakpoints[i]; + if (site && site->type() == BreakpointSite::Type::JS) { + CheckGCThingAfterMovingGC(site->asJS()->script); + } + } +} +#endif // JSGC_HASH_TABLE_CHECKS + +/* static */ +void DebugAPI::sweepBreakpointsSlow(FreeOp* fop, JSScript* script) { + bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script); + for (unsigned i = 0; i < script->length(); i++) { + BreakpointSite* site = + DebugScript::getBreakpointSite(script, script->offsetToPC(i)); + if (!site) { + continue; + } + + Breakpoint* nextbp; + for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) { + nextbp = bp->nextInSite(); + GCPtrNativeObject& dbgobj = bp->debugger->toJSObjectRef(); + + // If we are sweeping, then we expect the script and the + // debugger object to be swept in the same sweep group, except + // if the breakpoint was added after we computed the sweep + // groups. In this case both script and debugger object must be + // live. + MOZ_ASSERT_IF(script->zone()->isGCSweeping() && + dbgobj->zone()->isCollecting(), + dbgobj->zone()->isGCSweeping() || + (!scriptGone && dbgobj->asTenured().isMarkedAny())); + + bool dying = scriptGone || IsAboutToBeFinalized(&dbgobj); + MOZ_ASSERT_IF(!dying, !IsAboutToBeFinalized(&bp->getHandlerRef())); + if (dying) { + bp->destroy(fop); + } + } + } +} + +/* static */ +bool DebugAPI::stepModeEnabledSlow(JSScript* script) { + return DebugScript::get(script)->stepperCount > 0; +} + +/* static */ +bool DebugAPI::hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc) { + BreakpointSite* site = DebugScript::getBreakpointSite(script, pc); + return site && site->enabledCount > 0; +} + +} // namespace js diff --git a/js/src/debugger/DebugScript.h b/js/src/debugger/DebugScript.h new file mode 100644 index 000000000000..6e682a252c7f --- /dev/null +++ b/js/src/debugger/DebugScript.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dbg_DebugScript_h +#define dbg_DebugScript_h + +#include "debugger/Debugger.h" + +namespace js { + +// DebugScript manages the internal debugger state for a JSScript, which may be +// associated with multiple Debuggers. +class DebugScript { + friend class DebugAPI; + + /* + * If this is a generator script, this is the number of Debugger.Frames + * referring to calls to this generator, whether live or suspended. Closed + * generators do not contribute a count. + * + * When greater than zero, this script should be compiled with debug + * instrumentation to call Debugger::onResumeFrame at each resumption site, so + * that Debugger can reconnect any extant Debugger.Frames with the new + * concrete frame. + */ + uint32_t generatorObserverCount; + + /* + * The number of Debugger.Frame objects that refer to frames running this + * script and that have onStep handlers. When nonzero, the interpreter and JIT + * must arrange to call Debugger::onSingleStep before each bytecode, or at + * least at some useful granularity. + */ + uint32_t stepperCount; + + /* + * Number of breakpoint sites at opcodes in the script. This is the number + * of populated entries in DebugScript::breakpoints, below. + */ + uint32_t numSites; + + /* + * Breakpoints set in our script. For speed and simplicity, this array is + * parallel to script->code(): the BreakpointSite for the opcode at + * script->code()[offset] is debugScript->breakpoints[offset]. Naturally, + * this array's true length is script->length(). + */ + BreakpointSite* breakpoints[1]; + + /* + * True if this DebugScript carries any useful information. If false, it + * should be removed from its JSScript. + */ + bool needed() const { + return generatorObserverCount > 0 || stepperCount > 0 || numSites > 0; + } + + static size_t allocSize(size_t codeLength) { + return offsetof(DebugScript, breakpoints) + + codeLength * sizeof(BreakpointSite*); + } + + static DebugScript* get(JSScript* script); + static DebugScript* getOrCreate(JSContext* cx, JSScript* script); + + public: + static BreakpointSite* getBreakpointSite(JSScript* script, jsbytecode* pc); + static BreakpointSite* getOrCreateBreakpointSite(JSContext* cx, + JSScript* script, + jsbytecode* pc); + static void destroyBreakpointSite(FreeOp* fop, JSScript* script, + jsbytecode* pc); + + static void clearBreakpointsIn(FreeOp* fop, Realm* realm, + Debugger* dbg, JSObject* handler); + static void clearBreakpointsIn(FreeOp* fop, JSScript* script, + Debugger* dbg, JSObject* handler); + +#ifdef DEBUG + static uint32_t getStepperCount(JSScript* script); +#endif + + /* + * Increment or decrement the single-step count. If the count is non-zero + * then the script is in single-step mode. + * + * Only incrementing is fallible, as it could allocate a DebugScript. + */ + static bool incrementStepperCount(JSContext* cx, JSScript* script); + static void decrementStepperCount(FreeOp* fop, JSScript* script); + + /* + * Increment or decrement the generator observer count. If the count is + * non-zero then the script reports resumptions to the debugger. + * + * Only incrementing is fallible, as it could allocate a DebugScript. + */ + static bool incrementGeneratorObserverCount(JSContext* cx, JSScript* script); + static void decrementGeneratorObserverCount(FreeOp* fop, JSScript* script); +}; + +} /* namespace js */ + +#endif /* dbg_DebugScript_h */ diff --git a/js/src/debugger/Debugger.cpp b/js/src/debugger/Debugger.cpp index 643a0a3d4a01..2e1a1bbed251 100644 --- a/js/src/debugger/Debugger.cpp +++ b/js/src/debugger/Debugger.cpp @@ -18,6 +18,7 @@ #include "builtin/Promise.h" #include "debugger/DebuggerMemory.h" +#include "debugger/DebugScript.h" #include "debugger/Environment.h" #include "debugger/Frame.h" #include "debugger/NoExecute.h" @@ -54,6 +55,7 @@ #include "vm/WrapperObject.h" #include "wasm/WasmInstance.h" +#include "debugger/DebugAPI-inl.h" #include "debugger/Frame-inl.h" #include "debugger/Script-inl.h" #include "gc/GC-inl.h" @@ -332,7 +334,7 @@ Breakpoint* Breakpoint::nextInSite() { return siteLink.mNext; } JSBreakpointSite::JSBreakpointSite(JSScript* script, jsbytecode* pc) : BreakpointSite(Type::JS), script(script), pc(pc) { - MOZ_ASSERT(!script->hasBreakpointsAt(pc)); + MOZ_ASSERT(!DebugAPI::hasBreakpointsAt(script, pc)); } void JSBreakpointSite::recompile(FreeOp* fop) { @@ -343,7 +345,7 @@ void JSBreakpointSite::recompile(FreeOp* fop) { void JSBreakpointSite::destroyIfEmpty(FreeOp* fop) { if (isEmpty()) { - script->destroyBreakpointSite(fop, pc); + DebugScript::destroyBreakpointSite(fop, script, pc); } } @@ -459,6 +461,57 @@ DebuggerMemory& Debugger::memory() const { .as(); } +/*** DebuggerVectorHolder *****************************************************/ + +static void GlobalDebuggerVectorHolder_finalize(FreeOp* fop, JSObject* obj) { + MOZ_ASSERT(fop->maybeOnHelperThread()); + void* ptr = obj->as().getPrivate(); + auto debuggers = static_cast(ptr); + fop->delete_(obj, debuggers, MemoryUse::GlobalDebuggerVector); +} + +static const ClassOps GlobalDebuggerVectorHolder_classOps = { + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + GlobalDebuggerVectorHolder_finalize +}; + +static const Class GlobalDebuggerVectorHolder_class = { + "GlobalDebuggerVectorHolder", + JSCLASS_HAS_PRIVATE | JSCLASS_BACKGROUND_FINALIZE, + &GlobalDebuggerVectorHolder_classOps}; + +/* static */ +JSObject* DebugAPI::newGlobalDebuggersHolder(JSContext* cx) { + NativeObject* obj = + NewNativeObjectWithGivenProto(cx, &GlobalDebuggerVectorHolder_class, + nullptr); + if (!obj) { + return nullptr; + } + + GlobalObject::DebuggerVector* debuggers = + cx->new_(cx->zone()); + if (!debuggers) { + return nullptr; + } + + InitObjectPrivate(obj, debuggers, MemoryUse::GlobalDebuggerVector); + return obj; +} + +/* static */ +GlobalObject::DebuggerVector* DebugAPI::getGlobalDebuggers(JSObject* holder) { + MOZ_ASSERT(holder->getClass() == &GlobalDebuggerVectorHolder_class); + return (GlobalObject::DebuggerVector*)holder->as().getPrivate(); +} + +/*** Debugger accessors *******************************************************/ + bool Debugger::getFrame(JSContext* cx, const FrameIter& iter, MutableHandleValue vp) { RootedDebuggerFrame result(cx); @@ -551,12 +604,17 @@ bool Debugger::getFrame(JSContext* cx, const FrameIter& iter, return true; } -/* static */ -bool Debugger::hasLiveHook(GlobalObject* global, Hook which) { +static bool DebuggerExists(GlobalObject* global, + const std::function& predicate) { + // The GC analysis can't determine that the predicate can't GC, so let it know + // explicitly. + JS::AutoSuppressGCAnalysis nogc; + if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) { for (auto p = debuggers->begin(); p != debuggers->end(); p++) { - Debugger* dbg = *p; - if (dbg->enabled && dbg->getHook(which)) { + // 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())) { return true; } } @@ -564,6 +622,44 @@ bool Debugger::hasLiveHook(GlobalObject* global, Hook which) { return false; } +/* static */ +bool Debugger::hasLiveHook(GlobalObject* global, Hook which) { + return DebuggerExists(global, [=](Debugger* dbg) { + return dbg->enabled && dbg->getHook(which); + }); +} + +/* static */ +bool DebugAPI::debuggerObservesAllExecution(GlobalObject* global) { + return DebuggerExists(global, [=](Debugger* dbg) { + return dbg->observesAllExecution(); + }); +} + +/* static */ +bool DebugAPI::debuggerObservesCoverage(GlobalObject* global) { + return DebuggerExists(global, [=](Debugger* dbg) { + return dbg->observesCoverage(); + }); +} + +/* static */ +bool DebugAPI::debuggerObservesAsmJS(GlobalObject* global) { + return DebuggerExists(global, [=](Debugger* dbg) { + return dbg->observesAsmJS(); + }); +} + +/* static */ +bool DebugAPI::hasExceptionUnwindHook(GlobalObject* global) { + return Debugger::hasLiveHook(global, Debugger::OnExceptionUnwind); +} + +/* static */ +bool DebugAPI::hasDebuggerStatementHook(GlobalObject* global) { + return Debugger::hasLiveHook(global, Debugger::OnDebuggerStatement); +} + JSObject* Debugger::getHook(Hook hook) const { MOZ_ASSERT(hook >= 0 && hook < HookCount); const Value& v = object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + hook); @@ -2197,7 +2293,7 @@ ResumeMode DebugAPI::onTrap(JSContext* cx, MutableHandleValue vp) { isJS = true; pc = iter.pc(); bytecodeOffset = 0; - site = script->getBreakpointSite(pc); + site = DebugScript::getBreakpointSite(script, pc); } else { MOZ_ASSERT(iter.isWasm()); global.set(&iter.wasmInstance()->object()->global()); @@ -2271,7 +2367,7 @@ ResumeMode DebugAPI::onTrap(JSContext* cx, MutableHandleValue vp) { // Calling JS code invalidates site. Reload it. if (isJS) { - site = iter.script()->getBreakpointSite(pc); + site = DebugScript::getBreakpointSite(iter.script(), pc); } else { site = iter.wasmInstance()->debug().getBreakpointSite(cx, bytecodeOffset); @@ -2367,7 +2463,7 @@ ResumeMode DebugAPI::onSingleStep(JSContext* cx, MutableHandleValue vp) { } MOZ_ASSERT(liveStepperCount + suspendedStepperCount == - trappingScript->stepperCount()); + DebugScript::getStepperCount(trappingScript)); } #endif @@ -2509,6 +2605,52 @@ void DebugAPI::slowPathOnNewGlobalObject(JSContext* cx, MOZ_ASSERT(!cx->isExceptionPending()); } +/* static */ +void DebugAPI::slowPathNotifyParticipatesInGC( + uint64_t majorGCNumber, + GlobalObject::DebuggerVector& dbgs) { + for (GlobalObject::DebuggerVector::Range r = dbgs.all(); !r.empty(); + r.popFront()) { + if (!r.front().unbarrieredGet()->debuggeeIsBeingCollected(majorGCNumber)) { +#ifdef DEBUG + fprintf(stderr, + "OOM while notifying observing Debuggers of a GC: The " + "onGarbageCollection\n" + "hook will not be fired for this GC for some Debuggers!\n"); +#endif + return; + } + } +} + +/* static */ +Maybe DebugAPI::allocationSamplingProbability(GlobalObject* global) { + GlobalObject::DebuggerVector* dbgs = global->getDebuggers(); + if (!dbgs || dbgs->empty()) { + return Nothing(); + } + + DebugOnly*> begin = dbgs->begin(); + + double probability = 0; + bool foundAnyDebuggers = false; + 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); + // Use unbarrieredGet() to prevent triggering read barrier while collecting, + // this is safe as long as dbgp does not escape. + Debugger* dbgp = p->unbarrieredGet(); + + if (dbgp->trackingAllocationSites && dbgp->enabled) { + foundAnyDebuggers = true; + probability = std::max(dbgp->allocationSamplingProbability, probability); + } + } + + return foundAnyDebuggers ? Some(probability) : Nothing(); +} + /* static */ bool DebugAPI::slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame, @@ -4158,8 +4300,8 @@ bool Debugger::clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp) { THIS_DEBUGGER(cx, argc, vp, "clearAllBreakpoints", args, dbg); for (WeakGlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) { - r.front()->realm()->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, - nullptr); + DebugScript::clearBreakpointsIn(cx->runtime()->defaultFreeOp(), + r.front()->realm(), dbg, nullptr); } return true; } @@ -6114,8 +6256,8 @@ void Debugger::removeFromFrameMapsAndClearBreakpointsIn(JSContext* cx, // script is about to be destroyed. Remove any breakpoints in it. if (frame.isEvalFrame()) { RootedScript script(cx, frame.script()); - script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), nullptr, - nullptr); + DebugScript::clearBreakpointsIn(cx->runtime()->defaultFreeOp(), script, + nullptr, nullptr); } } diff --git a/js/src/debugger/Debugger.h b/js/src/debugger/Debugger.h index e42d6b9c886d..4f3edd0e3bb6 100644 --- a/js/src/debugger/Debugger.h +++ b/js/src/debugger/Debugger.h @@ -1178,8 +1178,8 @@ class WasmBreakpoint; class WasmBreakpointSite; class BreakpointSite { + friend class DebugAPI; friend class Breakpoint; - friend class ::JSScript; friend class Debugger; public: diff --git a/js/src/debugger/Frame.cpp b/js/src/debugger/Frame.cpp index 536a8d1c94fc..30c457672207 100644 --- a/js/src/debugger/Frame.cpp +++ b/js/src/debugger/Frame.cpp @@ -9,6 +9,7 @@ #include "mozilla/Assertions.h" #include "mozilla/ScopeExit.h" +#include "debugger/DebugScript.h" #include "debugger/Environment.h" #include "debugger/NoExecute.h" #include "debugger/Object.h" @@ -303,7 +304,7 @@ bool DebuggerFrame::setGenerator(JSContext* cx, { AutoRealm ar(cx, script); - if (!script->incrementGeneratorObserverCount(cx)) { + if (!DebugScript::incrementGeneratorObserverCount(cx, script)) { return false; } } @@ -334,11 +335,11 @@ void DebuggerFrame::clearGenerator(FreeOp* fop) { // calls may. HeapPtr& generatorScript = info->generatorScript(); if (!IsAboutToBeFinalized(&generatorScript)) { - generatorScript->decrementGeneratorObserverCount(fop); + DebugScript::decrementGeneratorObserverCount(fop, generatorScript); OnStepHandler* handler = onStepHandler(); if (handler) { - generatorScript->decrementStepperCount(fop); + DebugScript::decrementStepperCount(fop, generatorScript); handler->drop(fop, this); setReservedSlot(ONSTEP_HANDLER_SLOT, UndefinedValue()); } @@ -667,12 +668,13 @@ bool DebuggerFrame::setOnStepHandler(JSContext* cx, HandleDebuggerFrame frame, referent.script())) { return false; } - if (!referent.script()->incrementStepperCount(cx)) { + if (!DebugScript::incrementStepperCount(cx, referent.script())) { return false; } } else if (!handler && prior) { // Single stepping toggled on->off. - referent.script()->decrementStepperCount(cx->runtime()->defaultFreeOp()); + DebugScript::decrementStepperCount(cx->runtime()->defaultFreeOp(), + referent.script()); } } @@ -1008,7 +1010,7 @@ void DebuggerFrame::maybeDecrementFrameScriptStepperCount( instance->debug().decrementStepperCount( fop, frame.asWasmDebugFrame()->funcIndex()); } else { - frame.script()->decrementStepperCount(fop); + DebugScript::decrementStepperCount(fop, frame.script()); } // In the case of generator frames, we may end up trying to clean up the step diff --git a/js/src/debugger/Script.cpp b/js/src/debugger/Script.cpp index 1d653975b1d1..8620c0c3e70d 100644 --- a/js/src/debugger/Script.cpp +++ b/js/src/debugger/Script.cpp @@ -7,6 +7,7 @@ #include "debugger/Script-inl.h" #include "debugger/Debugger.h" +#include "debugger/DebugScript.h" #include "wasm/WasmInstance.h" #include "vm/BytecodeUtil-inl.h" @@ -1749,7 +1750,8 @@ struct DebuggerScript::SetBreakpointMatcher { } jsbytecode* pc = script->offsetToPC(offset_); - BreakpointSite* site = script->getOrCreateBreakpointSite(cx_, pc); + BreakpointSite* site = + DebugScript::getOrCreateBreakpointSite(cx_, script, pc); if (!site) { return false; } @@ -1844,7 +1846,8 @@ bool DebuggerScript::getBreakpoints(JSContext* cx, unsigned argc, Value* vp) { } for (unsigned i = 0; i < script->length(); i++) { - BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i)); + BreakpointSite* site = + DebugScript::getBreakpointSite(script, script->offsetToPC(i)); if (!site) { continue; } @@ -1874,7 +1877,8 @@ class DebuggerScript::ClearBreakpointMatcher { using ReturnType = bool; ReturnType match(HandleScript script) { - script->clearBreakpointsIn(cx_->runtime()->defaultFreeOp(), dbg_, handler_); + DebugScript::clearBreakpointsIn(cx_->runtime()->defaultFreeOp(), script, + dbg_, handler_); return true; } ReturnType match(Handle lazyScript) { diff --git a/js/src/debugger/moz.build b/js/src/debugger/moz.build index e97697396100..385a9d80d4bd 100644 --- a/js/src/debugger/moz.build +++ b/js/src/debugger/moz.build @@ -24,6 +24,7 @@ include('../js-cxxflags.mozbuild') SOURCES = [ 'Debugger.cpp', 'DebuggerMemory.cpp', + 'DebugScript.cpp', 'Environment.cpp', 'Frame.cpp', 'NoExecute.cpp', diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index 898644b5f545..922d6bc4fbb8 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -8,7 +8,6 @@ #include "jsutil.h" -#include "debugger/Debugger.h" #include "gc/FreeOp.h" #include "gc/Policy.h" #include "gc/PublicIterators.h" @@ -19,6 +18,7 @@ #include "vm/Runtime.h" #include "wasm/WasmInstance.h" +#include "debugger/DebugAPI-inl.h" #include "gc/GC-inl.h" #include "gc/Marking-inl.h" #include "gc/Nursery-inl.h" @@ -191,39 +191,7 @@ void Zone::sweepBreakpoints(FreeOp* fop) { MOZ_ASSERT(isGCSweepingOrCompacting()); for (auto iter = cellIterUnsafe(); !iter.done(); iter.next()) { JSScript* script = iter; - if (!script->hasAnyBreakpointsOrStepMode()) { - continue; - } - - bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script); - MOZ_ASSERT(script == iter); - for (unsigned i = 0; i < script->length(); i++) { - BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i)); - if (!site) { - continue; - } - - Breakpoint* nextbp; - for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) { - nextbp = bp->nextInSite(); - GCPtrNativeObject& dbgobj = bp->debugger->toJSObjectRef(); - - // If we are sweeping, then we expect the script and the - // debugger object to be swept in the same sweep group, except - // if the breakpoint was added after we computed the sweep - // groups. In this case both script and debugger object must be - // live. - MOZ_ASSERT_IF(isGCSweeping() && dbgobj->zone()->isCollecting(), - dbgobj->zone()->isGCSweeping() || - (!scriptGone && dbgobj->asTenured().isMarkedAny())); - - bool dying = scriptGone || IsAboutToBeFinalized(&dbgobj); - MOZ_ASSERT_IF(!dying, !IsAboutToBeFinalized(&bp->getHandlerRef())); - if (dying) { - bp->destroy(fop); - } - } - } + DebugAPI::sweepBreakpoints(fop, script); } for (RealmsInZoneIter realms(this); !realms.done(); realms.next()) { @@ -475,24 +443,7 @@ void Zone::notifyObservingDebuggers() { continue; } - GlobalObject::DebuggerVector* dbgs = global->getDebuggers(); - if (!dbgs) { - continue; - } - - for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty(); - r.popFront()) { - if (!r.front().unbarrieredGet()->debuggeeIsBeingCollected( - rt->gc.majorGCCount())) { -#ifdef DEBUG - fprintf(stderr, - "OOM while notifying observing Debuggers of a GC: The " - "onGarbageCollection\n" - "hook will not be fired for this GC for some Debuggers!\n"); -#endif - return; - } - } + DebugAPI::notifyParticipatesInGC(global, rt->gc.majorGCCount()); } } diff --git a/js/src/jit/BaselineCodeGen.cpp b/js/src/jit/BaselineCodeGen.cpp index 63c26f8e6cf8..a0129e69fd30 100644 --- a/js/src/jit/BaselineCodeGen.cpp +++ b/js/src/jit/BaselineCodeGen.cpp @@ -29,6 +29,7 @@ #include "vm/TraceLogging.h" #include "vtune/VTuneWrapper.h" +#include "debugger/DebugAPI-inl.h" #include "jit/BaselineFrameInfo-inl.h" #include "jit/MacroAssembler-inl.h" #include "jit/SharedICHelpers-inl.h" @@ -1578,7 +1579,8 @@ bool BaselineCompiler::emitDebugTrap() { JSScript* script = handler.script(); bool enabled = - script->stepModeEnabled() || script->hasBreakpointsAt(handler.pc()); + DebugAPI::stepModeEnabled(script) || + DebugAPI::hasBreakpointsAt(script, handler.pc()); #if defined(JS_CODEGEN_ARM64) // Flush any pending constant pools to prevent incorrect diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index 63e668aba122..bfd94fa6d01f 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -22,6 +22,7 @@ #include "vm/Interpreter.h" #include "vm/TraceLogging.h" +#include "debugger/DebugAPI-inl.h" #include "gc/PrivateIterators-inl.h" #include "jit/JitFrames-inl.h" #include "jit/MacroAssembler-inl.h" @@ -855,7 +856,8 @@ void BaselineScript::toggleDebugTraps(JSScript* script, jsbytecode* pc) { if (!pc || pc == curPC) { bool enabled = - script->stepModeEnabled() || script->hasBreakpointsAt(curPC); + DebugAPI::stepModeEnabled(script) || + DebugAPI::hasBreakpointsAt(script, curPC); // Patch the trap. CodeLocationLabel label(method(), CodeOffset(nativeOffset)); diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 106e55499414..274f98d05181 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -10,7 +10,6 @@ #include "jsutil.h" -#include "debugger/Debugger.h" #include "gc/Marking.h" #include "jit/BaselineDebugModeOSR.h" #include "jit/BaselineFrame.h" @@ -160,8 +159,7 @@ static void HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, // We need to bail when there is a catchable exception, and we are the // debuggee of a Debugger with a live onExceptionUnwind hook, or if a // Debugger has observed this frame (e.g., for onPop). - bool shouldBail = - Debugger::hasLiveHook(cx->global(), Debugger::OnExceptionUnwind); + bool shouldBail = DebugAPI::hasExceptionUnwindHook(cx->global()); RematerializedFrame* rematFrame = nullptr; if (!shouldBail) { JitActivation* act = cx->activation()->asJit(); diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 028a76d9a961..1e02203adca2 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -8,7 +8,6 @@ #include "builtin/Promise.h" #include "builtin/TypedObject.h" -#include "debugger/Debugger.h" #include "frontend/BytecodeCompiler.h" #include "jit/arm/Simulator-arm.h" #include "jit/BaselineIC.h" @@ -1127,9 +1126,10 @@ bool HandleDebugTrap(JSContext* cx, BaselineFrame* frame, uint8_t* retAddr, // is in step mode or has breakpoints. The Baseline Compiler can toggle // breakpoints more granularly for specific bytecode PCs. if (frame->runningInInterpreter()) { - MOZ_ASSERT(script->hasAnyBreakpointsOrStepMode()); + MOZ_ASSERT(DebugAPI::hasAnyBreakpointsOrStepMode(script)); } else { - MOZ_ASSERT(script->stepModeEnabled() || script->hasBreakpointsAt(pc)); + MOZ_ASSERT(DebugAPI::stepModeEnabled(script) || + DebugAPI::hasBreakpointsAt(script, pc)); } if (*pc == JSOP_AFTERYIELD) { @@ -1157,11 +1157,12 @@ bool HandleDebugTrap(JSContext* cx, BaselineFrame* frame, uint8_t* retAddr, RootedValue rval(cx); ResumeMode resumeMode = ResumeMode::Continue; - if (script->stepModeEnabled()) { + if (DebugAPI::stepModeEnabled(script)) { resumeMode = DebugAPI::onSingleStep(cx, &rval); } - if (resumeMode == ResumeMode::Continue && script->hasBreakpointsAt(pc)) { + if (resumeMode == ResumeMode::Continue && + DebugAPI::hasBreakpointsAt(script, pc)) { resumeMode = DebugAPI::onTrap(cx, &rval); } @@ -1212,7 +1213,7 @@ bool OnDebuggerStatement(JSContext* cx, BaselineFrame* frame, jsbytecode* pc, bool GlobalHasLiveOnDebuggerStatement(JSContext* cx) { AutoUnsafeCallWithABI unsafe; return cx->realm()->isDebuggee() && - Debugger::hasLiveHook(cx->global(), Debugger::OnDebuggerStatement); + DebugAPI::hasDebuggerStatementHook(cx->global()); } bool PushLexicalEnv(JSContext* cx, BaselineFrame* frame, diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 4cf2910d6194..f7c36a7f319a 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -25,7 +25,7 @@ #include "builtin/TypedObject.h" #include "builtin/WeakMapObject.h" #include "builtin/WeakSetObject.h" -#include "debugger/Debugger.h" +#include "debugger/DebugAPI.h" #include "gc/FreeOp.h" #include "js/ProtoKey.h" #include "vm/DateObject.h" @@ -841,32 +841,12 @@ bool js::DefineToStringTag(JSContext* cx, HandleObject obj, JSAtom* tag) { return DefineDataProperty(cx, obj, toStringTagId, tagString, JSPROP_READONLY); } -static void GlobalDebuggees_finalize(FreeOp* fop, JSObject* obj) { - MOZ_ASSERT(fop->maybeOnHelperThread()); - void* ptr = obj->as().getPrivate(); - auto debuggers = static_cast(ptr); - fop->delete_(obj, debuggers, MemoryUse::GlobalDebuggerVector); -} - -static const ClassOps GlobalDebuggees_classOps = {nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - GlobalDebuggees_finalize}; - -static const Class GlobalDebuggees_class = { - "GlobalDebuggee", JSCLASS_HAS_PRIVATE | JSCLASS_BACKGROUND_FINALIZE, - &GlobalDebuggees_classOps}; - GlobalObject::DebuggerVector* GlobalObject::getDebuggers() const { Value debuggers = getReservedSlot(DEBUGGERS); if (debuggers.isUndefined()) { return nullptr; } - MOZ_ASSERT(debuggers.toObject().getClass() == &GlobalDebuggees_class); - return (DebuggerVector*)debuggers.toObject().as().getPrivate(); + return DebugAPI::getGlobalDebuggers(&debuggers.toObject()); } /* static */ GlobalObject::DebuggerVector* GlobalObject::getOrCreateDebuggers( @@ -877,18 +857,13 @@ GlobalObject::DebuggerVector* GlobalObject::getDebuggers() const { return debuggers; } - NativeObject* obj = - NewNativeObjectWithGivenProto(cx, &GlobalDebuggees_class, nullptr); + JSObject* obj = DebugAPI::newGlobalDebuggersHolder(cx); if (!obj) { return nullptr; } - debuggers = cx->new_(cx->zone()); - if (!debuggers) { - return nullptr; - } - InitObjectPrivate(obj, debuggers, MemoryUse::GlobalDebuggerVector); + global->setReservedSlot(DEBUGGERS, ObjectValue(*obj)); - return debuggers; + return global->getDebuggers(); } /* static */ diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index b13d83040dfe..1dc8e9dd8fa1 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1797,7 +1797,7 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool Interpret(JSContext* cx, JS_BEGIN_MACRO \ script = (s); \ MOZ_ASSERT(cx->realm() == script->realm()); \ - if (script->hasAnyBreakpointsOrStepMode() || script->hasScriptCounts()) \ + if (DebugAPI::hasAnyBreakpointsOrStepMode(script) || script->hasScriptCounts()) \ activation.enableInterruptsUnconditionally(); \ JS_END_MACRO @@ -1888,7 +1888,7 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool Interpret(JSContext* cx, } if (script->isDebuggee()) { - if (script->stepModeEnabled()) { + if (DebugAPI::stepModeEnabled(script)) { RootedValue rval(cx); ResumeMode mode = DebugAPI::onSingleStep(cx, &rval); switch (mode) { @@ -1910,11 +1910,11 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool Interpret(JSContext* cx, moreInterrupts = true; } - if (script->hasAnyBreakpointsOrStepMode()) { + if (DebugAPI::hasAnyBreakpointsOrStepMode(script)) { moreInterrupts = true; } - if (script->hasBreakpointsAt(REGS.pc)) { + if (DebugAPI::hasBreakpointsAt(script, REGS.pc)) { RootedValue rval(cx); ResumeMode mode = DebugAPI::onTrap(cx, &rval); switch (mode) { diff --git a/js/src/vm/JSScript.cpp b/js/src/vm/JSScript.cpp index 3f43fb112dee..af1385ef8e67 100644 --- a/js/src/vm/JSScript.cpp +++ b/js/src/vm/JSScript.cpp @@ -29,7 +29,6 @@ #include "jstypes.h" #include "jsutil.h" -#include "debugger/Debugger.h" #include "frontend/BytecodeCompiler.h" #include "frontend/BytecodeEmitter.h" #include "frontend/SharedContext.h" @@ -4249,7 +4248,7 @@ void JSScript::finalize(FreeOp* fop) { jit::DestroyJitScripts(fop, this); destroyScriptCounts(); - destroyDebugScript(fop); + DebugAPI::destroyDebugScript(fop, this); #ifdef MOZ_VTUNE if (realm()->scriptVTuneIdMap) { @@ -4795,234 +4794,6 @@ JSScript* js::CloneScriptIntoFunction( return dst; } -DebugScript* JSScript::debugScript() { - MOZ_ASSERT(hasDebugScript()); - DebugScriptMap* map = realm()->debugScriptMap.get(); - MOZ_ASSERT(map); - DebugScriptMap::Ptr p = map->lookup(this); - MOZ_ASSERT(p); - return p->value().get(); -} - -DebugScript* JSScript::releaseDebugScript() { - MOZ_ASSERT(hasDebugScript()); - DebugScriptMap* map = realm()->debugScriptMap.get(); - MOZ_ASSERT(map); - DebugScriptMap::Ptr p = map->lookup(this); - MOZ_ASSERT(p); - DebugScript* debug = p->value().release(); - map->remove(p); - clearFlag(MutableFlags::HasDebugScript); - return debug; -} - -void JSScript::destroyDebugScript(FreeOp* fop) { - if (hasDebugScript()) { -#ifdef DEBUG - for (jsbytecode* pc = code(); pc < codeEnd(); pc++) { - MOZ_ASSERT(!getBreakpointSite(pc)); - } -#endif - freeDebugScript(fop); - } -} - -void JSScript::freeDebugScript(FreeOp* fop) { - MOZ_ASSERT(hasDebugScript()); - fop->free_(this, releaseDebugScript(), DebugScript::allocSize(length()), - MemoryUse::ScriptDebugScript); -} - -DebugScript* JSScript::getOrCreateDebugScript(JSContext* cx) { - if (hasDebugScript()) { - return debugScript(); - } - - size_t nbytes = DebugScript::allocSize(length()); - UniqueDebugScript debug( - reinterpret_cast(cx->pod_calloc(nbytes))); - if (!debug) { - return nullptr; - } - - /* Create realm's debugScriptMap if necessary. */ - if (!realm()->debugScriptMap) { - auto map = cx->make_unique(); - if (!map) { - return nullptr; - } - - realm()->debugScriptMap = std::move(map); - } - - DebugScript* borrowed = debug.get(); - if (!realm()->debugScriptMap->putNew(this, std::move(debug))) { - ReportOutOfMemory(cx); - return nullptr; - } - - setFlag(MutableFlags::HasDebugScript); // safe to set this; we can't fail - // after this point - AddCellMemory(this, nbytes, MemoryUse::ScriptDebugScript); - - /* - * Ensure that any Interpret() instances running on this script have - * interrupts enabled. The interrupts must stay enabled until the - * debug state is destroyed. - */ - for (ActivationIterator iter(cx); !iter.done(); ++iter) { - if (iter->isInterpreter()) { - iter->asInterpreter()->enableInterruptsIfRunning(this); - } - } - - return borrowed; -} - -bool JSScript::incrementGeneratorObserverCount(JSContext* cx) { - cx->check(this); - MOZ_ASSERT(cx->realm()->isDebuggee()); - - AutoRealm ar(cx, this); - - DebugScript* debug = getOrCreateDebugScript(cx); - if (!debug) { - return false; - } - - debug->generatorObserverCount++; - - // It is our caller's responsibility, before bumping the generator observer - // count, to make sure that the baseline code includes the necessary - // JS_AFTERYIELD instrumentation by calling - // {ensure,update}ExecutionObservabilityOfScript. - MOZ_ASSERT_IF(hasBaselineScript(), baseline->hasDebugInstrumentation()); - - return true; -} - -void JSScript::decrementGeneratorObserverCount(js::FreeOp* fop) { - DebugScript* debug = debugScript(); - MOZ_ASSERT(debug); - MOZ_ASSERT(debug->generatorObserverCount > 0); - - debug->generatorObserverCount--; - - if (!debug->needed()) { - destroyDebugScript(fop); - } -} - -bool JSScript::incrementStepperCount(JSContext* cx) { - cx->check(this); - MOZ_ASSERT(cx->realm()->isDebuggee()); - - AutoRealm ar(cx, this); - - DebugScript* debug = getOrCreateDebugScript(cx); - if (!debug) { - return false; - } - - debug->stepperCount++; - - if (debug->stepperCount == 1) { - if (hasBaselineScript()) { - baseline->toggleDebugTraps(this, nullptr); - } - } - - return true; -} - -void JSScript::decrementStepperCount(FreeOp* fop) { - DebugScript* debug = debugScript(); - MOZ_ASSERT(debug); - MOZ_ASSERT(debug->stepperCount > 0); - - debug->stepperCount--; - - if (debug->stepperCount == 0) { - if (hasBaselineScript()) { - baseline->toggleDebugTraps(this, nullptr); - } - - if (!debug->needed()) { - freeDebugScript(fop); - } - } -} - -BreakpointSite* JSScript::getOrCreateBreakpointSite(JSContext* cx, - jsbytecode* pc) { - AutoRealm ar(cx, this); - - DebugScript* debug = getOrCreateDebugScript(cx); - if (!debug) { - return nullptr; - } - - BreakpointSite*& site = debug->breakpoints[pcToOffset(pc)]; - - if (!site) { - site = cx->new_(this, pc); - if (!site) { - return nullptr; - } - debug->numSites++; - AddCellMemory(this, sizeof(JSBreakpointSite), MemoryUse::BreakpointSite); - } - - return site; -} - -void JSScript::destroyBreakpointSite(FreeOp* fop, jsbytecode* pc) { - DebugScript* debug = debugScript(); - BreakpointSite*& site = debug->breakpoints[pcToOffset(pc)]; - MOZ_ASSERT(site); - - size_t size = site->type() == BreakpointSite::Type::JS - ? sizeof(JSBreakpointSite) - : sizeof(WasmBreakpointSite); - fop->delete_(this, site, size, MemoryUse::BreakpointSite); - site = nullptr; - - debug->numSites--; - if (!debug->needed()) { - freeDebugScript(fop); - } -} - -void JSScript::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg, - JSObject* handler) { - if (!hasAnyBreakpointsOrStepMode()) { - return; - } - - for (jsbytecode* pc = code(); pc < codeEnd(); pc++) { - BreakpointSite* site = getBreakpointSite(pc); - if (site) { - Breakpoint* nextbp; - for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) { - nextbp = bp->nextInSite(); - if ((!dbg || bp->debugger == dbg) && - (!handler || bp->getHandler() == handler)) { - bp->destroy(fop); - } - } - } - } -} - -bool JSScript::hasBreakpointsAt(jsbytecode* pc) { - BreakpointSite* site = getBreakpointSite(pc); - if (!site) { - return false; - } - - return site->enabledCount > 0; -} - /* static */ bool ImmutableScriptData::InitFromEmitter( JSContext* cx, js::HandleScript script, frontend::BytecodeEmitter* bce, uint32_t nslots) { diff --git a/js/src/vm/JSScript.h b/js/src/vm/JSScript.h index 98710407bd9f..92591e83c251 100644 --- a/js/src/vm/JSScript.h +++ b/js/src/vm/JSScript.h @@ -68,14 +68,14 @@ class JitScript; #define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript*)0x1) class AutoSweepJitScript; -class BreakpointSite; -class Debugger; class GCParallelTask; class LazyScript; class ModuleObject; class RegExpObject; class SourceCompressionTask; class Shape; +class DebugAPI; +class DebugScript; namespace frontend { struct BytecodeEmitter; @@ -253,58 +253,6 @@ using ScriptVTuneIdMap = HashMap, SystemAllocPolicy>; #endif -class DebugScript { - friend class ::JSScript; - friend class JS::Realm; - - /* - * If this is a generator script, this is the number of Debugger.Frames - * referring to calls to this generator, whether live or suspended. Closed - * generators do not contribute a count. - * - * When greater than zero, this script should be compiled with debug - * instrumentation to call Debugger::onResumeFrame at each resumption site, so - * that Debugger can reconnect any extant Debugger.Frames with the new - * concrete frame. - */ - uint32_t generatorObserverCount; - - /* - * The number of Debugger.Frame objects that refer to frames running this - * script and that have onStep handlers. When nonzero, the interpreter and JIT - * must arrange to call Debugger::onSingleStep before each bytecode, or at - * least at some useful granularity. - */ - uint32_t stepperCount; - - /* - * Number of breakpoint sites at opcodes in the script. This is the number - * of populated entries in DebugScript::breakpoints, below. - */ - uint32_t numSites; - - /* - * Breakpoints set in our script. For speed and simplicity, this array is - * parallel to script->code(): the BreakpointSite for the opcode at - * script->code()[offset] is debugScript->breakpoints[offset]. Naturally, - * this array's true length is script->length(). - */ - BreakpointSite* breakpoints[1]; - - /* - * True if this DebugScript carries any useful information. If false, it - * should be removed from its JSScript. - */ - bool needed() const { - return generatorObserverCount > 0 || stepperCount > 0 || numSites > 0; - } - - static size_t allocSize(size_t codeLength) { - return offsetof(DebugScript, breakpoints) + - codeLength * sizeof(BreakpointSite*); - } -}; - using UniqueDebugScript = js::UniquePtr; using DebugScriptMap = HashMap, SystemAllocPolicy>; @@ -3104,65 +3052,14 @@ class JSScript : public js::BaseScript { bool formalIsAliased(unsigned argSlot); bool formalLivesInArgumentsObject(unsigned argSlot); - private: - /* Change this->stepMode to |newValue|. */ - void setNewStepMode(js::FreeOp* fop, uint32_t newValue); - - js::DebugScript* getOrCreateDebugScript(JSContext* cx); - js::DebugScript* debugScript(); - js::DebugScript* releaseDebugScript(); - void destroyDebugScript(js::FreeOp* fop); - void freeDebugScript(js::FreeOp* fop); - - bool hasDebugScript() const { return hasFlag(MutableFlags::HasDebugScript); } - - public: - bool hasBreakpointsAt(jsbytecode* pc); - bool hasAnyBreakpointsOrStepMode() { return hasDebugScript(); } - // See comment above 'debugMode' in Realm.h for explanation of // invariants of debuggee compartments, scripts, and frames. inline bool isDebuggee() const; - js::BreakpointSite* getBreakpointSite(jsbytecode* pc) { - return hasDebugScript() ? debugScript()->breakpoints[pcToOffset(pc)] - : nullptr; - } - - js::BreakpointSite* getOrCreateBreakpointSite(JSContext* cx, jsbytecode* pc); - - void destroyBreakpointSite(js::FreeOp* fop, jsbytecode* pc); - - void clearBreakpointsIn(js::FreeOp* fop, js::Debugger* dbg, - JSObject* handler); - - /* - * Increment or decrement the single-step count. If the count is non-zero - * then the script is in single-step mode. - * - * Only incrementing is fallible, as it could allocate a DebugScript. - */ - bool incrementStepperCount(JSContext* cx); - void decrementStepperCount(js::FreeOp* fop); - - bool stepModeEnabled() { - return hasDebugScript() && debugScript()->stepperCount > 0; - } - -#ifdef DEBUG - uint32_t stepperCount() { - return hasDebugScript() ? debugScript()->stepperCount : 0; - } -#endif - - /* - * Increment or decrement the generator observer count. If the count is - * non-zero then the script reports resumptions to the debugger. - * - * Only incrementing is fallible, as it could allocate a DebugScript. - */ - bool incrementGeneratorObserverCount(JSContext* cx); - void decrementGeneratorObserverCount(js::FreeOp* fop); + // Access the flag for whether this script has a DebugScript in its realm's + // map. This should only be used by the DebugScript class. + bool hasDebugScript() const { return hasFlag(MutableFlags::HasDebugScript); } + void setHasDebugScript(bool b) { setFlag(MutableFlags::HasDebugScript, b); } void finalize(js::FreeOp* fop); diff --git a/js/src/vm/Realm.cpp b/js/src/vm/Realm.cpp index 946ade427b03..a6304bd05aa2 100644 --- a/js/src/vm/Realm.cpp +++ b/js/src/vm/Realm.cpp @@ -12,7 +12,7 @@ #include "jsfriendapi.h" -#include "debugger/Debugger.h" +#include "debugger/DebugAPI.h" #include "gc/Policy.h" #include "gc/PublicIterators.h" #include "jit/JitOptions.h" @@ -565,12 +565,7 @@ void Realm::checkScriptMapsAfterMovingGC() { MOZ_ASSERT(script->realm() == this); CheckGCThingAfterMovingGC(script); DebugScript* ds = r.front().value().get(); - for (uint32_t i = 0; i < ds->numSites; i++) { - BreakpointSite* site = ds->breakpoints[i]; - if (site && site->type() == BreakpointSite::Type::JS) { - CheckGCThingAfterMovingGC(site->asJS()->script); - } - } + DebugAPI::checkDebugScriptAfterMovingGC(ds); auto ptr = debugScriptMap->lookup(script); MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); } @@ -775,22 +770,20 @@ void Realm::updateDebuggerObservesFlag(unsigned flag) { zone()->runtimeFromMainThread()->gc.isForegroundSweeping() ? unsafeUnbarrieredMaybeGlobal() : maybeGlobal(); - const GlobalObject::DebuggerVector* v = global->getDebuggers(); - for (auto p = v->begin(); p != v->end(); p++) { - // Use unbarrieredGet() to prevent triggering read barrier while collecting, - // this is safe as long as dbg does not escape. - Debugger* dbg = p->unbarrieredGet(); - if (flag == DebuggerObservesAllExecution - ? dbg->observesAllExecution() - : flag == DebuggerObservesCoverage - ? dbg->observesCoverage() - : flag == DebuggerObservesAsmJS && dbg->observesAsmJS()) { - debugModeBits_ |= flag; - return; - } + bool observes = false; + if (flag == DebuggerObservesAllExecution) { + observes = DebugAPI::debuggerObservesAllExecution(global); + } else if (flag == DebuggerObservesCoverage) { + observes = DebugAPI::debuggerObservesCoverage(global); + } else if (flag == DebuggerObservesAsmJS) { + observes = DebugAPI::debuggerObservesAsmJS(global); } - debugModeBits_ &= ~flag; + if (observes) { + debugModeBits_ |= flag; + } else { + debugModeBits_ &= ~flag; + } } void Realm::setIsDebuggee() { @@ -871,16 +864,6 @@ void Realm::clearScriptCounts() { void Realm::clearScriptNames() { scriptNameMap.reset(); } -void Realm::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg, - HandleObject handler) { - for (auto script = zone()->cellIter(); !script.done(); - script.next()) { - if (script->realm() == this && script->hasAnyBreakpointsOrStepMode()) { - script->clearBreakpointsIn(fop, dbg, handler); - } - } -} - void ObjectRealm::addSizeOfExcludingThis( mozilla::MallocSizeOf mallocSizeOf, size_t* innerViewsArg, size_t* lazyArrayBuffersArg, size_t* objectMetadataTablesArg, diff --git a/js/src/vm/Realm.h b/js/src/vm/Realm.h index 93473937d92b..891867845e6c 100644 --- a/js/src/vm/Realm.h +++ b/js/src/vm/Realm.h @@ -768,9 +768,6 @@ class JS::Realm : public JS::shadow::Realm { // scripts. bool ensureDelazifyScriptsForDebugger(JSContext* cx); - void clearBreakpointsIn(js::FreeOp* fop, js::Debugger* dbg, - JS::HandleObject handler); - // Initializes randomNumberGenerator if needed. mozilla::non_crypto::XorShift128PlusRNG& getOrCreateRandomNumberGenerator(); diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 1f2738fe9db0..cd6e93a5d0e7 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -26,7 +26,6 @@ #include "jsmath.h" #include "builtin/Promise.h" -#include "debugger/DebugAPI.h" #include "gc/FreeOp.h" #include "gc/GCInternals.h" #include "gc/PublicIterators.h" @@ -53,6 +52,7 @@ #include "vm/TraceLoggingGraph.h" #include "wasm/WasmSignalHandlers.h" +#include "debugger/DebugAPI-inl.h" #include "gc/GC-inl.h" #include "vm/JSContext-inl.h" #include "vm/Realm-inl.h" @@ -434,7 +434,7 @@ static bool HandleInterrupt(JSContext* cx, bool invokeCallback) { if (cx->realm()->isDebuggee()) { ScriptFrameIter iter(cx); if (!iter.done() && cx->compartment() == iter.compartment() && - iter.script()->stepModeEnabled()) { + DebugAPI::stepModeEnabled(iter.script())) { RootedValue rval(cx); switch (DebugAPI::onSingleStep(cx, &rval)) { case ResumeMode::Terminate: diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index 3c7a7230fa7b..0a9b987f186b 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -19,7 +19,6 @@ #include "jsmath.h" #include "jsnum.h" -#include "debugger/Debugger.h" #include "gc/FreeOp.h" #include "gc/HashUtil.h" #include "gc/Marking.h" @@ -1816,31 +1815,12 @@ void SavedStacks::chooseSamplingProbability(Realm* realm) { return; } - GlobalObject::DebuggerVector* dbgs = global->getDebuggers(); - if (!dbgs || dbgs->empty()) { + Maybe probability = DebugAPI::allocationSamplingProbability(global); + if (probability.isNothing()) { return; } - mozilla::DebugOnly*> begin = dbgs->begin(); - mozilla::DebugOnly foundAnyDebuggers = false; - - double probability = 0; - 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); - // Use unbarrieredGet() to prevent triggering read barrier while collecting, - // this is safe as long as dbgp does not escape. - Debugger* dbgp = p->unbarrieredGet(); - - if (dbgp->trackingAllocationSites && dbgp->enabled) { - foundAnyDebuggers = true; - probability = std::max(dbgp->allocationSamplingProbability, probability); - } - } - MOZ_ASSERT(foundAnyDebuggers); - - this->setSamplingProbability(probability); + this->setSamplingProbability(*probability); } void SavedStacks::setSamplingProbability(double probability) {