/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 vm_Debugger_h #define vm_Debugger_h #include "mozilla/GuardObjects.h" #include "mozilla/LinkedList.h" #include "mozilla/Range.h" #include "mozilla/Vector.h" #include "jsclist.h" #include "jscntxt.h" #include "jscompartment.h" #include "jsweakmap.h" #include "jswrapper.h" #include "builtin/Promise.h" #include "ds/TraceableFifo.h" #include "gc/Barrier.h" #include "js/Debug.h" #include "js/GCVariant.h" #include "js/HashTable.h" #include "vm/GlobalObject.h" #include "vm/SavedStacks.h" #include "wasm/WasmJS.h" enum JSTrapStatus { JSTRAP_ERROR, JSTRAP_CONTINUE, JSTRAP_RETURN, JSTRAP_THROW, JSTRAP_LIMIT }; namespace js { class Breakpoint; class DebuggerMemory; class WasmInstanceObject; typedef HashSet, RuntimeAllocPolicy> WeakGlobalObjectSet; /* * 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. * * The purpose of this is to allow the garbage collector to easily find edges * from debuggee object compartments to debugger compartments when calculating * the compartment groups. Note that these edges are the inverse of the edges * stored in the cross compartment map. * * 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 it results in a * simpler and faster implementation. * * 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. * * Also note that keys in these weakmaps can be in any compartment, debuggee or * not, because they cannot be deleted when a compartment is no longer a * debuggee: the values need to maintain object identity across add/remove/add * transitions. */ template class DebuggerWeakMap : private WeakMap, HeapPtr, MovableCellHasher>> { private: typedef HeapPtr Key; typedef HeapPtr Value; typedef HashMap, RuntimeAllocPolicy> CountMap; CountMap zoneCounts; JSCompartment* compartment; public: typedef WeakMap> Base; explicit DebuggerWeakMap(JSContext* cx) : Base(cx), zoneCounts(cx->runtime()), compartment(cx->compartment()) { } public: /* Expose those parts of HashMap public interface that are used by Debugger methods. */ typedef typename Base::Entry Entry; typedef typename Base::Ptr Ptr; typedef typename Base::AddPtr AddPtr; typedef typename Base::Range Range; typedef typename Base::Enum Enum; typedef typename Base::Lookup Lookup; /* Expose WeakMap public interface */ using Base::lookupForAdd; using Base::all; using Base::trace; MOZ_MUST_USE bool init(uint32_t len = 16) { return Base::init(len) && zoneCounts.init(); } template bool relookupOrAdd(AddPtr& p, const KeyInput& k, const ValueInput& v) { MOZ_ASSERT(v->compartment() == this->compartment); MOZ_ASSERT(!k->compartment()->creationOptions().mergeable()); MOZ_ASSERT_IF(!InvisibleKeysOk, !k->compartment()->creationOptions().invisibleToDebugger()); MOZ_ASSERT(!Base::has(k)); if (!incZoneCount(k->zone())) return false; bool ok = Base::relookupOrAdd(p, k, v); if (!ok) decZoneCount(k->zone()); return ok; } void remove(const Lookup& l) { MOZ_ASSERT(Base::has(l)); Base::remove(l); decZoneCount(l->zone()); } public: template void markCrossCompartmentEdges(JSTracer* tracer) { for (Enum e(*static_cast(this)); !e.empty(); e.popFront()) { traceValueEdges(tracer, e.front().value()); Key key = e.front().key(); TraceEdge(tracer, &key, "Debugger WeakMap key"); if (key != e.front().key()) e.rekeyFront(key); key.unsafeSet(nullptr); } } bool hasKeyInZone(JS::Zone* zone) { CountMap::Ptr p = zoneCounts.lookup(zone); MOZ_ASSERT_IF(p.found(), p->value() > 0); return p.found(); } private: /* Override sweep method to also update our edge cache. */ void sweep() { for (Enum e(*static_cast(this)); !e.empty(); e.popFront()) { if (gc::IsAboutToBeFinalized(&e.front().mutableKey())) { decZoneCount(e.front().key()->zone()); e.removeFront(); } } Base::assertEntriesNotAboutToBeFinalized(); } MOZ_MUST_USE bool incZoneCount(JS::Zone* zone) { CountMap::Ptr p = zoneCounts.lookupWithDefault(zone, 0); if (!p) return false; ++p->value(); return true; } void decZoneCount(JS::Zone* zone) { CountMap::Ptr p = zoneCounts.lookup(zone); MOZ_ASSERT(p); MOZ_ASSERT(p->value() > 0); --p->value(); if (p->value() == 0) zoneCounts.remove(zone); } }; class LeaveDebuggeeNoExecute; // Suppresses all debuggee NX checks, i.e., allow all execution. Used to allow // certain whitelisted operations to execute code. // // WARNING // WARNING Do not use this unless you know what you are doing! // WARNING class AutoSuppressDebuggeeNoExecuteChecks { EnterDebuggeeNoExecute** stack_; EnterDebuggeeNoExecute* prev_; public: explicit AutoSuppressDebuggeeNoExecuteChecks(JSContext* cx) { stack_ = &cx->runtime()->noExecuteDebuggerTop; prev_ = *stack_; *stack_ = nullptr; } ~AutoSuppressDebuggeeNoExecuteChecks() { MOZ_ASSERT(!*stack_); *stack_ = prev_; } }; class MOZ_RAII EvalOptions { const char* filename_; unsigned lineno_; public: EvalOptions() : filename_(nullptr), lineno_(1) {} ~EvalOptions(); const char* filename() const { return filename_; } unsigned lineno() const { return lineno_; } MOZ_MUST_USE bool setFilename(JSContext* cx, const char* filename); void setLineno(unsigned lineno) { lineno_ = lineno; } }; /* * Env is the type of what ES5 calls "lexical environments" (runtime activations * of lexical scopes). This is currently just JSObject, and is implemented by * CallObject, LexicalEnvironmentObject, and WithEnvironmentObject, among * others--but environments and objects are really two different concepts. */ typedef JSObject Env; // Either a real JSScript or synthesized. // // If synthesized, the referent is one of the following: // // 1. A WasmInstanceObject, denoting a synthesized toplevel wasm module // script. // 2. A wasm JSFunction, denoting a synthesized wasm function script. // NYI! typedef mozilla::Variant DebuggerScriptReferent; // Either a ScriptSourceObject, for ordinary JS, or a WasmInstanceObject, // denoting the synthesized source of a wasm module. typedef mozilla::Variant DebuggerSourceReferent; class Debugger : private mozilla::LinkedListElement { friend class Breakpoint; friend class DebuggerMemory; friend class SavedStacks; friend class mozilla::LinkedListElement; friend class mozilla::LinkedList; friend bool (::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj); friend bool (::JS::dbg::IsDebugger)(JSObject&); friend bool (::JS::dbg::GetDebuggeeGlobals)(JSContext*, JSObject&, AutoObjectVector&); friend void JS::dbg::onNewPromise(JSContext* cx, HandleObject promise); friend void JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise); friend bool JS::dbg::FireOnGarbageCollectionHook(JSContext* cx, JS::dbg::GarbageCollectionEvent::Ptr&& data); public: enum Hook { OnDebuggerStatement, OnExceptionUnwind, OnNewScript, OnEnterFrame, 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_HOOK_START = JSSLOT_DEBUG_PROTO_STOP, JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount, JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP, JSSLOT_DEBUG_COUNT }; class ExecutionObservableSet { public: typedef HashSet::Range ZoneRange; virtual Zone* singleZone() const { return nullptr; } virtual JSScript* singleScriptForZoneInvalidation() const { return nullptr; } virtual const HashSet* zones() const { return nullptr; } virtual bool shouldRecompileOrInvalidate(JSScript* script) const = 0; virtual bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const = 0; }; // This enum is converted to and compare with bool values; NotObserving // must be 0 and Observing must be 1. enum IsObserving { NotObserving = 0, Observing = 1 }; // Return true if the given compartment is a debuggee of this debugger, // false otherwise. bool isDebuggeeUnbarriered(const JSCompartment* compartment) 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); } bool isEnabled() const { return enabled; } static SavedFrame* getObjectAllocationSite(JSObject& obj); struct AllocationsLogEntry { AllocationsLogEntry(HandleObject frame, double when, const char* className, HandleAtom ctorName, size_t size, bool inNursery) : frame(frame), when(when), className(className), ctorName(ctorName), size(size), inNursery(inNursery) { MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is()); }; HeapPtr frame; double when; const char* className; HeapPtr ctorName; size_t size; bool inNursery; void trace(JSTracer* trc) { TraceNullableEdge(trc, &frame, "Debugger::AllocationsLogEntry::frame"); TraceNullableEdge(trc, &ctorName, "Debugger::AllocationsLogEntry::ctorName"); } }; // Barrier methods so we can have ReadBarriered. static void readBarrier(Debugger* dbg) { InternalBarrierMethods::readBarrier(dbg->object); } static void writeBarrierPost(Debugger** vp, Debugger* prev, Debugger* next) {} private: GCPtrNativeObject 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. */ js::GCPtrObject uncaughtExceptionHook; /* Strong reference. */ bool enabled; bool allowUnobservedAsmJS; // Whether to enable code coverage on the Debuggee. bool collectCoverageInfo; JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */ // The set of GC numbers for which one or more of this Debugger's observed // debuggees participated in. using GCNumberSet = HashSet, RuntimeAllocPolicy>; GCNumberSet observedGCs; using AllocationsLog = js::TraceableFifo; AllocationsLog allocationsLog; bool trackingAllocationSites; double allocationSamplingProbability; size_t maxAllocationsLogLength; bool allocationsLogOverflowed; static const size_t DEFAULT_MAX_LOG_LENGTH = 5000; MOZ_MUST_USE bool appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame, double 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); /* * Return true if the given global is being observed by at least one * Debugger that is tracking allocations. */ static bool isObservedByDebuggerTrackingAllocations(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 enabled and tracking allocations. */ static MOZ_MUST_USE bool addAllocationsTracking(JSContext* cx, Handle 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. */ MOZ_MUST_USE bool addAllocationsTrackingForAllDebuggees(JSContext* cx); void removeAllocationsTrackingForAllDebuggees(); /* * If this Debugger is enabled, and has a onNewGlobalObject handler, then * this link is inserted into the circular list headed by * JSRuntime::onNewGlobalObjectWatchers. Otherwise, this is set to a * singleton cycle. */ JSCList onNewGlobalObjectWatchersLink; /* * 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, DefaultHasher, RuntimeAllocPolicy> FrameMap; FrameMap frames; /* An ephemeral map from JSScript* to Debugger.Script instances. */ typedef DebuggerWeakMap ScriptWeakMap; ScriptWeakMap scripts; /* The map from debuggee source script objects to their Debugger.Source instances. */ typedef DebuggerWeakMap SourceWeakMap; SourceWeakMap sources; /* The map from debuggee objects to their Debugger.Object instances. */ typedef DebuggerWeakMap ObjectWeakMap; ObjectWeakMap objects; /* The map from debuggee Envs to Debugger.Environment instances. */ ObjectWeakMap environments; /* The map from WasmInstanceObjects to synthesized Debugger.Script instances. */ typedef DebuggerWeakMap WasmInstanceWeakMap; WasmInstanceWeakMap wasmInstanceScripts; /* The map from WasmInstanceObjects to synthesized Debugger.Source instances. */ WasmInstanceWeakMap wasmInstanceSources; /* * Keep track of tracelogger last drained identifiers to know if there are * lost events. */ #ifdef NIGHTLY_BUILD uint32_t traceLoggerLastDrainedSize; uint32_t traceLoggerLastDrainedIteration; #endif uint32_t traceLoggerScriptedCallsLastDrainedSize; uint32_t traceLoggerScriptedCallsLastDrainedIteration; class ScriptQuery; class ObjectQuery; MOZ_MUST_USE bool addDebuggeeGlobal(JSContext* cx, Handle obj); void removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global, WeakGlobalObjectSet::Enum* debugEnum); /* * Report and clear the pending exception on ac.context, if any, and return * JSTRAP_ERROR. */ JSTrapStatus reportUncaughtException(mozilla::Maybe& ac); /* * Cope with an error or exception in a debugger hook. * * If callHook is true, then call the uncaughtExceptionHook, if any. If, in * addition, vp is given, then parse the value returned by * uncaughtExceptionHook as a resumption value. * * If there is no uncaughtExceptionHook, or if it fails, report and clear * the pending exception on ac.context and return JSTRAP_ERROR. * * This always calls ac.leave(); ac is a parameter because this method must * do some things in the debugger compartment and some things in the * debuggee compartment. */ JSTrapStatus handleUncaughtException(mozilla::Maybe& ac); JSTrapStatus handleUncaughtException(mozilla::Maybe& ac, MutableHandleValue vp, const mozilla::Maybe& thisVForCheck = mozilla::Nothing(), AbstractFramePtr frame = NullFramePtr()); JSTrapStatus handleUncaughtExceptionHelper(mozilla::Maybe& ac, MutableHandleValue* vp, const mozilla::Maybe& thisVForCheck, AbstractFramePtr frame); /* * Handle the result of a hook that is expected to return a resumption * value . This is called * when we return from a debugging hook to debuggee code. The interpreter wants * a (JSTrapStatus, Value) pair telling it how to proceed. * * Precondition: ac is entered. We are in the debugger compartment. * * Postcondition: This called ac.leave(). See handleUncaughtException. * * If ok is false, the hook failed. If an exception is pending in * ac.context(), return handleUncaughtException(ac, vp, callhook). * Otherwise just return JSTRAP_ERROR. * * If ok is true, there must be no exception pending in ac.context(). rv may be: * undefined - Return JSTRAP_CONTINUE to continue execution normally. * {return: value} or {throw: value} - Call unwrapDebuggeeValue to * unwrap value. Store the result in *vp and return JSTRAP_RETURN * or JSTRAP_THROW. The interpreter will force the current frame to * return or throw an exception. * null - Return JSTRAP_ERROR to terminate the debuggee with an * uncatchable error. * anything else - Make a new TypeError the pending exception and * return handleUncaughtException(ac, vp, callHook). */ JSTrapStatus processHandlerResult(mozilla::Maybe& ac, bool OK, const Value& rv, AbstractFramePtr frame, jsbytecode* pc, MutableHandleValue vp); JSTrapStatus processParsedHandlerResult(mozilla::Maybe& ac, AbstractFramePtr frame, jsbytecode* pc, bool success, JSTrapStatus status, MutableHandleValue vp); JSTrapStatus processParsedHandlerResultHelper(mozilla::Maybe& ac, AbstractFramePtr frame, const mozilla::Maybe& maybeThisv, bool success, JSTrapStatus status, MutableHandleValue vp); bool processResumptionValue(mozilla::Maybe& ac, AbstractFramePtr frame, const mozilla::Maybe& maybeThis, HandleValue rval, JSTrapStatus& statusp, MutableHandleValue vp); GlobalObject* unwrapDebuggeeArgument(JSContext* cx, const Value& v); static void traceObject(JSTracer* trc, JSObject* obj); void trace(JSTracer* trc); static void finalize(FreeOp* fop, JSObject* obj); void markCrossCompartmentEdges(JSTracer* tracer); static const ClassOps classOps_; public: static const Class class_; private: static MOZ_MUST_USE bool getHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which); static MOZ_MUST_USE bool setHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which); static bool getEnabled(JSContext* cx, unsigned argc, Value* vp); static bool setEnabled(JSContext* cx, unsigned argc, Value* vp); static bool getOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp); static bool setOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp); static bool getOnExceptionUnwind(JSContext* cx, unsigned argc, Value* vp); static bool setOnExceptionUnwind(JSContext* cx, unsigned argc, Value* vp); static bool getOnNewScript(JSContext* cx, unsigned argc, Value* vp); static bool setOnNewScript(JSContext* cx, unsigned argc, Value* vp); static bool getOnEnterFrame(JSContext* cx, unsigned argc, Value* vp); static bool setOnEnterFrame(JSContext* cx, unsigned argc, Value* vp); static bool getOnNewGlobalObject(JSContext* cx, unsigned argc, Value* vp); static bool setOnNewGlobalObject(JSContext* cx, unsigned argc, Value* vp); static bool getOnNewPromise(JSContext* cx, unsigned argc, Value* vp); static bool setOnNewPromise(JSContext* cx, unsigned argc, Value* vp); static bool getOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp); static bool setOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp); static bool getUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp); static bool setUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp); static bool getAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp); static bool setAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp); static bool getCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp); static bool setCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp); static bool getMemory(JSContext* cx, unsigned argc, Value* vp); static bool addDebuggee(JSContext* cx, unsigned argc, Value* vp); static bool addAllGlobalsAsDebuggees(JSContext* cx, unsigned argc, Value* vp); static bool removeDebuggee(JSContext* cx, unsigned argc, Value* vp); static bool removeAllDebuggees(JSContext* cx, unsigned argc, Value* vp); static bool hasDebuggee(JSContext* cx, unsigned argc, Value* vp); static bool getDebuggees(JSContext* cx, unsigned argc, Value* vp); static bool getNewestFrame(JSContext* cx, unsigned argc, Value* vp); static bool clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp); static bool findScripts(JSContext* cx, unsigned argc, Value* vp); static bool findObjects(JSContext* cx, unsigned argc, Value* vp); static bool findAllGlobals(JSContext* cx, unsigned argc, Value* vp); static bool makeGlobalObjectReference(JSContext* cx, unsigned argc, Value* vp); static bool setupTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp); static bool drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp); static bool startTraceLogger(JSContext* cx, unsigned argc, Value* vp); static bool endTraceLogger(JSContext* cx, unsigned argc, Value* vp); static bool isCompilableUnit(JSContext* cx, unsigned argc, Value* vp); #ifdef NIGHTLY_BUILD static bool setupTraceLogger(JSContext* cx, unsigned argc, Value* vp); static bool drainTraceLogger(JSContext* cx, unsigned argc, Value* vp); #endif static bool adoptDebuggeeValue(JSContext* cx, unsigned argc, Value* vp); static bool construct(JSContext* cx, unsigned argc, Value* vp); static const JSPropertySpec properties[]; static const JSFunctionSpec methods[]; static const JSFunctionSpec static_methods[]; static void removeFromFrameMapsAndClearBreakpointsIn(JSContext* cx, AbstractFramePtr frame); static bool updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObservableSet& obs, IsObserving observing); static bool updateExecutionObservabilityOfScripts(JSContext* cx, const ExecutionObservableSet& obs, IsObserving observing); static bool updateExecutionObservability(JSContext* cx, ExecutionObservableSet& obs, IsObserving observing); template static void forEachDebuggerFrame(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; static MOZ_MUST_USE bool getDebuggerFrames(AbstractFramePtr frame, MutableHandle frames); public: static MOZ_MUST_USE bool ensureExecutionObservabilityOfOsrFrame(JSContext* cx, InterpreterFrame* frame); // Public for DebuggerScript_setBreakpoint. static MOZ_MUST_USE 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 coverage of any JavaScript // execution. IsObserving observesCoverage() const; private: static MOZ_MUST_USE bool ensureExecutionObservabilityOfFrame(JSContext* cx, AbstractFramePtr frame); static MOZ_MUST_USE bool ensureExecutionObservabilityOfCompartment(JSContext* cx, JSCompartment* comp); static bool hookObservesAllExecution(Hook which); MOZ_MUST_USE bool updateObservesAllExecutionOnDebuggees(JSContext* cx, IsObserving observing); MOZ_MUST_USE bool updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing); void updateObservesAsmJSOnDebuggees(IsObserving observing); JSObject* getHook(Hook hook) const; bool hasAnyLiveHooks(JSRuntime* rt) const; static MOZ_MUST_USE bool slowPathCheckNoExecute(JSContext* cx, HandleScript script); static JSTrapStatus slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame); static MOZ_MUST_USE bool slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok); static JSTrapStatus slowPathOnDebuggerStatement(JSContext* cx, AbstractFramePtr frame); static JSTrapStatus slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame); static void slowPathOnNewScript(JSContext* cx, HandleScript script); static void slowPathOnNewWasmInstance(JSContext* cx, Handle wasmInstance); static void slowPathOnNewGlobalObject(JSContext* cx, Handle global); static MOZ_MUST_USE bool slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame, double when, GlobalObject::DebuggerVector& dbgs); static void slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise); template static JSTrapStatus dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled, FireHookFun fireHook); JSTrapStatus fireDebuggerStatement(JSContext* cx, MutableHandleValue vp); JSTrapStatus fireExceptionUnwind(JSContext* cx, MutableHandleValue vp); JSTrapStatus fireEnterFrame(JSContext* cx, MutableHandleValue vp); JSTrapStatus fireNewGlobalObject(JSContext* cx, Handle global, MutableHandleValue vp); JSTrapStatus firePromiseHook(JSContext* cx, Hook hook, HandleObject promise, MutableHandleValue vp); NativeObject* newVariantWrapper(JSContext* cx, Handle referent) { return newDebuggerScript(cx, referent); } NativeObject* newVariantWrapper(JSContext* cx, Handle 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 JSObject* wrapVariantReferent(JSContext* cx, Map& map, Handle key, Handle referent); JSObject* wrapVariantReferent(JSContext* cx, Handle referent); JSObject* wrapVariantReferent(JSContext* cx, Handle referent); /* * Allocate and initialize a Debugger.Script instance whose referent is * |referent|. */ NativeObject* newDebuggerScript(JSContext* cx, Handle referent); /* * Allocate and initialize a Debugger.Source instance whose referent is * |referent|. */ NativeObject* newDebuggerSource(JSContext* cx, Handle referent); /* * Receive a "new script" event from the engine. A new script was compiled * or deserialized. */ void fireNewScript(JSContext* cx, Handle scriptReferent); /* * Receive a "garbage collection" event from the engine. A GC cycle with the * given data was recently completed. */ void fireOnGarbageCollectionHook(JSContext* cx, const JS::dbg::GarbageCollectionEvent::Ptr& gcData); /* * Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy * its data if we need to make a new Debugger.Frame. */ MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame, const ScriptFrameIter* maybeIter, MutableHandleValue vp); MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame, const ScriptFrameIter* maybeIter, MutableHandleDebuggerFrame result); inline Breakpoint* firstBreakpoint() const; static inline Debugger* fromOnNewGlobalObjectWatchersLink(JSCList* link); static MOZ_MUST_USE bool replaceFrameGuts(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to, ScriptFrameIter& iter); public: Debugger(JSContext* cx, NativeObject* dbg); ~Debugger(); MOZ_MUST_USE bool init(JSContext* cx); inline const js::GCPtrNativeObject& toJSObject() const; inline js::GCPtrNativeObject& toJSObjectRef(); static inline Debugger* fromJSObject(const JSObject* obj); static Debugger* fromChildJSObject(JSObject* obj); bool hasMemory() const; DebuggerMemory& memory() const; WeakGlobalObjectSet::Range allDebuggees() const { return debuggees.all(); } /*********************************** Methods for interaction with the GC. */ /* * A Debugger object is live if: * * the Debugger JSObject is live (Debugger::trace handles this case); OR * * it is in the middle of dispatching an event (the event dispatching * code roots it in this case); OR * * it is enabled, and it is debugging at least one live compartment, * and at least one of the following is true: * - it has a debugger hook installed * - it has a breakpoint set on a live script * - it has a watchpoint set on a live object. * * Debugger::markAllIteratively handles the last case. If it finds any * Debugger objects that are definitely live but not yet marked, it marks * them and returns true. If not, it returns false. */ static void markIncomingCrossCompartmentEdges(JSTracer* tracer); static MOZ_MUST_USE bool markAllIteratively(GCMarker* trc); static void markAll(JSTracer* trc); static void sweepAll(FreeOp* fop); static void detachAllDebuggersFromGlobal(FreeOp* fop, GlobalObject* global); static void findZoneEdges(JS::Zone* v, gc::ZoneComponentFinder& finder); // Checks it the current compartment is allowed to execute code. static inline MOZ_MUST_USE bool checkNoExecute(JSContext* cx, HandleScript script); /* * JSTrapStatus Overview * --------------------- * * The |onEnterFrame|, |onDebuggerStatement|, and |onExceptionUnwind| * methods below return a JSTrapStatus code that indicates how execution * should proceed: * * - JSTRAP_CONTINUE: Continue execution normally. * * - JSTRAP_THROW: Throw an exception. The method has set |cx|'s * pending exception to the value to be thrown. * * - JSTRAP_ERROR: Terminate execution (as is done when a script is terminated * for running too long). The method has cleared |cx|'s pending * exception. * * - JSTRAP_RETURN: Return from the new frame immediately. The method has * set the youngest JS frame's return value appropriately. */ /* * Announce to the debugger that the context has entered a new JavaScript * frame, |frame|. Call whatever hooks have been registered to observe new * frames. */ static inline JSTrapStatus onEnterFrame(JSContext* cx, AbstractFramePtr frame); /* * Announce to the debugger a |debugger;| statement on has been * encountered on the youngest JS frame on |cx|. Call whatever hooks have * been registered to observe this. * * Note that this method is called for all |debugger;| statements, * regardless of the frame's debuggee-ness. */ static inline JSTrapStatus onDebuggerStatement(JSContext* cx, AbstractFramePtr frame); /* * Announce to the debugger that an exception has been thrown and propagated * to |frame|. Call whatever hooks have been registered to observe this. */ static inline JSTrapStatus onExceptionUnwind(JSContext* cx, AbstractFramePtr frame); /* * Announce to the debugger that the thread has exited a JavaScript frame, |frame|. * If |ok| is true, the frame is returning normally; if |ok| is false, the frame * is throwing an exception or terminating. * * Change cx's current exception and |frame|'s return value to reflect the changes * in behavior the hooks request, if any. Return the new error/success value. * * This function may be called twice for the same outgoing frame; only the * first call has any effect. (Permitting double calls simplifies some * cases where an onPop handler's resumption value changes a return to a * throw, or vice versa: we can redirect to a complete copy of the * alternative path, containing its own call to onLeaveFrame.) */ static inline MOZ_MUST_USE bool onLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok); static inline void onNewScript(JSContext* cx, HandleScript script); static inline void onNewWasmInstance(JSContext* cx, Handle wasmInstance); static inline void onNewGlobalObject(JSContext* cx, Handle global); static inline MOZ_MUST_USE bool onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame, double when); static JSTrapStatus onTrap(JSContext* cx, MutableHandleValue vp); static JSTrapStatus onSingleStep(JSContext* cx, MutableHandleValue vp); static MOZ_MUST_USE bool handleBaselineOsr(JSContext* cx, InterpreterFrame* from, jit::BaselineFrame* to); static MOZ_MUST_USE bool handleIonBailout(JSContext* cx, jit::RematerializedFrame* from, jit::BaselineFrame* to); static void handleUnrecoverableIonBailoutError(JSContext* cx, jit::RematerializedFrame* frame); static void propagateForcedReturn(JSContext* cx, AbstractFramePtr frame, HandleValue rval); static bool hasLiveHook(GlobalObject* global, Hook which); static bool inFrameMaps(AbstractFramePtr frame); /************************************* 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; /* * 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. */ MOZ_MUST_USE bool wrapEnvironment(JSContext* cx, Handle env, MutableHandleValue vp); MOZ_MUST_USE bool wrapEnvironment(JSContext* cx, Handle env, MutableHandleDebuggerEnvironment result); /* * Like cx->compartment()->wrap(cx, vp), but for the debugger compartment. * * Preconditions: *vp is a value from a debuggee compartment; 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 JSCompartment::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_OPTIMIZED_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 }. */ MOZ_MUST_USE bool wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp); MOZ_MUST_USE bool wrapDebuggeeObject(JSContext* cx, HandleObject obj, MutableHandleDebuggerObject 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 compartment; * 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.) */ MOZ_MUST_USE bool unwrapDebuggeeValue(JSContext* cx, MutableHandleValue vp); MOZ_MUST_USE bool unwrapDebuggeeObject(JSContext* cx, MutableHandleObject obj); MOZ_MUST_USE bool unwrapPropertyDescriptor(JSContext* cx, HandleObject obj, MutableHandle desc); /* * Store the Debugger.Frame object for frame in *vp. * * Use this if you have already access to a frame pointer without having * to incur the cost of walking the stack. */ MOZ_MUST_USE bool getScriptFrame(JSContext* cx, AbstractFramePtr frame, MutableHandleValue vp) { return getScriptFrameWithIter(cx, frame, nullptr, vp); } /* * Store the Debugger.Frame object for iter in *vp/result. Eagerly copies a * ScriptFrameIter::Data. * * Use this if you had to make a ScriptFrameIter to get the required * frame, in which case the cost of walking the stack has already been * paid. */ MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter, MutableHandleValue vp) { return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp); } MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter, MutableHandleDebuggerFrame result); /* * Set |*status| and |*value| to a (JSTrapStatus, Value) pair reflecting a * standard SpiderMonkey call state: a boolean success value |ok|, a return * value |rv|, and a context |cx| that may or may not have an exception set. * If an exception was pending on |cx|, it is cleared (and |ok| is asserted * to be false). */ static void resultToCompletion(JSContext* cx, bool ok, const Value& rv, JSTrapStatus* status, MutableHandleValue value); /* * Set |*result| to a JavaScript completion value corresponding to |status| * and |value|. |value| should be the return value or exception value, not * wrapped as a debuggee value. |cx| must be in the debugger compartment. */ MOZ_MUST_USE bool newCompletionValue(JSContext* cx, JSTrapStatus status, const Value& value, MutableHandleValue result); /* * Precondition: we are in the debuggee compartment (ac is entered) and ok * is true if the operation in the debuggee compartment succeeded, false on * error or exception. * * Postcondition: we are in the debugger compartment, having called * ac.leave() even if an error occurred. * * On success, a completion value is in vp and ac.context does not have a * pending exception. (This ordinarily returns true even if the ok argument * is false.) */ MOZ_MUST_USE bool receiveCompletionValue(mozilla::Maybe& ac, bool ok, HandleValue val, MutableHandleValue vp); /* * Return the Debugger.Script object for |script|, or create a new one if * needed. The context |cx| must be in the debugger compartment; |script| * must be a script in a debuggee compartment. */ JSObject* wrapScript(JSContext* cx, HandleScript 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 compartment. */ JSObject* wrapWasmScript(JSContext* cx, Handle 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 compartment. */ JSObject* wrapSource(JSContext* cx, js::HandleScriptSource 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 compartment. */ JSObject* wrapWasmSource(JSContext* cx, Handle wasmInstance); private: Debugger(const Debugger&) = delete; Debugger & operator=(const Debugger&) = delete; }; enum class DebuggerEnvironmentType { Declarative, With, Object }; class DebuggerEnvironment : public NativeObject { public: enum { OWNER_SLOT }; static const unsigned RESERVED_SLOTS = 1; static const Class class_; static NativeObject* initClass(JSContext* cx, HandleObject dbgCtor, HandleObject objProto); static DebuggerEnvironment* create(JSContext* cx, HandleObject proto, HandleObject referent, HandleNativeObject debugger); DebuggerEnvironmentType type() const; MOZ_MUST_USE bool getParent(JSContext* cx, MutableHandleDebuggerEnvironment result) const; MOZ_MUST_USE bool getObject(JSContext* cx, MutableHandleDebuggerObject result) const; MOZ_MUST_USE bool getCallee(JSContext* cx, MutableHandleDebuggerObject result) const; bool isDebuggee() const; bool isOptimized() const; static MOZ_MUST_USE bool getNames(JSContext* cx, HandleDebuggerEnvironment environment, MutableHandle result); static MOZ_MUST_USE bool find(JSContext* cx, HandleDebuggerEnvironment environment, HandleId id, MutableHandleDebuggerEnvironment result); static MOZ_MUST_USE bool getVariable(JSContext* cx, HandleDebuggerEnvironment environment, HandleId id, MutableHandleValue result); static MOZ_MUST_USE bool setVariable(JSContext* cx, HandleDebuggerEnvironment environment, HandleId id, HandleValue value); private: static const ClassOps classOps_; static const JSPropertySpec properties_[]; static const JSFunctionSpec methods_[]; Env* referent() const { Env* env = static_cast(getPrivate()); MOZ_ASSERT(env); return env; } Debugger* owner() const; bool requireDebuggee(JSContext* cx) const; static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool typeGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool parentGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool objectGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool calleeGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool inspectableGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool optimizedOutGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool namesMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool findMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool getVariableMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool setVariableMethod(JSContext* cx, unsigned argc, Value* vp); }; enum class DebuggerFrameType { Eval, Global, Call, Module }; enum class DebuggerFrameImplementation { Interpreter, Baseline, Ion }; class DebuggerFrame : public NativeObject { public: enum { OWNER_SLOT }; static const unsigned RESERVED_SLOTS = 1; static const Class class_; static NativeObject* initClass(JSContext* cx, HandleObject dbgCtor, HandleObject objProto); static DebuggerFrame* create(JSContext* cx, HandleObject proto, AbstractFramePtr referent, const ScriptFrameIter* maybeIter, HandleNativeObject debugger); static MOZ_MUST_USE bool getCallee(JSContext* cx, HandleDebuggerFrame frame, MutableHandleDebuggerObject result); static MOZ_MUST_USE bool getIsConstructing(JSContext* cx, HandleDebuggerFrame frame, bool& result); static MOZ_MUST_USE bool getEnvironment(JSContext* cx, HandleDebuggerFrame frame, MutableHandleDebuggerEnvironment result); static bool getIsGenerator(HandleDebuggerFrame frame); static MOZ_MUST_USE bool getOffset(JSContext* cx, HandleDebuggerFrame frame, size_t& result); static MOZ_MUST_USE bool getOlder(JSContext* cx, HandleDebuggerFrame frame, MutableHandleDebuggerFrame result); static MOZ_MUST_USE bool getThis(JSContext* cx, HandleDebuggerFrame frame, MutableHandleValue result); static DebuggerFrameType getType(HandleDebuggerFrame frame); static DebuggerFrameImplementation getImplementation(HandleDebuggerFrame frame); static MOZ_MUST_USE bool eval(JSContext* cx, HandleDebuggerFrame frame, mozilla::Range chars, HandleObject bindings, const EvalOptions& options, JSTrapStatus& status, MutableHandleValue value); bool isLive() const; private: static const ClassOps classOps_; static const JSPropertySpec properties_[]; static const JSFunctionSpec methods_[]; static AbstractFramePtr getReferent(HandleDebuggerFrame frame); static MOZ_MUST_USE bool getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame, mozilla::Maybe& result); static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool calleeGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool constructingGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool environmentGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool generatorGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool liveGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool offsetGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool olderGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool thisGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool typeGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool implementationGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool evalMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool evalWithBindingsMethod(JSContext* cx, unsigned argc, Value* vp); Debugger* owner() const; }; class DebuggerObject : public NativeObject { public: static const Class class_; static NativeObject* initClass(JSContext* cx, HandleObject obj, HandleObject debugCtor); static DebuggerObject* create(JSContext* cx, HandleObject proto, HandleObject obj, HandleNativeObject debugger); // Properties static MOZ_MUST_USE bool getClassName(JSContext* cx, HandleDebuggerObject object, MutableHandleString result); static MOZ_MUST_USE bool getGlobal(JSContext* cx, HandleDebuggerObject object, MutableHandleDebuggerObject result); static MOZ_MUST_USE bool getParameterNames(JSContext* cx, HandleDebuggerObject object, MutableHandle result); static MOZ_MUST_USE bool getBoundTargetFunction(JSContext* cx, HandleDebuggerObject object, MutableHandleDebuggerObject result); static MOZ_MUST_USE bool getBoundThis(JSContext* cx, HandleDebuggerObject object, MutableHandleValue result); static MOZ_MUST_USE bool getBoundArguments(JSContext* cx, HandleDebuggerObject object, MutableHandle result); static MOZ_MUST_USE bool getAllocationSite(JSContext* cx, HandleDebuggerObject object, MutableHandleObject result); static MOZ_MUST_USE bool getErrorMessageName(JSContext* cx, HandleDebuggerObject object, MutableHandleString result); static MOZ_MUST_USE bool getErrorLineNumber(JSContext* cx, HandleDebuggerObject object, MutableHandleValue result); static MOZ_MUST_USE bool getErrorColumnNumber(JSContext* cx, HandleDebuggerObject object, MutableHandleValue result); static MOZ_MUST_USE bool getScriptedProxyTarget(JSContext* cx, HandleDebuggerObject object, MutableHandleDebuggerObject result); static MOZ_MUST_USE bool getScriptedProxyHandler(JSContext* cx, HandleDebuggerObject object, MutableHandleDebuggerObject result); #ifdef SPIDERMONKEY_PROMISE static MOZ_MUST_USE bool getPromiseValue(JSContext* cx, HandleDebuggerObject object, MutableHandleValue result); static MOZ_MUST_USE bool getPromiseReason(JSContext* cx, HandleDebuggerObject object, MutableHandleValue result); #endif // SPIDERMONKEY_PROMISE // Methods static MOZ_MUST_USE bool isExtensible(JSContext* cx, HandleDebuggerObject object, bool& result); static MOZ_MUST_USE bool isSealed(JSContext* cx, HandleDebuggerObject object, bool& result); static MOZ_MUST_USE bool isFrozen(JSContext* cx, HandleDebuggerObject object, bool& result); static MOZ_MUST_USE bool getPrototypeOf(JSContext* cx, HandleDebuggerObject object, MutableHandleDebuggerObject result); static MOZ_MUST_USE bool getOwnPropertyNames(JSContext* cx, HandleDebuggerObject object, MutableHandle result); static MOZ_MUST_USE bool getOwnPropertySymbols(JSContext* cx, HandleDebuggerObject object, MutableHandle result); static MOZ_MUST_USE bool getOwnPropertyDescriptor(JSContext* cx, HandleDebuggerObject object, HandleId id, MutableHandle desc); static MOZ_MUST_USE bool preventExtensions(JSContext* cx, HandleDebuggerObject object); static MOZ_MUST_USE bool seal(JSContext* cx, HandleDebuggerObject object); static MOZ_MUST_USE bool freeze(JSContext* cx, HandleDebuggerObject object); static MOZ_MUST_USE bool defineProperty(JSContext* cx, HandleDebuggerObject object, HandleId id, Handle desc); static MOZ_MUST_USE bool defineProperties(JSContext* cx, HandleDebuggerObject object, Handle ids, Handle descs); static MOZ_MUST_USE bool deleteProperty(JSContext* cx, HandleDebuggerObject object, HandleId id, ObjectOpResult& result); static MOZ_MUST_USE bool call(JSContext* cx, HandleDebuggerObject object, HandleValue thisv, Handle args, MutableHandleValue result); static MOZ_MUST_USE bool forceLexicalInitializationByName(JSContext* cx, HandleDebuggerObject object, HandleId id, bool& result); static MOZ_MUST_USE bool executeInGlobal(JSContext* cx, HandleDebuggerObject object, mozilla::Range chars, HandleObject bindings, const EvalOptions& options, JSTrapStatus& status, MutableHandleValue value); static MOZ_MUST_USE bool makeDebuggeeValue(JSContext* cx, HandleDebuggerObject object, HandleValue value, MutableHandleValue result); static MOZ_MUST_USE bool unsafeDereference(JSContext* cx, HandleDebuggerObject object, MutableHandleObject result); static MOZ_MUST_USE bool unwrap(JSContext* cx, HandleDebuggerObject object, MutableHandleDebuggerObject result); // Infallible properties bool isCallable() const; bool isFunction() const; bool isDebuggeeFunction() const; bool isBoundFunction() const; bool isArrowFunction() const; bool isGlobal() const; bool isScriptedProxy() const; #ifdef SPIDERMONKEY_PROMISE bool isPromise() const; #endif // SPIDERMONKEY_PROMISE JSAtom* name() const; JSAtom* displayName() const; #ifdef SPIDERMONKEY_PROMISE JS::PromiseState promiseState() const; double promiseLifetime() const; double promiseTimeToResolution() const; #endif // SPIDERMONKEY_PROMISE private: enum { OWNER_SLOT }; static const unsigned RESERVED_SLOTS = 1; static const ClassOps classOps_; static const JSPropertySpec properties_[]; #ifdef SPIDERMONKEY_PROMISE static const JSPropertySpec promiseProperties_[]; #endif // SPIDERMONKEY_PROMISE static const JSFunctionSpec methods_[]; JSObject* referent() const { JSObject* obj = (JSObject*) getPrivate(); MOZ_ASSERT(obj); return obj; } Debugger* owner() const; #ifdef SPIDERMONKEY_PROMISE PromiseObject* promise() const; #endif // SPIDERMONKEY_PROMISE static MOZ_MUST_USE bool requireGlobal(JSContext* cx, HandleDebuggerObject object); #ifdef SPIDERMONKEY_PROMISE static MOZ_MUST_USE bool requirePromise(JSContext* cx, HandleDebuggerObject object); #endif // SPIDERMONKEY_PROMISE static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); // JSNative properties static MOZ_MUST_USE bool callableGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool isBoundFunctionGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool isArrowFunctionGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool protoGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool classGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool nameGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool displayNameGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool parameterNamesGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool scriptGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool environmentGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool boundTargetFunctionGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool boundThisGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool boundArgumentsGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool globalGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool allocationSiteGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool errorMessageNameGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool errorLineNumberGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool errorColumnNumberGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool isProxyGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool proxyTargetGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool proxyHandlerGetter(JSContext* cx, unsigned argc, Value* vp); #ifdef SPIDERMONKEY_PROMISE static MOZ_MUST_USE bool isPromiseGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool promiseStateGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool promiseValueGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool promiseReasonGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool promiseLifetimeGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool promiseTimeToResolutionGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool promiseAllocationSiteGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool promiseResolutionSiteGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool promiseIDGetter(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool promiseDependentPromisesGetter(JSContext* cx, unsigned argc, Value* vp); #endif // SPIDERMONKEY_PROMISE // JSNative methods static MOZ_MUST_USE bool isExtensibleMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool isSealedMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool isFrozenMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool getOwnPropertyNamesMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool getOwnPropertySymbolsMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool getOwnPropertyDescriptorMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool preventExtensionsMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool sealMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool freezeMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool definePropertyMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool definePropertiesMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool deletePropertyMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool callMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool applyMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool asEnvironmentMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool forceLexicalInitializationByNameMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool executeInGlobalMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool executeInGlobalWithBindingsMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool makeDebuggeeValueMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool unsafeDereferenceMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool unwrapMethod(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE bool getErrorReport(JSContext* cx, HandleObject maybeError, JSErrorReport*& report); }; class BreakpointSite { friend class Breakpoint; friend struct ::JSCompartment; friend class ::JSScript; friend class Debugger; public: JSScript* script; jsbytecode * const pc; private: JSCList breakpoints; /* cyclic list of all js::Breakpoints at this instruction */ size_t enabledCount; /* number of breakpoints in the list that are enabled */ void recompile(FreeOp* fop); public: BreakpointSite(JSScript* script, jsbytecode* pc); Breakpoint* firstBreakpoint() const; bool hasBreakpoint(Breakpoint* bp); void inc(FreeOp* fop); void dec(FreeOp* fop); void destroyIfEmpty(FreeOp* fop); }; /* * Each Breakpoint is a member of two linked lists: its debugger's list and its * site's list. * * GC rules: * - script is live, breakpoint exists, and debugger is enabled * ==> debugger is live * - script is live, breakpoint exists, and debugger is live * ==> retain the breakpoint and the handler object is live * * Debugger::markAllIteratively implements these two rules. It uses * Debugger::hasAnyLiveHooks to check for rule 1. * * Nothing else causes a breakpoint to be retained, so if its script or * debugger is collected, the breakpoint is destroyed during GC sweep phase, * even if the debugger compartment isn't being GC'd. This is implemented in * Zone::sweepBreakpoints. */ class Breakpoint { friend struct ::JSCompartment; friend class Debugger; public: Debugger * const debugger; BreakpointSite * const site; private: /* |handler| is marked unconditionally during minor GC. */ js::PreBarrieredObject handler; JSCList debuggerLinks; JSCList siteLinks; public: static Breakpoint* fromDebuggerLinks(JSCList* links); static Breakpoint* fromSiteLinks(JSCList* links); Breakpoint(Debugger* debugger, BreakpointSite* site, JSObject* handler); void destroy(FreeOp* fop); Breakpoint* nextInDebugger(); Breakpoint* nextInSite(); const PreBarrieredObject& getHandler() const { return handler; } PreBarrieredObject& getHandlerRef() { return handler; } }; Breakpoint* Debugger::firstBreakpoint() const { if (JS_CLIST_IS_EMPTY(&breakpoints)) return nullptr; return Breakpoint::fromDebuggerLinks(JS_NEXT_LINK(&breakpoints)); } /* static */ Debugger* Debugger::fromOnNewGlobalObjectWatchersLink(JSCList* link) { char* p = reinterpret_cast(link); return reinterpret_cast(p - offsetof(Debugger, onNewGlobalObjectWatchersLink)); } const js::GCPtrNativeObject& Debugger::toJSObject() const { MOZ_ASSERT(object); return object; } js::GCPtrNativeObject& Debugger::toJSObjectRef() { MOZ_ASSERT(object); return object; } bool Debugger::observesEnterFrame() const { return enabled && getHook(OnEnterFrame); } bool Debugger::observesNewScript() const { return enabled && getHook(OnNewScript); } bool Debugger::observesNewGlobalObject() const { return enabled && getHook(OnNewGlobalObject); } bool Debugger::observesGlobal(GlobalObject* global) const { ReadBarriered debuggee(global); return debuggees.has(debuggee); } /* static */ void Debugger::onNewScript(JSContext* cx, HandleScript script) { // We early return in slowPathOnNewScript for self-hosted scripts, so we can // ignore those in our assertion here. MOZ_ASSERT_IF(!script->compartment()->creationOptions().invisibleToDebugger() && !script->selfHosted(), script->compartment()->firedOnNewGlobalObject); if (script->compartment()->isDebuggee()) slowPathOnNewScript(cx, script); } /* static */ void Debugger::onNewGlobalObject(JSContext* cx, Handle global) { MOZ_ASSERT(!global->compartment()->firedOnNewGlobalObject); #ifdef DEBUG global->compartment()->firedOnNewGlobalObject = true; #endif if (!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers)) Debugger::slowPathOnNewGlobalObject(cx, global); } /* static */ bool Debugger::onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame, double when) { GlobalObject::DebuggerVector* dbgs = cx->global()->getDebuggers(); if (!dbgs || dbgs->empty()) return true; RootedObject hobj(cx, obj); return Debugger::slowPathOnLogAllocationSite(cx, hobj, frame, when, *dbgs); } MOZ_MUST_USE bool ReportObjectRequired(JSContext* cx); } /* namespace js */ namespace JS { template <> struct DeletePolicy : public js::GCManagedDeletePolicy {}; } /* namespace JS */ #endif /* vm_Debugger_h */