summaryrefslogtreecommitdiffstats
path: root/js/src/debugger/DebugAPI.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/debugger/DebugAPI.h
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/debugger/DebugAPI.h')
-rw-r--r--js/src/debugger/DebugAPI.h420
1 files changed, 420 insertions, 0 deletions
diff --git a/js/src/debugger/DebugAPI.h b/js/src/debugger/DebugAPI.h
new file mode 100644
index 0000000000..27b2cd8ba4
--- /dev/null
+++ b/js/src/debugger/DebugAPI.h
@@ -0,0 +1,420 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef debugger_DebugAPI_h
+#define debugger_DebugAPI_h
+
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/JSContext.h"
+#include "vm/Realm.h"
+
+namespace js {
+
+// This file contains the API which SpiderMonkey should use to interact with any
+// active Debuggers.
+
+class AbstractGeneratorObject;
+class DebugScriptMap;
+class PromiseObject;
+
+namespace gc {
+class AutoSuppressGC;
+} // namespace gc
+
+/**
+ * DebugAPI::onNativeCall allows the debugger to call callbacks just before
+ * some native functions are to be executed. It also allows the hooks
+ * themselves to affect the result of the call. This enum represents the
+ * various affects that DebugAPI::onNativeCall may perform.
+ */
+enum class NativeResumeMode {
+ /**
+ * If the debugger hook did not return a value to manipulate the result of
+ * the native call, execution can continue unchanged.
+ *
+ * Continue indicates that the native function should execute normally.
+ */
+ Continue,
+
+ /**
+ * If the debugger hook returned an explicit return value that is meant to
+ * take the place of the native call's result, execution of the native
+ * function needs to be skipped in favor of the explicit result.
+ *
+ * Override indicates that the native function should be skipped and that
+ * the debugger has already stored the return value into the CallArgs.
+ */
+ Override,
+
+ /**
+ * If the debugger hook returns an explicit termination or an explicit
+ * thrown exception, execution of the native function needs to be skipped
+ * in favor of handling the error condition.
+ *
+ * Abort indicates that the native function should be skipped and that
+ * execution should be terminated. The debugger may or may not have set a
+ * pending exception.
+ */
+ Abort,
+};
+
+class DebugScript;
+class DebuggerVector;
+
+class DebugAPI {
+ public:
+ friend class Debugger;
+
+ /*** Methods for interaction with the GC. ***********************************/
+
+ /*
+ * Trace (inferred) owning edges from stack frames to Debugger.Frames, as part
+ * of root marking.
+ *
+ * Even if a Debugger.Frame for a live stack frame is entirely unreachable
+ * from JS, if it has onStep or onPop hooks set, then collecting it would have
+ * observable side effects - namely, the hooks would fail to run. The effect
+ * is the same as if the stack frame held an owning edge to its
+ * Debugger.Frame.
+ *
+ * Debugger.Frames must also be retained if the Debugger to which they belong
+ * is reachable, even if they have no hooks set, but we handle that elsewhere;
+ * this function is only concerned with the inferred roots from stack frames
+ * to Debugger.Frames that have hooks set.
+ */
+ static void traceFramesWithLiveHooks(JSTracer* tracer);
+
+ /*
+ * Trace (inferred) owning edges from generator objects to Debugger.Frames.
+ *
+ * Even if a Debugger.Frame for a live suspended generator object is entirely
+ * unreachable from JS, if it has onStep or onPop hooks set, then collecting
+ * it would have observable side effects - namely, the hooks would fail to run
+ * if the generator is resumed. The effect is the same as if the generator
+ * object held an owning edge to its Debugger.Frame.
+ */
+ static inline void traceGeneratorFrame(JSTracer* tracer,
+ AbstractGeneratorObject* generator);
+
+ // Trace cross compartment edges in all debuggers relevant to the current GC.
+ static void traceCrossCompartmentEdges(JSTracer* tracer);
+
+ // Trace all debugger-owned GC things unconditionally, during a moving GC.
+ static void traceAllForMovingGC(JSTracer* trc);
+
+ // Trace the debug script map. Called as part of tracing a zone's roots.
+ static void traceDebugScriptMap(JSTracer* trc, DebugScriptMap* map);
+
+ static void traceFromRealm(JSTracer* trc, Realm* realm);
+
+ // The garbage collector calls this after everything has been marked, but
+ // before anything has been finalized. We use this to clear Debugger /
+ // debuggee edges at a point where the parties concerned are all still
+ // initialized. This does not update edges to moved GC things which is handled
+ // via the other trace methods.
+ static void sweepAll(JS::GCContext* gcx);
+
+ // Add sweep group edges due to the presence of any debuggers.
+ [[nodiscard]] static bool findSweepGroupEdges(JSRuntime* rt);
+
+ // Remove the debugging information associated with a script.
+ static void removeDebugScript(JS::GCContext* gcx, JSScript* script);
+
+ // Delete a Zone's debug script map. Called when a zone is destroyed.
+ static void deleteDebugScriptMap(DebugScriptMap* map);
+
+ // Validate the debugging information in a script after a moving GC>
+#ifdef JSGC_HASH_TABLE_CHECKS
+ static void checkDebugScriptAfterMovingGC(DebugScript* ds);
+#endif
+
+#ifdef DEBUG
+ static bool edgeIsInDebuggerWeakmap(JSRuntime* rt, JSObject* src,
+ JS::GCCellPtr dst);
+#endif
+
+ /*** Methods for querying script breakpoint state. **************************/
+
+ // Query information about whether any debuggers are observing a script.
+ static inline bool stepModeEnabled(JSScript* script);
+ static inline bool hasBreakpointsAt(JSScript* script, jsbytecode* pc);
+ static inline bool hasAnyBreakpointsOrStepMode(JSScript* script);
+
+ /*** Methods for interacting with the JITs. *********************************/
+
+ // Update Debugger frames when an interpreter frame is replaced with a
+ // baseline frame.
+ [[nodiscard]] static bool handleBaselineOsr(JSContext* cx,
+ InterpreterFrame* from,
+ jit::BaselineFrame* to);
+
+ // Update Debugger frames when an Ion frame bails out and is replaced with a
+ // baseline frame.
+ [[nodiscard]] static bool handleIonBailout(JSContext* cx,
+ jit::RematerializedFrame* from,
+ jit::BaselineFrame* to);
+
+ // Detach any Debugger frames from an Ion frame after an error occurred while
+ // it bailed out.
+ static void handleUnrecoverableIonBailoutError(
+ JSContext* cx, jit::RematerializedFrame* frame);
+
+ // When doing on-stack-replacement of a debuggee interpreter frame with a
+ // baseline frame, ensure that the resulting frame can be observed by the
+ // debugger.
+ [[nodiscard]] static bool ensureExecutionObservabilityOfOsrFrame(
+ JSContext* cx, AbstractFramePtr osrSourceFrame);
+
+ // Describes a set of scripts or frames whose execution observability can
+ // change due to debugger activity.
+ class ExecutionObservableSet {
+ public:
+ using ZoneRange = HashSet<Zone*>::Range;
+
+ 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(FrameIter& 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 };
+
+ /*** Methods for calling installed debugger handlers. ***********************/
+
+ // Called when a new script becomes accessible to debuggers.
+ static void onNewScript(JSContext* cx, HandleScript script);
+
+ // Called when a new wasm instance becomes accessible to debuggers.
+ static inline void onNewWasmInstance(
+ JSContext* cx, Handle<WasmInstanceObject*> wasmInstance);
+
+ /*
+ * Announce to the debugger that the context has entered a new JavaScript
+ * frame, |frame|. Call whatever hooks have been registered to observe new
+ * frames.
+ */
+ [[nodiscard]] static inline bool onEnterFrame(JSContext* cx,
+ AbstractFramePtr frame);
+
+ /*
+ * Like onEnterFrame, but for resuming execution of a generator or async
+ * function. `frame` is a new baseline or interpreter frame, but abstractly
+ * it can be identified with a particular generator frame that was
+ * suspended earlier.
+ *
+ * There is no separate user-visible Debugger.onResumeFrame hook; this
+ * fires .onEnterFrame (again, since we're re-entering the frame).
+ *
+ * Unfortunately, the interpreter and the baseline JIT arrange for this to
+ * be called in different ways. The interpreter calls it from JSOp::Resume,
+ * immediately after pushing the resumed frame; the JIT calls it from
+ * JSOp::AfterYield, just after the generator resumes. The difference
+ * should not be user-visible.
+ */
+ [[nodiscard]] static inline bool onResumeFrame(JSContext* cx,
+ AbstractFramePtr frame);
+
+ static inline NativeResumeMode onNativeCall(JSContext* cx,
+ const CallArgs& args,
+ CallReason reason);
+
+ /*
+ * 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.
+ */
+ [[nodiscard]] static inline bool 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.
+ */
+ [[nodiscard]] static inline bool 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.)
+ */
+ [[nodiscard]] static inline bool onLeaveFrame(JSContext* cx,
+ AbstractFramePtr frame,
+ const jsbytecode* pc, bool ok);
+
+ // Call any breakpoint handlers for the current scripted location.
+ [[nodiscard]] static bool onTrap(JSContext* cx);
+
+ // Call any stepping handlers for the current scripted location.
+ [[nodiscard]] static bool onSingleStep(JSContext* cx);
+
+ // Notify any Debugger instances observing this promise's global that a new
+ // promise was allocated.
+ static inline void onNewPromise(JSContext* cx,
+ Handle<PromiseObject*> promise);
+
+ // Notify any Debugger instances observing this promise's global that the
+ // promise has settled (ie, it has either been fulfilled or rejected). Note
+ // that this is *not* equivalent to the promise resolution (ie, the promise's
+ // fate getting locked in) because you can resolve a promise with another
+ // pending promise, in which case neither promise has settled yet.
+ //
+ // This should never be called on the same promise more than once, because a
+ // promise can only make the transition from unsettled to settled once.
+ static inline void onPromiseSettled(JSContext* cx,
+ Handle<PromiseObject*> promise);
+
+ // Notify any Debugger instances that a new global object has been created.
+ static inline void onNewGlobalObject(JSContext* cx,
+ Handle<GlobalObject*> global);
+
+ /*** Methods for querying installed debugger handlers. **********************/
+
+ // Whether any debugger is observing execution in a global.
+ static bool debuggerObservesAllExecution(GlobalObject* global);
+
+ // Whether any debugger is observing JS execution coverage in a global.
+ static bool debuggerObservesCoverage(GlobalObject* global);
+
+ // Whether any Debugger is observing asm.js execution in a global.
+ static bool debuggerObservesAsmJS(GlobalObject* global);
+
+ // Whether any Debugger is observing WebAssembly execution in a global.
+ static bool debuggerObservesWasm(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& debuggee);
+
+ // If any debuggers are tracking allocations for a global, return the
+ // probability that a given allocation should be tracked. Nothing otherwise.
+ static mozilla::Maybe<double> allocationSamplingProbability(
+ GlobalObject* global);
+
+ // Whether any debugger is observing exception unwinds in a realm.
+ static bool hasExceptionUnwindHook(GlobalObject* global);
+
+ // Whether any debugger is observing debugger statements in a realm.
+ static bool hasDebuggerStatementHook(GlobalObject* global);
+
+ /*** Assorted methods for interacting with the runtime. *********************/
+
+ // Checks if the current compartment is allowed to execute code.
+ [[nodiscard]] static inline bool checkNoExecute(JSContext* cx,
+ HandleScript script);
+
+ /*
+ * Announce to the debugger that a generator object has been created,
+ * via JSOp::Generator.
+ *
+ * This does not fire user hooks, but it's needed for debugger bookkeeping.
+ */
+ [[nodiscard]] static inline bool onNewGenerator(
+ JSContext* cx, AbstractFramePtr frame,
+ Handle<AbstractGeneratorObject*> genObj);
+
+ // If necessary, record an object that was just allocated for any observing
+ // debuggers.
+ [[nodiscard]] static inline bool onLogAllocationSite(
+ JSContext* cx, JSObject* obj, Handle<SavedFrame*> frame,
+ mozilla::TimeStamp when);
+
+ // Announce to the debugger that a global object is being collected by the
+ // specified major GC.
+ static inline void notifyParticipatesInGC(GlobalObject* global,
+ uint64_t majorGCNumber);
+
+ private:
+ static bool stepModeEnabledSlow(JSScript* script);
+ static bool hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc);
+ static void slowPathOnNewGlobalObject(JSContext* cx,
+ Handle<GlobalObject*> global);
+ static void slowPathNotifyParticipatesInGC(uint64_t majorGCNumber,
+ JS::Realm::DebuggerVector& dbgs,
+ const JS::AutoRequireNoGC& nogc);
+ [[nodiscard]] static bool slowPathOnLogAllocationSite(
+ JSContext* cx, HandleObject obj, Handle<SavedFrame*> frame,
+ mozilla::TimeStamp when, JS::Realm::DebuggerVector& dbgs,
+ const gc::AutoSuppressGC& nogc);
+ [[nodiscard]] static bool slowPathOnLeaveFrame(JSContext* cx,
+ AbstractFramePtr frame,
+ const jsbytecode* pc, bool ok);
+ [[nodiscard]] static bool slowPathOnNewGenerator(
+ JSContext* cx, AbstractFramePtr frame,
+ Handle<AbstractGeneratorObject*> genObj);
+ [[nodiscard]] static bool slowPathCheckNoExecute(JSContext* cx,
+ HandleScript script);
+ [[nodiscard]] static bool slowPathOnEnterFrame(JSContext* cx,
+ AbstractFramePtr frame);
+ [[nodiscard]] static bool slowPathOnResumeFrame(JSContext* cx,
+ AbstractFramePtr frame);
+ static NativeResumeMode slowPathOnNativeCall(JSContext* cx,
+ const CallArgs& args,
+ CallReason reason);
+ [[nodiscard]] static bool slowPathOnDebuggerStatement(JSContext* cx,
+ AbstractFramePtr frame);
+ [[nodiscard]] static bool slowPathOnExceptionUnwind(JSContext* cx,
+ AbstractFramePtr frame);
+ static void slowPathOnNewWasmInstance(
+ JSContext* cx, Handle<WasmInstanceObject*> wasmInstance);
+ static void slowPathOnNewPromise(JSContext* cx,
+ Handle<PromiseObject*> promise);
+ static void slowPathOnPromiseSettled(JSContext* cx,
+ Handle<PromiseObject*> promise);
+ static bool inFrameMaps(AbstractFramePtr frame);
+ static void slowPathTraceGeneratorFrame(JSTracer* tracer,
+ AbstractGeneratorObject* generator);
+};
+
+// 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->noExecuteDebuggerTop.ref();
+ prev_ = *stack_;
+ *stack_ = nullptr;
+ }
+
+ ~AutoSuppressDebuggeeNoExecuteChecks() {
+ MOZ_ASSERT(!*stack_);
+ *stack_ = prev_;
+ }
+};
+
+} /* namespace js */
+
+#endif /* debugger_DebugAPI_h */