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