diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/debugger/Debugger.h | 1631 |
1 files changed, 1631 insertions, 0 deletions
diff --git a/js/src/debugger/Debugger.h b/js/src/debugger/Debugger.h new file mode 100644 index 0000000000..3af7575806 --- /dev/null +++ b/js/src/debugger/Debugger.h @@ -0,0 +1,1631 @@ +/* -*- 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 debugger_Debugger_h +#define debugger_Debugger_h + +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER1 +#include "mozilla/Attributes.h" // for MOZ_RAII +#include "mozilla/DoublyLinkedList.h" // for DoublyLinkedListElement +#include "mozilla/HashTable.h" // for HashSet, DefaultHasher (ptr only) +#include "mozilla/LinkedList.h" // for LinkedList (ptr only) +#include "mozilla/Maybe.h" // for Maybe, Nothing +#include "mozilla/Range.h" // for Range +#include "mozilla/Result.h" // for Result +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/Variant.h" // for Variant + +#include <stddef.h> // for size_t +#include <stdint.h> // for uint32_t, uint64_t, uintptr_t +#include <utility> // for std::move + +#include "jstypes.h" // for JS_GC_ZEAL +#include "NamespaceImports.h" // for Value, HandleObject + +#include "debugger/DebugAPI.h" // for DebugAPI +#include "debugger/Object.h" // for DebuggerObject +#include "ds/TraceableFifo.h" // for TraceableFifo +#include "gc/Barrier.h" // +#include "gc/Tracer.h" // for TraceNullableEdge, TraceEdge +#include "gc/WeakMap.h" // for WeakMap +#include "gc/ZoneAllocator.h" // for ZoneAllocPolicy +#include "js/Debug.h" // JS_DefineDebuggerObject +#include "js/GCAPI.h" // for GarbageCollectionEvent +#include "js/GCVariant.h" // for GCVariant +#include "js/Proxy.h" // for PropertyDescriptor +#include "js/RootingAPI.h" // for Handle +#include "js/TracingAPI.h" // for TraceRoot +#include "js/Wrapper.h" // for UncheckedUnwrap +#include "proxy/DeadObjectProxy.h" // for IsDeadProxyObject +#include "vm/GeneratorObject.h" // for AbstractGeneratorObject +#include "vm/GlobalObject.h" // for GlobalObject +#include "vm/JSContext.h" // for JSContext +#include "vm/JSObject.h" // for JSObject +#include "vm/JSScript.h" // for JSScript, ScriptSourceObject +#include "vm/NativeObject.h" // for NativeObject +#include "vm/Runtime.h" // for JSRuntime +#include "vm/SavedFrame.h" // for SavedFrame +#include "vm/Stack.h" // for AbstractFramePtr, FrameIter +#include "vm/StringType.h" // for JSAtom +#include "wasm/WasmJS.h" // for WasmInstanceObject + +class JS_PUBLIC_API JSFunction; + +namespace JS { +class JS_PUBLIC_API AutoStableStringChars; +class JS_PUBLIC_API Compartment; +class JS_PUBLIC_API Realm; +class JS_PUBLIC_API Zone; +} /* namespace JS */ + +namespace js { +class AutoRealm; +class CrossCompartmentKey; +class Debugger; +class DebuggerEnvironment; +class PromiseObject; +namespace gc { +struct Cell; +} /* namespace gc */ +namespace wasm { +class Instance; +} /* namespace wasm */ +} /* namespace js */ + +/* + * Windows 3.x used a cooperative multitasking model, with a Yield macro that + * let you relinquish control to other cooperative threads. Microsoft replaced + * it with an empty macro long ago. We should be free to use it in our code. + */ +#undef Yield + +namespace js { + +class Breakpoint; +class DebuggerFrame; +class DebuggerScript; +class DebuggerSource; +class DebuggerMemory; +class ScriptedOnStepHandler; +class ScriptedOnPopHandler; +class DebuggerDebuggeeLink; + +/** + * Tells how the JS engine should resume debuggee execution after firing a + * debugger hook. Most debugger hooks get to choose how the debuggee proceeds; + * see js/src/doc/Debugger/Conventions.md under "Resumption Values". + * + * Debugger::processHandlerResult() translates between JavaScript values and + * this enum. + */ +enum class ResumeMode { + /** + * The debuggee should continue unchanged. + * + * This corresponds to a resumption value of `undefined`. + */ + Continue, + + /** + * Throw an exception in the debuggee. + * + * This corresponds to a resumption value of `{throw: <value>}`. + */ + Throw, + + /** + * Terminate the debuggee, as if it had been cancelled via the "slow + * script" ribbon. + * + * This corresponds to a resumption value of `null`. + */ + Terminate, + + /** + * Force the debuggee to return from the current frame. + * + * This corresponds to a resumption value of `{return: <value>}`. + */ + Return, +}; + +/** + * A completion value, describing how some sort of JavaScript evaluation + * completed. This is used to tell an onPop handler what's going on with the + * frame, and to report the outcome of call, apply, setProperty, and getProperty + * operations. + * + * Local variables of type Completion should be held in Rooted locations, + * and passed using Handle and MutableHandle. + */ +class Completion { + public: + struct Return { + explicit Return(const Value& value) : value(value) {} + Value value; + + void trace(JSTracer* trc) { + JS::TraceRoot(trc, &value, "js::Completion::Return::value"); + } + }; + + struct Throw { + Throw(const Value& exception, SavedFrame* stack) + : exception(exception), stack(stack) {} + Value exception; + SavedFrame* stack; + + void trace(JSTracer* trc) { + JS::TraceRoot(trc, &exception, "js::Completion::Throw::exception"); + JS::TraceRoot(trc, &stack, "js::Completion::Throw::stack"); + } + }; + + struct Terminate { + void trace(JSTracer* trc) {} + }; + + struct InitialYield { + explicit InitialYield(AbstractGeneratorObject* generatorObject) + : generatorObject(generatorObject) {} + AbstractGeneratorObject* generatorObject; + + void trace(JSTracer* trc) { + JS::TraceRoot(trc, &generatorObject, + "js::Completion::InitialYield::generatorObject"); + } + }; + + struct Yield { + Yield(AbstractGeneratorObject* generatorObject, const Value& iteratorResult) + : generatorObject(generatorObject), iteratorResult(iteratorResult) {} + AbstractGeneratorObject* generatorObject; + Value iteratorResult; + + void trace(JSTracer* trc) { + JS::TraceRoot(trc, &generatorObject, + "js::Completion::Yield::generatorObject"); + JS::TraceRoot(trc, &iteratorResult, + "js::Completion::Yield::iteratorResult"); + } + }; + + struct Await { + Await(AbstractGeneratorObject* generatorObject, const Value& awaitee) + : generatorObject(generatorObject), awaitee(awaitee) {} + AbstractGeneratorObject* generatorObject; + Value awaitee; + + void trace(JSTracer* trc) { + JS::TraceRoot(trc, &generatorObject, + "js::Completion::Await::generatorObject"); + JS::TraceRoot(trc, &awaitee, "js::Completion::Await::awaitee"); + } + }; + + // The JS::Result macros want to assign to an existing variable, so having a + // default constructor is handy. + Completion() : variant(Terminate()) {} + + // Construct a completion from a specific variant. + // + // Unfortunately, using a template here would prevent the implicit definitions + // of the copy and move constructor and assignment operators, which is icky. + explicit Completion(Return&& variant) + : variant(std::forward<Return>(variant)) {} + explicit Completion(Throw&& variant) + : variant(std::forward<Throw>(variant)) {} + explicit Completion(Terminate&& variant) + : variant(std::forward<Terminate>(variant)) {} + explicit Completion(InitialYield&& variant) + : variant(std::forward<InitialYield>(variant)) {} + explicit Completion(Yield&& variant) + : variant(std::forward<Yield>(variant)) {} + explicit Completion(Await&& variant) + : variant(std::forward<Await>(variant)) {} + + // Capture a JavaScript operation result as a Completion value. This clears + // any exception and stack from cx, taking ownership of them itself. + static Completion fromJSResult(JSContext* cx, bool ok, const Value& rv); + + // Construct a completion given an AbstractFramePtr that is being popped. This + // clears any exception and stack from cx, taking ownership of them itself. + static Completion fromJSFramePop(JSContext* cx, AbstractFramePtr frame, + const jsbytecode* pc, bool ok); + + template <typename V> + bool is() const { + return variant.template is<V>(); + } + + template <typename V> + V& as() { + return variant.template as<V>(); + } + + template <typename V> + const V& as() const { + return variant.template as<V>(); + } + + void trace(JSTracer* trc); + + /* True if this completion is a suspension of a generator or async call. */ + bool suspending() const { + return variant.is<InitialYield>() || variant.is<Yield>() || + variant.is<Await>(); + } + + /* Set `result` to a Debugger API completion value describing this completion. + */ + bool buildCompletionValue(JSContext* cx, Debugger* dbg, + MutableHandleValue result) const; + + /* + * Set `resumeMode`, `value`, and `exnStack` to values describing this + * completion. + */ + void toResumeMode(ResumeMode& resumeMode, MutableHandleValue value, + MutableHandle<SavedFrame*> exnStack) const; + /* + * Given a `ResumeMode` and value (typically derived from a resumption value + * returned by a Debugger hook), update this completion as requested. + */ + void updateFromHookResult(ResumeMode resumeMode, HandleValue value); + + private: + using Variant = + mozilla::Variant<Return, Throw, Terminate, InitialYield, Yield, Await>; + struct BuildValueMatcher; + struct ToResumeModeMatcher; + + Variant variant; +}; + +typedef HashSet<WeakHeapPtr<GlobalObject*>, + MovableCellHasher<WeakHeapPtr<GlobalObject*>>, ZoneAllocPolicy> + WeakGlobalObjectSet; + +#ifdef DEBUG +extern void CheckDebuggeeThing(BaseScript* script, bool invisibleOk); + +extern void CheckDebuggeeThing(JSObject* obj, bool invisibleOk); +#endif + +/* + * [SMDOC] Cross-compartment weakmap entries for Debugger API objects + * + * The Debugger API creates objects like Debugger.Object, Debugger.Script, + * Debugger.Environment, etc. to refer to things in the debuggee. Each Debugger + * gets at most one Debugger.Mumble for each referent: Debugger.Mumbles are + * unique per referent per Debugger. This is accomplished by storing the + * debugger objects in a DebuggerWeakMap, using the debuggee thing as the key. + * + * Since a Debugger and its debuggee must be in different compartments, a + * Debugger.Mumble's pointer to its referent is a cross-compartment edge, from + * the debugger's compartment into the debuggee compartment. Like any other sort + * of cross-compartment edge, the GC needs to be able to find all of these edges + * readily. The GC therefore consults the debugger's weakmap tables as + * necessary. This allows the garbage collector to easily find edges between + * debuggee object compartments and debugger compartments when calculating the + * zone sweep groups. + * + * The current implementation results in all debuggee object compartments being + * swept in the same group as the debugger. This is a conservative approach, and + * compartments may be unnecessarily grouped. However this results in a simpler + * and faster implementation. + */ + +/* + * A weakmap from GC thing keys to JSObject values that supports the keys being + * in different compartments to the values. All values must be in the same + * compartment. + * + * If InvisibleKeysOk is true, then the map can have keys in invisible-to- + * debugger compartments. If it is false, we assert that such entries are never + * created. + * + * Note that keys in these weakmaps can be in any compartment, debuggee or 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.) + */ +template <class Referent, class Wrapper, bool InvisibleKeysOk = false> +class DebuggerWeakMap : private WeakMap<HeapPtr<Referent*>, HeapPtr<Wrapper*>> { + private: + using Key = HeapPtr<Referent*>; + using Value = HeapPtr<Wrapper*>; + + JS::Compartment* compartment; + + public: + typedef WeakMap<Key, Value> Base; + using ReferentType = Referent; + using WrapperType = Wrapper; + + explicit DebuggerWeakMap(JSContext* cx) + : Base(cx), compartment(cx->compartment()) {} + + public: + // Expose those parts of HashMap public interface that are used by Debugger + // methods. + + using Entry = typename Base::Entry; + using Ptr = typename Base::Ptr; + using AddPtr = typename Base::AddPtr; + using Range = typename Base::Range; + using Lookup = typename Base::Lookup; + + // Expose WeakMap public interface. + + using Base::all; + using Base::has; + using Base::lookup; + using Base::lookupForAdd; + using Base::lookupUnbarriered; + using Base::remove; + using Base::trace; + using Base::zone; +#ifdef DEBUG + using Base::hasEntry; +#endif + + class Enum : public Base::Enum { + public: + explicit Enum(DebuggerWeakMap& map) : Base::Enum(map) {} + }; + + template <typename KeyInput, typename ValueInput> + bool relookupOrAdd(AddPtr& p, const KeyInput& k, const ValueInput& v) { + MOZ_ASSERT(v->compartment() == this->compartment); +#ifdef DEBUG + CheckDebuggeeThing(k, InvisibleKeysOk); +#endif + MOZ_ASSERT(!Base::has(k)); + bool ok = Base::relookupOrAdd(p, k, v); + return ok; + } + + public: + void traceCrossCompartmentEdges(JSTracer* tracer) { + for (Enum e(*this); !e.empty(); e.popFront()) { + TraceEdge(tracer, &e.front().mutableKey(), "Debugger WeakMap key"); + e.front().value()->trace(tracer); + } + } + + bool findSweepGroupEdges() override; + + private: +#ifdef JS_GC_ZEAL + // Let the weak map marking verifier know that this map can + // contain keys in other zones. + virtual bool allowKeysInOtherZones() const override { return true; } +#endif +}; + +class LeaveDebuggeeNoExecute; + +class MOZ_RAII EvalOptions { + JS::UniqueChars filename_; + unsigned lineno_ = 1; + bool hideFromDebugger_ = false; + + public: + EvalOptions() = default; + ~EvalOptions() = default; + const char* filename() const { return filename_.get(); } + unsigned lineno() const { return lineno_; } + bool hideFromDebugger() const { return hideFromDebugger_; } + [[nodiscard]] bool setFilename(JSContext* cx, const char* filename); + void setLineno(unsigned lineno) { lineno_ = lineno; } + void setHideFromDebugger(bool hide) { hideFromDebugger_ = hide; } +}; + +/* + * Env is the type of what ECMA-262 calls "lexical environments" (the records + * that represent scopes and bindings). See vm/EnvironmentObject.h. + * + * This is JSObject rather than js::EnvironmentObject because GlobalObject and + * some proxies, despite not being in the EnvironmentObject class hierarchy, + * can be in environment chains. + */ +using Env = JSObject; + +// The referent of a Debugger.Script. +// +// - For most scripts, we point at their BaseScript. +// +// - For Web Assembly instances for which we are presenting a script-like +// interface, we point at their WasmInstanceObject. +// +// The DebuggerScript object itself simply stores a Cell* in its private +// pointer, but when we're working with that pointer in C++ code, we'd rather +// not pass around a Cell* and be constantly asserting that, yes, this really +// does point to something okay. Instead, we immediately build an instance of +// this type from the Cell* and use that instead, so we can benefit from +// Variant's static checks. +typedef mozilla::Variant<BaseScript*, WasmInstanceObject*> + DebuggerScriptReferent; + +// The referent of a Debugger.Source. +// +// - For most sources, this is a ScriptSourceObject. +// +// - For Web Assembly instances for which we are presenting a source-like +// interface, we point at their WasmInstanceObject. +// +// The DebuggerSource object actually simply stores a Cell* in its private +// pointer. See the comments for DebuggerScriptReferent for the rationale for +// this type. +typedef mozilla::Variant<ScriptSourceObject*, WasmInstanceObject*> + DebuggerSourceReferent; + +template <typename HookIsEnabledFun /* bool (Debugger*) */> +class MOZ_RAII DebuggerList { + private: + // Note: In the general case, 'debuggers' contains references to objects in + // different compartments--every compartment *except* the debugger's. + RootedValueVector debuggers; + HookIsEnabledFun hookIsEnabled; + + public: + /** + * The hook function will be called during `init()` to build the list of + * active debuggers, and again during dispatch to validate that the hook is + * still active for the given debugger. + */ + DebuggerList(JSContext* cx, HookIsEnabledFun hookIsEnabled) + : debuggers(cx), hookIsEnabled(hookIsEnabled) {} + + [[nodiscard]] bool init(JSContext* cx); + + bool empty() { return debuggers.empty(); } + + template <typename FireHookFun /* ResumeMode (Debugger*) */> + bool dispatchHook(JSContext* cx, FireHookFun fireHook); + + template <typename FireHookFun /* void (Debugger*) */> + void dispatchQuietHook(JSContext* cx, FireHookFun fireHook); + + template <typename FireHookFun /* bool (Debugger*, ResumeMode&, MutableHandleValue) */> + [[nodiscard]] bool dispatchResumptionHook(JSContext* cx, + AbstractFramePtr frame, + FireHookFun fireHook); +}; + +// The Debugger.prototype object. +class DebuggerPrototypeObject : public NativeObject { + public: + static const JSClass class_; +}; + +class DebuggerInstanceObject : public NativeObject { + private: + static const JSClassOps classOps_; + + public: + static const JSClass class_; +}; + +class Debugger : private mozilla::LinkedListElement<Debugger> { + friend class DebugAPI; + friend class Breakpoint; + friend class DebuggerFrame; + friend class DebuggerMemory; + friend class DebuggerInstanceObject; + + template <typename> + friend class DebuggerList; + friend struct JSRuntime::GlobalObjectWatchersLinkAccess<Debugger>; + friend struct JSRuntime::GarbageCollectionWatchersLinkAccess<Debugger>; + friend class SavedStacks; + friend class ScriptedOnStepHandler; + friend class ScriptedOnPopHandler; + friend class mozilla::LinkedListElement<Debugger>; + friend class mozilla::LinkedList<Debugger>; + friend bool(::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj); + friend bool(::JS::dbg::IsDebugger)(JSObject&); + friend bool(::JS::dbg::GetDebuggeeGlobals)(JSContext*, JSObject&, + MutableHandleObjectVector); + friend bool JS::dbg::FireOnGarbageCollectionHookRequired(JSContext* cx); + friend bool JS::dbg::FireOnGarbageCollectionHook( + JSContext* cx, JS::dbg::GarbageCollectionEvent::Ptr&& data); + + public: + enum Hook { + OnDebuggerStatement, + OnExceptionUnwind, + OnNewScript, + OnEnterFrame, + OnNativeCall, + OnNewGlobalObject, + OnNewPromise, + OnPromiseSettled, + OnGarbageCollection, + HookCount + }; + enum { + JSSLOT_DEBUG_PROTO_START, + JSSLOT_DEBUG_FRAME_PROTO = JSSLOT_DEBUG_PROTO_START, + JSSLOT_DEBUG_ENV_PROTO, + JSSLOT_DEBUG_OBJECT_PROTO, + JSSLOT_DEBUG_SCRIPT_PROTO, + JSSLOT_DEBUG_SOURCE_PROTO, + JSSLOT_DEBUG_MEMORY_PROTO, + JSSLOT_DEBUG_PROTO_STOP, + JSSLOT_DEBUG_DEBUGGER = JSSLOT_DEBUG_PROTO_STOP, + JSSLOT_DEBUG_HOOK_START, + JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount, + JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP, + JSSLOT_DEBUG_DEBUGGEE_LINK, + JSSLOT_DEBUG_COUNT + }; + + // Bring DebugAPI::IsObserving into the Debugger namespace. + using IsObserving = DebugAPI::IsObserving; + static const IsObserving Observing = DebugAPI::Observing; + static const IsObserving NotObserving = DebugAPI::NotObserving; + + // Return true if the given realm is a debuggee of this debugger, + // false otherwise. + bool isDebuggeeUnbarriered(const Realm* realm) const; + + // Return true if this Debugger observed a debuggee that participated in the + // GC identified by the given GC number. Return false otherwise. + // May return false negatives if we have hit OOM. + bool observedGC(uint64_t majorGCNumber) const { + return observedGCs.has(majorGCNumber); + } + + // Notify this Debugger that one or more of its debuggees is participating + // in the GC identified by the given GC number. + bool debuggeeIsBeingCollected(uint64_t majorGCNumber) { + return observedGCs.put(majorGCNumber); + } + + static SavedFrame* getObjectAllocationSite(JSObject& obj); + + struct AllocationsLogEntry { + AllocationsLogEntry(HandleObject frame, mozilla::TimeStamp when, + const char* className, size_t size, bool inNursery) + : frame(frame), + when(when), + className(className), + size(size), + inNursery(inNursery) { + MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>() || + IsDeadProxyObject(frame)); + } + + HeapPtr<JSObject*> frame; + mozilla::TimeStamp when; + const char* className; + size_t size; + bool inNursery; + + void trace(JSTracer* trc) { + TraceNullableEdge(trc, &frame, "Debugger::AllocationsLogEntry::frame"); + } + }; + + private: + HeapPtr<NativeObject*> object; /* The Debugger object. Strong reference. */ + WeakGlobalObjectSet + debuggees; /* Debuggee globals. Cross-compartment weak references. */ + JS::ZoneSet debuggeeZones; /* Set of zones that we have debuggees in. */ + HeapPtr<JSObject*> uncaughtExceptionHook; /* Strong reference. */ + bool allowUnobservedAsmJS; + bool allowUnobservedWasm; + + // Whether to enable code coverage on the Debuggee. + bool collectCoverageInfo; + + template <typename T> + struct DebuggerLinkAccess { + static mozilla::DoublyLinkedListElement<T>& Get(T* aThis) { + return aThis->debuggerLink; + } + }; + + // List of all js::Breakpoints in this debugger. + using BreakpointList = + mozilla::DoublyLinkedList<js::Breakpoint, + DebuggerLinkAccess<js::Breakpoint>>; + BreakpointList breakpoints; + + // The set of GC numbers for which one or more of this Debugger's observed + // debuggees participated in. + using GCNumberSet = + HashSet<uint64_t, DefaultHasher<uint64_t>, ZoneAllocPolicy>; + GCNumberSet observedGCs; + + using AllocationsLog = js::TraceableFifo<AllocationsLogEntry>; + + AllocationsLog allocationsLog; + bool trackingAllocationSites; + double allocationSamplingProbability; + size_t maxAllocationsLogLength; + bool allocationsLogOverflowed; + + static const size_t DEFAULT_MAX_LOG_LENGTH = 5000; + + [[nodiscard]] bool appendAllocationSite(JSContext* cx, HandleObject obj, + Handle<SavedFrame*> frame, + mozilla::TimeStamp when); + + /* + * Recompute the set of debuggee zones based on the set of debuggee globals. + */ + void recomputeDebuggeeZoneSet(); + + /* + * Return true if there is an existing object metadata callback for the + * given global's compartment that will prevent our instrumentation of + * allocations. + */ + static bool cannotTrackAllocations(const GlobalObject& global); + + /* + * 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. + */ + [[nodiscard]] static bool addAllocationsTracking( + JSContext* cx, Handle<GlobalObject*> debuggee); + + /* + * Remove allocations tracking for objects allocated within the given + * global's compartment. This is a no-op if there are still Debuggers + * observing this global and who are tracking allocations. + */ + static void removeAllocationsTracking(GlobalObject& global); + + /* + * Add or remove allocations tracking for all debuggees. + */ + [[nodiscard]] bool addAllocationsTrackingForAllDebuggees(JSContext* cx); + void removeAllocationsTrackingForAllDebuggees(); + + /* + * If this Debugger has a onNewGlobalObject handler, then + * this link is inserted into the list headed by + * JSRuntime::onNewGlobalObjectWatchers. + */ + mozilla::DoublyLinkedListElement<Debugger> onNewGlobalObjectWatchersLink; + + /* + * If this Debugger has a onGarbageCollection handler, then + * this link is inserted into the list headed by + * JSRuntime::onGarbageCollectionWatchers. + */ + mozilla::DoublyLinkedListElement<Debugger> onGarbageCollectionWatchersLink; + + /* + * Map from stack frames that are currently on the stack to Debugger.Frame + * instances. + * + * The keys are always live stack frames. We drop them from this map as + * soon as they leave the stack (see slowPathOnLeaveFrame) and in + * removeDebuggee. + * + * We don't trace the keys of this map (the frames are on the stack and + * thus necessarily live), but we do trace the values. It's like a WeakMap + * that way, but since stack frames are not gc-things, the implementation + * has to be different. + */ + typedef HashMap<AbstractFramePtr, HeapPtr<DebuggerFrame*>, + DefaultHasher<AbstractFramePtr>, ZoneAllocPolicy> + FrameMap; + FrameMap frames; + + /* + * Map from generator objects to their Debugger.Frame instances. + * + * When a Debugger.Frame is created for a generator frame, it is added to + * this map and remains there for the lifetime of the generator, whether + * that frame is on the stack at the moment or not. This is in addition to + * the entry in `frames` that exists as long as the generator frame is on + * the stack. + * + * We need to keep the Debugger.Frame object alive to deliver it to the + * 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 realm that is a debuggee of + * the Debugger.Frame's owner. + * + * 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.) + * + * An entry in this table exists if and only if the Debugger.Frame's + * GENERATOR_INFO_SLOT is set. + */ + typedef DebuggerWeakMap<AbstractGeneratorObject, DebuggerFrame> + GeneratorWeakMap; + GeneratorWeakMap generatorFrames; + + // An ephemeral map from BaseScript* to Debugger.Script instances. + using ScriptWeakMap = DebuggerWeakMap<BaseScript, DebuggerScript>; + ScriptWeakMap scripts; + + using BaseScriptVector = JS::GCVector<BaseScript*>; + + // The map from debuggee source script objects to their Debugger.Source + // instances. + typedef DebuggerWeakMap<ScriptSourceObject, DebuggerSource, true> + SourceWeakMap; + SourceWeakMap sources; + + // The map from debuggee objects to their Debugger.Object instances. + typedef DebuggerWeakMap<JSObject, DebuggerObject> ObjectWeakMap; + ObjectWeakMap objects; + + // The map from debuggee Envs to Debugger.Environment instances. + typedef DebuggerWeakMap<JSObject, DebuggerEnvironment> EnvironmentWeakMap; + EnvironmentWeakMap environments; + + // The map from WasmInstanceObjects to synthesized Debugger.Script + // instances. + typedef DebuggerWeakMap<WasmInstanceObject, DebuggerScript> + WasmInstanceScriptWeakMap; + WasmInstanceScriptWeakMap wasmInstanceScripts; + + // The map from WasmInstanceObjects to synthesized Debugger.Source + // instances. + typedef DebuggerWeakMap<WasmInstanceObject, DebuggerSource> + WasmInstanceSourceWeakMap; + WasmInstanceSourceWeakMap wasmInstanceSources; + + class QueryBase; + class ScriptQuery; + class SourceQuery; + class ObjectQuery; + + enum class FromSweep { No, Yes }; + + [[nodiscard]] bool addDebuggeeGlobal(JSContext* cx, + Handle<GlobalObject*> obj); + void removeDebuggeeGlobal(JS::GCContext* gcx, GlobalObject* global, + WeakGlobalObjectSet::Enum* debugEnum, + FromSweep fromSweep); + + /* + * Handle the result of a hook that is expected to return a resumption + * value <https://wiki.mozilla.org/Debugger#Resumption_Values>. This is + * called when we return from a debugging hook to debuggee code. + * + * If `success` is false, the hook failed. If an exception is pending in + * ar.context(), attempt to handle it via the uncaught exception hook, + * otherwise report it to the AutoRealm's global. + * + * If `success` is true, there must be no exception pending in ar.context(). + * `rv` may be: + * + * undefined - Set `resultMode` to `ResumeMode::Continue` to continue + * execution normally. + * + * {return: value} or {throw: value} - Call unwrapDebuggeeValue to + * unwrap `value`. Store the result in `vp` and set `resultMode` to + * `ResumeMode::Return` or `ResumeMode::Throw`. The interpreter + * will force the current frame to return or throw an exception. + * + * null - Set `resultMode` to `ResumeMode::Terminate` to terminate the + * debuggee with an uncatchable error. + * + * anything else - Make a new TypeError the pending exception and + * attempt to handle it with the uncaught exception handler. + */ + [[nodiscard]] bool processHandlerResult( + JSContext* cx, bool success, HandleValue rv, AbstractFramePtr frame, + jsbytecode* pc, ResumeMode& resultMode, MutableHandleValue vp); + + [[nodiscard]] bool processParsedHandlerResult( + JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc, bool success, + ResumeMode resumeMode, HandleValue value, ResumeMode& resultMode, + MutableHandleValue vp); + + /** + * Given a resumption return value from a hook, parse and validate it based + * on the given frame, and split the result into a ResumeMode and Value. + */ + [[nodiscard]] bool prepareResumption(JSContext* cx, AbstractFramePtr frame, + const jsbytecode* pc, + ResumeMode& resumeMode, + MutableHandleValue vp); + + /** + * If there is a pending exception and a handler, call the handler with the + * exception so that it can attempt to resolve the error. + */ + [[nodiscard]] bool callUncaughtExceptionHandler(JSContext* cx, + MutableHandleValue vp); + + /** + * If the context has a pending exception, report it to the current global. + */ + void reportUncaughtException(JSContext* cx); + + /* + * Call the uncaught exception handler if there is one, returning true + * if it handled the error, or false otherwise. + */ + [[nodiscard]] bool handleUncaughtException(JSContext* cx); + + GlobalObject* unwrapDebuggeeArgument(JSContext* cx, const Value& v); + + static void traceObject(JSTracer* trc, JSObject* obj); + + void trace(JSTracer* trc); + + void traceForMovingGC(JSTracer* trc); + void traceCrossCompartmentEdges(JSTracer* tracer); + + private: + template <typename F> + void forEachWeakMap(const F& f); + + [[nodiscard]] static bool getHookImpl(JSContext* cx, const CallArgs& args, + Debugger& dbg, Hook which); + [[nodiscard]] static bool setHookImpl(JSContext* cx, const CallArgs& args, + Debugger& dbg, Hook which); + + [[nodiscard]] static bool getGarbageCollectionHook(JSContext* cx, + const CallArgs& args, + Debugger& dbg); + [[nodiscard]] static bool setGarbageCollectionHook(JSContext* cx, + const CallArgs& args, + Debugger& dbg); + + static bool isCompilableUnit(JSContext* cx, unsigned argc, Value* vp); + static bool recordReplayProcessKind(JSContext* cx, unsigned argc, Value* vp); + static bool construct(JSContext* cx, unsigned argc, Value* vp); + + struct CallData; + + static const JSPropertySpec properties[]; + static const JSFunctionSpec methods[]; + static const JSFunctionSpec static_methods[]; + + /** + * Suspend the DebuggerFrame, clearing on-stack data but leaving it linked + * with the AbstractGeneratorObject so it can be re-used later. + */ + static void suspendGeneratorDebuggerFrames(JSContext* cx, + AbstractFramePtr frame); + + /** + * Terminate the DebuggerFrame, clearing all data associated with the frame + * so that it cannot be used to introspect stack frame data. + */ + static void terminateDebuggerFrames(JSContext* cx, AbstractFramePtr frame); + + /** + * Terminate a given DebuggerFrame, removing all internal state and all + * references to the frame from the Debugger itself. If the frame is being + * terminated while 'frames' or 'generatorFrames' are being iterated, pass a + * pointer to the iteration Enum to remove the entry and ensure that iteration + * behaves properly. + * + * The AbstractFramePtr may be omited in a call so long as it is either + * called again later with the correct 'frame', or the frame itself has never + * had on-stack data or a 'frames' entry and has never had an onStep handler. + */ + static void terminateDebuggerFrame( + JS::GCContext* gcx, Debugger* dbg, DebuggerFrame* dbgFrame, + AbstractFramePtr frame, FrameMap::Enum* maybeFramesEnum = nullptr, + GeneratorWeakMap::Enum* maybeGeneratorFramesEnum = nullptr); + + static bool updateExecutionObservabilityOfFrames( + JSContext* cx, const DebugAPI::ExecutionObservableSet& obs, + IsObserving observing); + static bool updateExecutionObservabilityOfScripts( + JSContext* cx, const DebugAPI::ExecutionObservableSet& obs, + IsObserving observing); + static bool updateExecutionObservability( + JSContext* cx, DebugAPI::ExecutionObservableSet& obs, + IsObserving observing); + + template <typename FrameFn /* void (Debugger*, DebuggerFrame*) */> + static void forEachOnStackDebuggerFrame(AbstractFramePtr frame, FrameFn fn); + template <typename FrameFn /* void (Debugger*, DebuggerFrame*) */> + static void forEachOnStackOrSuspendedDebuggerFrame(JSContext* cx, + AbstractFramePtr frame, + FrameFn fn); + + /* + * Return a vector containing all Debugger.Frame instances referring to + * |frame|. |global| is |frame|'s global object; if nullptr or omitted, we + * compute it ourselves from |frame|. + */ + using DebuggerFrameVector = GCVector<DebuggerFrame*, 0, SystemAllocPolicy>; + [[nodiscard]] static bool getDebuggerFrames( + AbstractFramePtr frame, MutableHandle<DebuggerFrameVector> frames); + + public: + // Public for DebuggerScript::setBreakpoint. + [[nodiscard]] static bool ensureExecutionObservabilityOfScript( + JSContext* cx, JSScript* script); + + // Whether the Debugger instance needs to observe all non-AOT JS + // execution of its debugees. + IsObserving observesAllExecution() const; + + // Whether the Debugger instance needs to observe AOT-compiled asm.js + // execution of its debuggees. + IsObserving observesAsmJS() const; + + // Whether the Debugger instance needs to observe compiled Wasm + // execution of its debuggees. + IsObserving observesWasm() const; + + // Whether the Debugger instance needs to observe coverage of any JavaScript + // execution. + IsObserving observesCoverage() const; + + // Whether the Debugger instance needs to observe native call invocations. + IsObserving observesNativeCalls() const; + + private: + [[nodiscard]] static bool ensureExecutionObservabilityOfFrame( + JSContext* cx, AbstractFramePtr frame); + [[nodiscard]] static bool ensureExecutionObservabilityOfRealm( + JSContext* cx, JS::Realm* realm); + + static bool hookObservesAllExecution(Hook which); + + [[nodiscard]] bool updateObservesAllExecutionOnDebuggees( + JSContext* cx, IsObserving observing); + [[nodiscard]] bool updateObservesCoverageOnDebuggees(JSContext* cx, + IsObserving observing); + void updateObservesAsmJSOnDebuggees(IsObserving observing); + void updateObservesWasmOnDebuggees(IsObserving observing); + + JSObject* getHook(Hook hook) const; + bool hasAnyLiveHooks() const; + inline bool isHookCallAllowed(JSContext* cx) const; + + static void slowPathPromiseHook(JSContext* cx, Hook hook, + Handle<PromiseObject*> promise); + + template <typename HookIsEnabledFun /* bool (Debugger*) */, + typename FireHookFun /* void (Debugger*) */> + static void dispatchQuietHook(JSContext* cx, HookIsEnabledFun hookIsEnabled, + FireHookFun fireHook); + template < + typename HookIsEnabledFun /* bool (Debugger*) */, typename FireHookFun /* bool (Debugger*, ResumeMode&, MutableHandleValue) */> + [[nodiscard]] static bool dispatchResumptionHook( + JSContext* cx, AbstractFramePtr frame, HookIsEnabledFun hookIsEnabled, + FireHookFun fireHook); + + template <typename RunImpl /* bool () */> + [[nodiscard]] bool enterDebuggerHook(JSContext* cx, RunImpl runImpl) { + if (!isHookCallAllowed(cx)) { + return true; + } + + AutoRealm ar(cx, object); + + if (!runImpl()) { + // We do not want errors within one hook to effect errors in other hooks, + // so the only errors that we allow to propagate out of a debugger hook + // are OOM errors and general terminations. + if (!cx->isExceptionPending() || cx->isThrowingOutOfMemory()) { + return false; + } + + reportUncaughtException(cx); + } + MOZ_ASSERT(!cx->isExceptionPending()); + return true; + } + + [[nodiscard]] bool fireDebuggerStatement(JSContext* cx, + ResumeMode& resumeMode, + MutableHandleValue vp); + [[nodiscard]] bool fireExceptionUnwind(JSContext* cx, HandleValue exc, + ResumeMode& resumeMode, + MutableHandleValue vp); + [[nodiscard]] bool fireEnterFrame(JSContext* cx, ResumeMode& resumeMode, + MutableHandleValue vp); + [[nodiscard]] bool fireNativeCall(JSContext* cx, const CallArgs& args, + CallReason reason, ResumeMode& resumeMode, + MutableHandleValue vp); + [[nodiscard]] bool fireNewGlobalObject(JSContext* cx, + Handle<GlobalObject*> global); + [[nodiscard]] bool firePromiseHook(JSContext* cx, Hook hook, + HandleObject promise); + + DebuggerScript* newVariantWrapper(JSContext* cx, + Handle<DebuggerScriptReferent> referent) { + return newDebuggerScript(cx, referent); + } + DebuggerSource* newVariantWrapper(JSContext* cx, + Handle<DebuggerSourceReferent> referent) { + return newDebuggerSource(cx, referent); + } + + /* + * Helper function to help wrap Debugger objects whose referents may be + * variants. Currently Debugger.Script and Debugger.Source referents may + * be variants. + * + * Prefer using wrapScript, wrapWasmScript, wrapSource, and wrapWasmSource + * whenever possible. + */ + template <typename ReferentType, typename Map> + typename Map::WrapperType* wrapVariantReferent( + JSContext* cx, Map& map, + Handle<typename Map::WrapperType::ReferentVariant> referent); + DebuggerScript* wrapVariantReferent(JSContext* cx, + Handle<DebuggerScriptReferent> referent); + DebuggerSource* wrapVariantReferent(JSContext* cx, + Handle<DebuggerSourceReferent> referent); + + /* + * Allocate and initialize a Debugger.Script instance whose referent is + * |referent|. + */ + DebuggerScript* newDebuggerScript(JSContext* cx, + Handle<DebuggerScriptReferent> referent); + + /* + * Allocate and initialize a Debugger.Source instance whose referent is + * |referent|. + */ + DebuggerSource* newDebuggerSource(JSContext* cx, + Handle<DebuggerSourceReferent> referent); + + /* + * Receive a "new script" event from the engine. A new script was compiled + * or deserialized. + */ + [[nodiscard]] bool fireNewScript( + JSContext* cx, Handle<DebuggerScriptReferent> scriptReferent); + + /* + * Receive a "garbage collection" event from the engine. A GC cycle with the + * given data was recently completed. + */ + [[nodiscard]] bool fireOnGarbageCollectionHook( + JSContext* cx, const JS::dbg::GarbageCollectionEvent::Ptr& gcData); + + inline Breakpoint* firstBreakpoint() const; + + [[nodiscard]] static bool replaceFrameGuts(JSContext* cx, + AbstractFramePtr from, + AbstractFramePtr to, + ScriptFrameIter& iter); + + public: + Debugger(JSContext* cx, NativeObject* dbg); + ~Debugger(); + + inline const js::HeapPtr<NativeObject*>& toJSObject() const; + inline js::HeapPtr<NativeObject*>& toJSObjectRef(); + static inline Debugger* fromJSObject(const JSObject* obj); + +#ifdef DEBUG + static bool isChildJSObject(JSObject* obj); +#endif + + Zone* zone() const { return toJSObject()->zone(); } + + bool hasMemory() const; + DebuggerMemory& memory() const; + + WeakGlobalObjectSet::Range allDebuggees() const { return debuggees.all(); } + +#ifdef DEBUG + static bool isDebuggerCrossCompartmentEdge(JSObject* obj, + const js::gc::Cell* cell); +#endif + + static bool hasLiveHook(GlobalObject* global, Hook which); + + /*** Functions for use by Debugger.cpp. *********************************/ + + inline bool observesEnterFrame() const; + inline bool observesNewScript() const; + inline bool observesNewGlobalObject() const; + inline bool observesGlobal(GlobalObject* global) const; + bool observesFrame(AbstractFramePtr frame) const; + bool observesFrame(const FrameIter& iter) const; + bool observesScript(JSScript* script) const; + bool observesWasm(wasm::Instance* instance) const; + + /* + * If env is nullptr, call vp->setNull() and return true. Otherwise, find + * or create a Debugger.Environment object for the given Env. On success, + * store the Environment object in *vp and return true. + */ + [[nodiscard]] bool wrapEnvironment(JSContext* cx, Handle<Env*> env, + MutableHandleValue vp); + [[nodiscard]] bool wrapEnvironment( + JSContext* cx, Handle<Env*> env, + MutableHandle<DebuggerEnvironment*> result); + + /* + * Like cx->compartment()->wrap(cx, vp), but for the debugger realm. + * + * Preconditions: *vp is a value from a debuggee realm; cx is in the + * debugger's compartment. + * + * If *vp is an object, this produces a (new or existing) Debugger.Object + * wrapper for it. Otherwise this is the same as Compartment::wrap. + * + * If *vp is a magic JS_OPTIMIZED_OUT value, this produces a plain object + * of the form { optimizedOut: true }. + * + * If *vp is a magic JS_MISSING_ARGUMENTS value signifying missing + * arguments, this produces a plain object of the form { missingArguments: + * true }. + * + * If *vp is a magic JS_UNINITIALIZED_LEXICAL value signifying an + * unaccessible uninitialized binding, this produces a plain object of the + * form { uninitialized: true }. + */ + [[nodiscard]] bool wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp); + [[nodiscard]] bool wrapDebuggeeObject(JSContext* cx, HandleObject obj, + MutableHandle<DebuggerObject*> result); + [[nodiscard]] bool wrapNullableDebuggeeObject( + JSContext* cx, HandleObject obj, MutableHandle<DebuggerObject*> result); + + /* + * Unwrap a Debug.Object, without rewrapping it for any particular debuggee + * compartment. + * + * Preconditions: cx is in the debugger compartment. *vp is a value in that + * compartment. (*vp should be a "debuggee value", meaning it is the + * debugger's reflection of a value in the debuggee.) + * + * If *vp is a Debugger.Object, store the referent in *vp. Otherwise, if *vp + * is an object, throw a TypeError, because it is not a debuggee + * value. Otherwise *vp is a primitive, so leave it alone. + * + * When passing values from the debuggee to the debugger: + * enter debugger compartment; + * call wrapDebuggeeValue; // compartment- and debugger-wrapping + * + * When passing values from the debugger to the debuggee: + * call unwrapDebuggeeValue; // debugger-unwrapping + * enter debuggee realm; + * call cx->compartment()->wrap; // compartment-rewrapping + * + * (Extreme nerd sidebar: Unwrapping happens in two steps because there are + * two different kinds of symmetry at work: regardless of which direction + * we're going, we want any exceptions to be created and thrown in the + * debugger compartment--mirror symmetry. But compartment wrapping always + * happens in the target compartment--rotational symmetry.) + */ + [[nodiscard]] bool unwrapDebuggeeValue(JSContext* cx, MutableHandleValue vp); + [[nodiscard]] bool unwrapDebuggeeObject(JSContext* cx, + MutableHandleObject obj); + [[nodiscard]] bool unwrapPropertyDescriptor( + JSContext* cx, HandleObject obj, MutableHandle<PropertyDescriptor> desc); + + /* + * Store the Debugger.Frame object for iter in *vp/result. + * + * If this Debugger does not already have a Frame object for the frame + * `iter` points to, a new Frame object is created, and `iter`'s private + * data is copied into it. + */ + [[nodiscard]] bool getFrame(JSContext* cx, const FrameIter& iter, + MutableHandleValue vp); + [[nodiscard]] bool getFrame(JSContext* cx, + MutableHandle<DebuggerFrame*> result); + [[nodiscard]] bool getFrame(JSContext* cx, const FrameIter& iter, + MutableHandle<DebuggerFrame*> result); + [[nodiscard]] bool getFrame(JSContext* cx, + Handle<AbstractGeneratorObject*> genObj, + MutableHandle<DebuggerFrame*> result); + + /* + * Return the Debugger.Script object for |script|, or create a new one if + * needed. The context |cx| must be in the debugger realm; |script| must be + * a script in a debuggee realm. + */ + DebuggerScript* wrapScript(JSContext* cx, Handle<BaseScript*> script); + + /* + * Return the Debugger.Script object for |wasmInstance| (the toplevel + * script), synthesizing a new one if needed. The context |cx| must be in + * the debugger compartment; |wasmInstance| must be a WasmInstanceObject in + * the debuggee realm. + */ + DebuggerScript* wrapWasmScript(JSContext* cx, + Handle<WasmInstanceObject*> wasmInstance); + + /* + * Return the Debugger.Source object for |source|, or create a new one if + * needed. The context |cx| must be in the debugger compartment; |source| + * must be a script source object in a debuggee realm. + */ + DebuggerSource* wrapSource(JSContext* cx, + js::Handle<ScriptSourceObject*> source); + + /* + * Return the Debugger.Source object for |wasmInstance| (the entire module), + * synthesizing a new one if needed. The context |cx| must be in the + * debugger compartment; |wasmInstance| must be a WasmInstanceObject in the + * debuggee realm. + */ + DebuggerSource* wrapWasmSource(JSContext* cx, + Handle<WasmInstanceObject*> wasmInstance); + + DebuggerDebuggeeLink* getDebuggeeLink(); + + private: + Debugger(const Debugger&) = delete; + Debugger& operator=(const Debugger&) = delete; +}; + +// Specialize InternalBarrierMethods so we can have WeakHeapPtr<Debugger*>. +template <> +struct InternalBarrierMethods<Debugger*> { + static bool isMarkable(Debugger* dbg) { return dbg->toJSObject(); } + + static void postBarrier(Debugger** vp, Debugger* prev, Debugger* next) {} + + static void readBarrier(Debugger* dbg) { + InternalBarrierMethods<JSObject*>::readBarrier(dbg->toJSObject()); + } + +#ifdef DEBUG + static void assertThingIsNotGray(Debugger* dbg) {} +#endif +}; + +/** + * 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 + * the Debugger API to notify the user of certain events. For each event type, + * we define a separate subclass of Handler. + * + * When a reflection object accepts a Handler, it calls its 'hold' method; and + * if the Handler is replaced by another, or the reflection object is finalized, + * the reflection object calls the Handler's 'drop' method. The reflection + * object does not otherwise manage the Handler's lifetime, say, by calling its + * destructor or freeing its memory. A simple Handler implementation might have + * an empty 'hold' method, and have its 'drop' method delete the Handler. A more + * complex Handler might process many kinds of events, and thus inherit from + * many Handler subclasses and be held by many reflection objects + * simultaneously; a handler like this could use 'hold' and 'drop' to manage a + * reference count. + * + * To support SpiderMonkey's memory use tracking, 'hold' and 'drop' also require + * a pointer to the owning reflection object, so that the Holder implementation + * can properly report changes in ownership to functions using the + * js::gc::MemoryUse categories. + */ +struct Handler { + virtual ~Handler() = default; + + /* + * If this Handler is a reference to a callable JSObject, return that + * JSObject. Otherwise, this method returns nullptr. + * + * The JavaScript getters for handler properties on reflection objects use + * this method to obtain the callable the handler represents. When a Handler's + * 'object' method returns nullptr, that handler is simply not visible to + * JavaScript. + */ + virtual JSObject* object() const = 0; + + /* Report that this Handler is now held by owner. See comment above. */ + virtual void hold(JSObject* owner) = 0; + + /* Report that this Handler is no longer held by owner. See comment above. */ + virtual void drop(JS::GCContext* gcx, JSObject* owner) = 0; + + /* + * Trace the reference to the handler. This method will be called by the + * reflection object holding this Handler whenever the former is traced. + */ + virtual void trace(JSTracer* tracer) = 0; + + /* Allocation size in bytes for memory accounting purposes. */ + virtual size_t allocSize() const = 0; +}; + +class JSBreakpointSite; +class WasmBreakpointSite; + +/** + * Breakpoint GC rules: + * + * BreakpointSites and Breakpoints are owned by the code in which they are set. + * Tracing a JSScript or WasmInstance traces all BreakpointSites set in it, + * which traces all Breakpoints; and if the code is garbage collected, the + * BreakpointSite and the Breakpoints set at it are freed as well. Doing so is + * not observable to JS, since the handlers would never fire, and there is no + * way to enumerate all breakpoints without specifying a specific script, in + * which case it must not have been GC'd. + * + * Although BreakpointSites and Breakpoints are not GC things, they should be + * treated as belonging to the code's compartment. This means that the + * BreakpointSite concrete subclasses' pointers to the code are not + * cross-compartment references, but a Breakpoint's pointers to its handler and + * owning Debugger are cross-compartment references, and go through + * cross-compartment wrappers. + */ + +/** + * A location in a JSScript or WasmInstance at which we have breakpoints. A + * BreakpointSite owns a linked list of all the breakpoints set at its location. + * In general, this list contains breakpoints set by multiple Debuggers in + * various compartments. + * + * BreakpointSites are created only as needed, for locations at which + * breakpoints are currently set. When the last breakpoint is removed from a + * location, the BreakpointSite is removed as well. + * + * This is an abstract base class, with subclasses specialized for the different + * sorts of code a breakpoint might be set in. JSBreakpointSite manages sites in + * JSScripts, and WasmBreakpointSite manages sites in WasmInstances. + */ +class BreakpointSite { + friend class DebugAPI; + friend class Breakpoint; + friend class Debugger; + + private: + template <typename T> + struct SiteLinkAccess { + static mozilla::DoublyLinkedListElement<T>& Get(T* aThis) { + return aThis->siteLink; + } + }; + + // List of all js::Breakpoints at this instruction. + using BreakpointList = + mozilla::DoublyLinkedList<js::Breakpoint, SiteLinkAccess<js::Breakpoint>>; + BreakpointList breakpoints; + + protected: + BreakpointSite() = default; + virtual ~BreakpointSite() = default; + void finalize(JS::GCContext* gcx); + virtual gc::Cell* owningCell() = 0; + + public: + Breakpoint* firstBreakpoint() const; + bool hasBreakpoint(Breakpoint* bp); + + bool isEmpty() const; + virtual void trace(JSTracer* trc); + virtual void remove(JS::GCContext* gcx) = 0; + void destroyIfEmpty(JS::GCContext* gcx) { + if (isEmpty()) { + remove(gcx); + } + } + virtual Realm* realm() const = 0; +}; + +/* + * A breakpoint set at a given BreakpointSite, indicating the owning debugger + * and the handler object. A Breakpoint is a member of two linked lists: its + * owning debugger's list and its site's list. + */ +class Breakpoint { + friend class DebugAPI; + friend class Debugger; + friend class BreakpointSite; + + public: + /* Our owning debugger. */ + Debugger* const debugger; + + /** + * A cross-compartment wrapper for our owning debugger's object, a CCW in the + * code's compartment to the Debugger object in its own compartment. Holding + * this lets the GC know about the effective cross-compartment reference from + * the code to the debugger; see "Breakpoint GC Rules", above. + * + * This is almost redundant with the `debugger` field, except that we need + * access to our owning `Debugger` regardless of the relative privilege levels + * of debugger and debuggee, regardless of whether we're in the midst of a GC, + * and so on - unwrapping is just too entangled. + */ + const HeapPtr<JSObject*> wrappedDebugger; + + /* The site at which we're inserted. */ + BreakpointSite* const site; + + private: + /** + * The breakpoint handler object, via a cross-compartment wrapper in the + * code's compartment. + * + * Although eventually we would like this to be a `js::Handler` instance, for + * now it is just cross-compartment wrapper for the JS object supplied to + * `setBreakpoint`, hopefully with a callable `hit` property. + */ + const HeapPtr<JSObject*> handler; + + /** + * Link elements for each list this breakpoint can be in. + */ + mozilla::DoublyLinkedListElement<Breakpoint> debuggerLink; + mozilla::DoublyLinkedListElement<Breakpoint> siteLink; + + void trace(JSTracer* trc); + + public: + Breakpoint(Debugger* debugger, HandleObject wrappedDebugger, + BreakpointSite* site, HandleObject handler); + + enum MayDestroySite { False, True }; + + /** + * Unlink this breakpoint from its Debugger's and and BreakpointSite's lists, + * and free its memory. + * + * This is the low-level primitive shared by breakpoint removal and script + * finalization code. It is only concerned with cleaning up this Breakpoint; + * it does not check for now-empty BreakpointSites, unneeded DebugScripts, or + * the like. + */ + void delete_(JS::GCContext* gcx); + + /** + * Remove this breakpoint. Unlink it from its Debugger's and BreakpointSite's + * lists, and if the BreakpointSite is now empty, clean that up and update JIT + * code as necessary. + */ + void remove(JS::GCContext* gcx); + + Breakpoint* nextInDebugger(); + Breakpoint* nextInSite(); + JSObject* getHandler() const { return handler; } +}; + +class JSBreakpointSite : public BreakpointSite { + public: + const HeapPtr<JSScript*> script; + jsbytecode* const pc; + + public: + JSBreakpointSite(JSScript* script, jsbytecode* pc); + + void trace(JSTracer* trc) override; + void delete_(JS::GCContext* gcx); + void remove(JS::GCContext* gcx) override; + Realm* realm() const override; + + private: + gc::Cell* owningCell() override; +}; + +class WasmBreakpointSite : public BreakpointSite { + public: + const HeapPtr<WasmInstanceObject*> instanceObject; + uint32_t offset; + + public: + WasmBreakpointSite(WasmInstanceObject* instanceObject, uint32_t offset); + + void trace(JSTracer* trc) override; + void delete_(JS::GCContext* gcx); + void remove(JS::GCContext* gcx) override; + Realm* realm() const override; + + private: + gc::Cell* owningCell() override; +}; + +Breakpoint* Debugger::firstBreakpoint() const { + if (breakpoints.isEmpty()) { + return nullptr; + } + return &(*breakpoints.begin()); +} + +const js::HeapPtr<NativeObject*>& Debugger::toJSObject() const { + MOZ_ASSERT(object); + return object; +} + +js::HeapPtr<NativeObject*>& Debugger::toJSObjectRef() { + MOZ_ASSERT(object); + return object; +} + +bool Debugger::observesEnterFrame() const { return getHook(OnEnterFrame); } + +bool Debugger::observesNewScript() const { return getHook(OnNewScript); } + +bool Debugger::observesNewGlobalObject() const { + return getHook(OnNewGlobalObject); +} + +bool Debugger::observesGlobal(GlobalObject* global) const { + WeakHeapPtr<GlobalObject*> debuggee(global); + return debuggees.has(debuggee); +} + +[[nodiscard]] bool ReportObjectRequired(JSContext* cx); + +JSObject* IdVectorToArray(JSContext* cx, HandleIdVector ids); +bool IsInterpretedNonSelfHostedFunction(JSFunction* fun); +JSScript* GetOrCreateFunctionScript(JSContext* cx, HandleFunction fun); +ArrayObject* GetFunctionParameterNamesArray(JSContext* cx, HandleFunction fun); +bool ValueToIdentifier(JSContext* cx, HandleValue v, MutableHandleId id); +bool ValueToStableChars(JSContext* cx, const char* fnname, HandleValue value, + JS::AutoStableStringChars& stableChars); +bool ParseEvalOptions(JSContext* cx, HandleValue value, EvalOptions& options); + +Result<Completion> DebuggerGenericEval( + JSContext* cx, const mozilla::Range<const char16_t> chars, + HandleObject bindings, const EvalOptions& options, Debugger* dbg, + HandleObject envArg, FrameIter* iter); + +bool ParseResumptionValue(JSContext* cx, HandleValue rval, + ResumeMode& resumeMode, MutableHandleValue vp); + +#define JS_DEBUG_PSG(Name, Getter) \ + JS_PSG(Name, CallData::ToNative<&CallData::Getter>, 0) + +#define JS_DEBUG_PSGS(Name, Getter, Setter) \ + JS_PSGS(Name, CallData::ToNative<&CallData::Getter>, \ + CallData::ToNative<&CallData::Setter>, 0) + +#define JS_DEBUG_FN(Name, Method, NumArgs) \ + JS_FN(Name, CallData::ToNative<&CallData::Method>, NumArgs, 0) + +} /* namespace js */ + +#endif /* debugger_Debugger_h */ |