diff options
Diffstat (limited to 'js/src/vm/Debugger.h')
-rw-r--r-- | js/src/vm/Debugger.h | 1584 |
1 files changed, 1584 insertions, 0 deletions
diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h new file mode 100644 index 000000000..3239ade6d --- /dev/null +++ b/js/src/vm/Debugger.h @@ -0,0 +1,1584 @@ +/* -*- 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<ReadBarrieredGlobalObject, + MovableCellHasher<ReadBarrieredGlobalObject>, + 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 UnbarrieredKey, bool InvisibleKeysOk=false> +class DebuggerWeakMap : private WeakMap<HeapPtr<UnbarrieredKey>, HeapPtr<JSObject*>, + MovableCellHasher<HeapPtr<UnbarrieredKey>>> +{ + private: + typedef HeapPtr<UnbarrieredKey> Key; + typedef HeapPtr<JSObject*> Value; + + typedef HashMap<JS::Zone*, + uintptr_t, + DefaultHasher<JS::Zone*>, + RuntimeAllocPolicy> CountMap; + + CountMap zoneCounts; + JSCompartment* compartment; + + public: + typedef WeakMap<Key, Value, MovableCellHasher<Key>> 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<typename KeyInput, typename ValueInput> + 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 (traceValueEdges)(JSTracer*, JSObject*)> + void markCrossCompartmentEdges(JSTracer* tracer) { + for (Enum e(*static_cast<Base*>(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<Base*>(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<JSScript*, WasmInstanceObject*> DebuggerScriptReferent; + +// Either a ScriptSourceObject, for ordinary JS, or a WasmInstanceObject, +// denoting the synthesized source of a wasm module. +typedef mozilla::Variant<ScriptSourceObject*, WasmInstanceObject*> DebuggerSourceReferent; + +class Debugger : private mozilla::LinkedListElement<Debugger> +{ + friend class Breakpoint; + friend class DebuggerMemory; + friend class SavedStacks; + 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&, 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<Zone*>::Range ZoneRange; + + virtual Zone* singleZone() const { return nullptr; } + virtual JSScript* singleScriptForZoneInvalidation() const { return nullptr; } + virtual const HashSet<Zone*>* 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<SavedFrame>()); + }; + + HeapPtr<JSObject*> frame; + double when; + const char* className; + HeapPtr<JSAtom*> 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<Debugger*>. + static void readBarrier(Debugger* dbg) { + InternalBarrierMethods<JSObject*>::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<uint64_t, DefaultHasher<uint64_t>, RuntimeAllocPolicy>; + 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; + + 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<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. + */ + 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<AbstractFramePtr, + HeapPtr<DebuggerFrame*>, + DefaultHasher<AbstractFramePtr>, + RuntimeAllocPolicy> FrameMap; + FrameMap frames; + + /* An ephemeral map from JSScript* to Debugger.Script instances. */ + typedef DebuggerWeakMap<JSScript*> ScriptWeakMap; + ScriptWeakMap scripts; + + /* The map from debuggee source script objects to their Debugger.Source instances. */ + typedef DebuggerWeakMap<JSObject*, true> SourceWeakMap; + SourceWeakMap sources; + + /* The map from debuggee objects to their Debugger.Object instances. */ + typedef DebuggerWeakMap<JSObject*> 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<WasmInstanceObject*> 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<GlobalObject*> 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<AutoCompartment>& 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<AutoCompartment>& ac); + JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment>& ac, + MutableHandleValue vp, + const mozilla::Maybe<HandleValue>& thisVForCheck = mozilla::Nothing(), + AbstractFramePtr frame = NullFramePtr()); + + JSTrapStatus handleUncaughtExceptionHelper(mozilla::Maybe<AutoCompartment>& ac, + MutableHandleValue* vp, + const mozilla::Maybe<HandleValue>& thisVForCheck, + AbstractFramePtr frame); + + /* + * 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. 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<AutoCompartment>& ac, bool OK, const Value& rv, + AbstractFramePtr frame, jsbytecode* pc, MutableHandleValue vp); + + JSTrapStatus processParsedHandlerResult(mozilla::Maybe<AutoCompartment>& ac, + AbstractFramePtr frame, jsbytecode* pc, + bool success, JSTrapStatus status, + MutableHandleValue vp); + + JSTrapStatus processParsedHandlerResultHelper(mozilla::Maybe<AutoCompartment>& ac, + AbstractFramePtr frame, + const mozilla::Maybe<HandleValue>& maybeThisv, + bool success, JSTrapStatus status, + MutableHandleValue vp); + + bool processResumptionValue(mozilla::Maybe<AutoCompartment>& ac, AbstractFramePtr frame, + const mozilla::Maybe<HandleValue>& 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 <typename FrameFn /* void (NativeObject*) */> + 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<DebuggerFrame*>; + static MOZ_MUST_USE bool getDebuggerFrames(AbstractFramePtr frame, + MutableHandle<DebuggerFrameVector> 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<WasmInstanceObject*> wasmInstance); + static void slowPathOnNewGlobalObject(JSContext* cx, Handle<GlobalObject*> 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 <typename HookIsEnabledFun /* bool (Debugger*) */, + typename FireHookFun /* JSTrapStatus (Debugger*) */> + 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<GlobalObject*> global, MutableHandleValue vp); + JSTrapStatus firePromiseHook(JSContext* cx, Hook hook, HandleObject promise, MutableHandleValue vp); + + NativeObject* newVariantWrapper(JSContext* cx, Handle<DebuggerScriptReferent> referent) { + return newDebuggerScript(cx, referent); + } + NativeObject* 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 ReferentVariant, typename Referent, typename Map> + JSObject* wrapVariantReferent(JSContext* cx, Map& map, Handle<CrossCompartmentKey> key, + Handle<ReferentVariant> referent); + JSObject* wrapVariantReferent(JSContext* cx, Handle<DebuggerScriptReferent> referent); + JSObject* wrapVariantReferent(JSContext* cx, Handle<DebuggerSourceReferent> referent); + + /* + * Allocate and initialize a Debugger.Script instance whose referent is + * |referent|. + */ + NativeObject* newDebuggerScript(JSContext* cx, Handle<DebuggerScriptReferent> referent); + + /* + * Allocate and initialize a Debugger.Source instance whose referent is + * |referent|. + */ + NativeObject* newDebuggerSource(JSContext* cx, Handle<DebuggerSourceReferent> referent); + + /* + * Receive a "new script" event from the engine. A new script was compiled + * or deserialized. + */ + void fireNewScript(JSContext* cx, Handle<DebuggerScriptReferent> 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<WasmInstanceObject*> wasmInstance); + static inline void onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> 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*> env, MutableHandleValue vp); + MOZ_MUST_USE bool wrapEnvironment(JSContext* cx, Handle<Env*> 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<PropertyDescriptor> 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<AutoCompartment>& 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<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 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<WasmInstanceObject*> 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<IdVector> 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<Env*>(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<const char16_t> 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<ScriptFrameIter>& 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<StringVector> 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<ValueVector> 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<IdVector> result); + static MOZ_MUST_USE bool getOwnPropertySymbols(JSContext* cx, HandleDebuggerObject object, + MutableHandle<IdVector> result); + static MOZ_MUST_USE bool getOwnPropertyDescriptor(JSContext* cx, HandleDebuggerObject object, + HandleId id, + MutableHandle<PropertyDescriptor> 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<PropertyDescriptor> desc); + static MOZ_MUST_USE bool defineProperties(JSContext* cx, HandleDebuggerObject object, + Handle<IdVector> ids, + Handle<PropertyDescriptorVector> 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<ValueVector> 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<const char16_t> 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<char*>(link); + return reinterpret_cast<Debugger*>(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<GlobalObject*> 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<GlobalObject*> 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<js::Debugger> : public js::GCManagedDeletePolicy<js::Debugger> +{}; + +} /* namespace JS */ + +#endif /* vm_Debugger_h */ |