summaryrefslogtreecommitdiffstats
path: root/js/src/debugger/Frame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/debugger/Frame.cpp')
-rw-r--r--js/src/debugger/Frame.cpp1950
1 files changed, 1950 insertions, 0 deletions
diff --git a/js/src/debugger/Frame.cpp b/js/src/debugger/Frame.cpp
new file mode 100644
index 0000000000..1ae529be78
--- /dev/null
+++ b/js/src/debugger/Frame.cpp
@@ -0,0 +1,1950 @@
+/* -*- 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/. */
+
+#include "debugger/Frame-inl.h"
+
+#include "mozilla/Assertions.h" // for AssertionConditionType
+#include "mozilla/HashTable.h" // for HashMapEntry
+#include "mozilla/Maybe.h" // for Maybe
+#include "mozilla/Range.h" // for Range
+#include "mozilla/RangedPtr.h" // for RangedPtr
+#include "mozilla/Result.h" // for Result
+#include "mozilla/ScopeExit.h" // for MakeScopeExit, ScopeExit
+#include "mozilla/ThreadLocal.h" // for ThreadLocal
+#include "mozilla/Vector.h" // for Vector
+
+#include <stddef.h> // for size_t
+#include <stdint.h> // for int32_t
+#include <string.h> // for strlen
+#include <utility> // for std::move
+
+#include "jsnum.h" // for Int32ToString
+
+#include "builtin/Array.h" // for NewDenseCopiedArray
+#include "debugger/Debugger.h" // for Completion, Debugger
+#include "debugger/DebugScript.h"
+#include "debugger/Environment.h" // for DebuggerEnvironment
+#include "debugger/NoExecute.h" // for LeaveDebuggeeNoExecute
+#include "debugger/Object.h" // for DebuggerObject
+#include "debugger/Script.h" // for DebuggerScript
+#include "frontend/BytecodeCompilation.h" // for CompileEvalScript
+#include "frontend/FrontendContext.h" // for AutoReportFrontendContext
+#include "gc/Barrier.h" // for HeapPtr
+#include "gc/GC.h" // for MemoryUse
+#include "gc/GCContext.h" // for JS::GCContext
+#include "gc/Marking.h" // for IsAboutToBeFinalized
+#include "gc/Tracer.h" // for TraceCrossCompartmentEdge
+#include "gc/ZoneAllocator.h" // for AddCellMemory
+#include "jit/JSJitFrameIter.h" // for InlineFrameIterator
+#include "jit/RematerializedFrame.h" // for RematerializedFrame
+#include "js/CallArgs.h" // for CallArgs
+#include "js/friend/ErrorMessages.h" // for GetErrorMessage, JSMSG_*
+#include "js/Object.h" // for SetReservedSlot
+#include "js/Proxy.h" // for PrivateValue
+#include "js/RootingAPI.h" // for Handle
+#include "js/SourceText.h" // for SourceText, SourceOwnership
+#include "js/StableStringChars.h" // for AutoStableStringChars
+#include "vm/ArgumentsObject.h" // for ArgumentsObject
+#include "vm/ArrayObject.h" // for ArrayObject
+#include "vm/AsyncFunction.h" // for AsyncFunctionGeneratorObject
+#include "vm/AsyncIteration.h" // for AsyncGeneratorObject
+#include "vm/BytecodeUtil.h" // for JSDVG_SEARCH_STACK
+#include "vm/Compartment.h" // for Compartment
+#include "vm/EnvironmentObject.h" // for IsGlobalLexicalEnvironment
+#include "vm/GeneratorObject.h" // for AbstractGeneratorObject
+#include "vm/GlobalObject.h" // for GlobalObject
+#include "vm/Interpreter.h" // for Call, ExecuteKernel
+#include "vm/JSAtom.h" // for Atomize
+#include "vm/JSContext.h" // for JSContext, ReportValueError
+#include "vm/JSFunction.h" // for JSFunction, NewNativeFunction
+#include "vm/JSObject.h" // for JSObject, RequireObject
+#include "vm/JSScript.h" // for JSScript
+#include "vm/NativeObject.h" // for NativeDefineDataProperty
+#include "vm/Realm.h" // for AutoRealm
+#include "vm/Runtime.h" // for JSAtomState
+#include "vm/Scope.h" // for PositionalFormalParameterIter
+#include "vm/Stack.h" // for AbstractFramePtr, FrameIter
+#include "vm/StringType.h" // for PropertyName, JSString
+#include "wasm/WasmDebug.h" // for DebugState
+#include "wasm/WasmDebugFrame.h" // for DebugFrame
+#include "wasm/WasmInstance.h" // for Instance
+#include "wasm/WasmJS.h" // for WasmInstanceObject
+
+#include "debugger/Debugger-inl.h" // for Debugger::fromJSObject
+#include "gc/WeakMap-inl.h" // for WeakMap::remove
+#include "vm/Compartment-inl.h" // for Compartment::wrap
+#include "vm/JSContext-inl.h" // for JSContext::check
+#include "vm/JSObject-inl.h" // for NewObjectWithGivenProto
+#include "vm/NativeObject-inl.h" // for NativeObject::global
+#include "vm/ObjectOperations-inl.h" // for GetProperty
+#include "vm/Realm-inl.h" // for AutoRealm::AutoRealm
+#include "vm/Stack-inl.h" // for AbstractFramePtr::script
+
+namespace js {
+namespace jit {
+class JitFrameLayout;
+} /* namespace jit */
+} /* namespace js */
+
+using namespace js;
+
+using JS::AutoStableStringChars;
+using JS::CompileOptions;
+using JS::SourceOwnership;
+using JS::SourceText;
+using mozilla::MakeScopeExit;
+using mozilla::Maybe;
+
+ScriptedOnStepHandler::ScriptedOnStepHandler(JSObject* object)
+ : object_(object) {
+ MOZ_ASSERT(object_->isCallable());
+}
+
+JSObject* ScriptedOnStepHandler::object() const { return object_; }
+
+void ScriptedOnStepHandler::hold(JSObject* owner) {
+ AddCellMemory(owner, allocSize(), MemoryUse::DebuggerOnStepHandler);
+}
+
+void ScriptedOnStepHandler::drop(JS::GCContext* gcx, JSObject* owner) {
+ gcx->delete_(owner, this, allocSize(), MemoryUse::DebuggerOnStepHandler);
+}
+
+void ScriptedOnStepHandler::trace(JSTracer* tracer) {
+ TraceEdge(tracer, &object_, "OnStepHandlerFunction.object");
+}
+
+bool ScriptedOnStepHandler::onStep(JSContext* cx, Handle<DebuggerFrame*> frame,
+ ResumeMode& resumeMode,
+ MutableHandleValue vp) {
+ RootedValue fval(cx, ObjectValue(*object_));
+ RootedValue rval(cx);
+ if (!js::Call(cx, fval, frame, &rval)) {
+ return false;
+ }
+
+ return ParseResumptionValue(cx, rval, resumeMode, vp);
+};
+
+size_t ScriptedOnStepHandler::allocSize() const { return sizeof(*this); }
+
+ScriptedOnPopHandler::ScriptedOnPopHandler(JSObject* object) : object_(object) {
+ MOZ_ASSERT(object->isCallable());
+}
+
+JSObject* ScriptedOnPopHandler::object() const { return object_; }
+
+void ScriptedOnPopHandler::hold(JSObject* owner) {
+ AddCellMemory(owner, allocSize(), MemoryUse::DebuggerOnPopHandler);
+}
+
+void ScriptedOnPopHandler::drop(JS::GCContext* gcx, JSObject* owner) {
+ gcx->delete_(owner, this, allocSize(), MemoryUse::DebuggerOnPopHandler);
+}
+
+void ScriptedOnPopHandler::trace(JSTracer* tracer) {
+ TraceEdge(tracer, &object_, "OnStepHandlerFunction.object");
+}
+
+bool ScriptedOnPopHandler::onPop(JSContext* cx, Handle<DebuggerFrame*> frame,
+ const Completion& completion,
+ ResumeMode& resumeMode,
+ MutableHandleValue vp) {
+ Debugger* dbg = frame->owner();
+
+ RootedValue completionValue(cx);
+ if (!completion.buildCompletionValue(cx, dbg, &completionValue)) {
+ return false;
+ }
+
+ RootedValue fval(cx, ObjectValue(*object_));
+ RootedValue rval(cx);
+ if (!js::Call(cx, fval, frame, completionValue, &rval)) {
+ return false;
+ }
+
+ return ParseResumptionValue(cx, rval, resumeMode, vp);
+};
+
+size_t ScriptedOnPopHandler::allocSize() const { return sizeof(*this); }
+
+js::Debugger* js::DebuggerFrame::owner() const {
+ JSObject* dbgobj = &getReservedSlot(OWNER_SLOT).toObject();
+ return Debugger::fromJSObject(dbgobj);
+}
+
+const JSClassOps DebuggerFrame::classOps_ = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ CallTraceMethod<DebuggerFrame>, // trace
+};
+
+const JSClass DebuggerFrame::class_ = {
+ "Frame",
+ JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
+ // We require foreground finalization so we can destruct GeneratorInfo's
+ // HeapPtrs.
+ JSCLASS_FOREGROUND_FINALIZE,
+ &DebuggerFrame::classOps_};
+
+enum { JSSLOT_DEBUGARGUMENTS_FRAME, JSSLOT_DEBUGARGUMENTS_COUNT };
+
+const JSClass DebuggerArguments::class_ = {
+ "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT)};
+
+bool DebuggerFrame::resume(const FrameIter& iter) {
+ FrameIter::Data* data = iter.copyData();
+ if (!data) {
+ return false;
+ }
+ setFrameIterData(data);
+ return true;
+}
+
+bool DebuggerFrame::hasAnyHooks() const {
+ return !getReservedSlot(ONSTEP_HANDLER_SLOT).isUndefined() ||
+ !getReservedSlot(ONPOP_HANDLER_SLOT).isUndefined();
+}
+
+/* static */
+NativeObject* DebuggerFrame::initClass(JSContext* cx,
+ Handle<GlobalObject*> global,
+ HandleObject dbgCtor) {
+ return InitClass(cx, dbgCtor, nullptr, nullptr, "Frame", construct, 0,
+ properties_, methods_, nullptr, nullptr);
+}
+
+/* static */
+DebuggerFrame* DebuggerFrame::create(
+ JSContext* cx, HandleObject proto, Handle<NativeObject*> debugger,
+ const FrameIter* maybeIter,
+ Handle<AbstractGeneratorObject*> maybeGenerator) {
+ Rooted<DebuggerFrame*> frame(
+ cx, NewObjectWithGivenProto<DebuggerFrame>(cx, proto));
+ if (!frame) {
+ return nullptr;
+ }
+
+ frame->setReservedSlot(OWNER_SLOT, ObjectValue(*debugger));
+
+ if (maybeIter) {
+ FrameIter::Data* data = maybeIter->copyData();
+ if (!data) {
+ return nullptr;
+ }
+
+ frame->setFrameIterData(data);
+ }
+
+ if (maybeGenerator) {
+ if (!DebuggerFrame::setGeneratorInfo(cx, frame, maybeGenerator)) {
+ frame->freeFrameIterData(cx->gcContext());
+ return nullptr;
+ }
+ }
+
+ return frame;
+}
+
+/**
+ * Information held by a DebuggerFrame about a generator/async call. A
+ * Debugger.Frame's GENERATOR_INFO_SLOT, if set, holds a PrivateValue pointing
+ * to one of these.
+ *
+ * This is created and attached as soon as a generator object is created for a
+ * debuggee generator/async frame, retained across suspensions and resumptions,
+ * and cleared when the generator call ends permanently.
+ *
+ * It may seem like this information might belong in ordinary reserved slots on
+ * the DebuggerFrame object. But that isn't possible:
+ *
+ * 1) Slots cannot contain cross-compartment references directly.
+ * 2) Ordinary cross-compartment wrappers aren't good enough, because the
+ * debugger must create its own magic entries in the wrapper table for the GC
+ * to get zone collection groups right.
+ * 3) Even if we make debugger wrapper table entries by hand, hiding
+ * cross-compartment edges as PrivateValues doesn't call post-barriers, and
+ * the generational GC won't update our pointer when the generator object
+ * gets tenured.
+ *
+ * Yes, officer, I definitely knew all this in advance and designed it this way
+ * the first time.
+ *
+ * Note that it is not necessary to have a second cross-compartment wrapper
+ * table entry to cover the pointer to the generator's script. The wrapper table
+ * entries play two roles: they help the GC put a debugger zone in the same zone
+ * group as its debuggee, and they serve as roots when collecting the debuggee
+ * zone, but not the debugger zone. Since an AbstractGeneratorObject holds a
+ * strong reference to its callee's script (via the callee), and the AGO and the
+ * script are always in the same compartment, it suffices to add a
+ * cross-compartment wrapper table entry for the Debugger.Frame -> AGO edge.
+ */
+class DebuggerFrame::GeneratorInfo {
+ // An unwrapped cross-compartment reference to the generator object.
+ //
+ // Always an object.
+ //
+ // This cannot be GCPtr because we are not always destructed during sweeping;
+ // a Debugger.Frame's generator is also cleared when the generator returns
+ // permanently.
+ const HeapPtr<Value> unwrappedGenerator_;
+
+ // A cross-compartment reference to the generator's script.
+ const HeapPtr<JSScript*> generatorScript_;
+
+ public:
+ GeneratorInfo(Handle<AbstractGeneratorObject*> unwrappedGenerator,
+ HandleScript generatorScript)
+ : unwrappedGenerator_(ObjectValue(*unwrappedGenerator)),
+ generatorScript_(generatorScript) {}
+
+ // Trace a rooted instance of this class, e.g. a Rooted<GeneratorInfo>.
+ void trace(JSTracer* tracer) {
+ TraceRoot(tracer, &unwrappedGenerator_, "Debugger.Frame generator object");
+ TraceRoot(tracer, &generatorScript_, "Debugger.Frame generator script");
+ }
+ // Trace a GeneratorInfo from a DebuggerFrame object.
+ void trace(JSTracer* tracer, DebuggerFrame& frameObj) {
+ TraceCrossCompartmentEdge(tracer, &frameObj, &unwrappedGenerator_,
+ "Debugger.Frame generator object");
+ TraceCrossCompartmentEdge(tracer, &frameObj, &generatorScript_,
+ "Debugger.Frame generator script");
+ }
+
+ AbstractGeneratorObject& unwrappedGenerator() const {
+ return unwrappedGenerator_.toObject().as<AbstractGeneratorObject>();
+ }
+
+ JSScript* generatorScript() { return generatorScript_; }
+
+ bool isGeneratorScriptAboutToBeFinalized() {
+ return IsAboutToBeFinalized(generatorScript_);
+ }
+};
+
+bool js::DebuggerFrame::isSuspended() const {
+ return hasGeneratorInfo() &&
+ generatorInfo()->unwrappedGenerator().isSuspended();
+}
+
+js::AbstractGeneratorObject& js::DebuggerFrame::unwrappedGenerator() const {
+ return generatorInfo()->unwrappedGenerator();
+}
+
+#ifdef DEBUG
+JSScript* js::DebuggerFrame::generatorScript() const {
+ return generatorInfo()->generatorScript();
+}
+#endif
+
+/* static */
+bool DebuggerFrame::setGeneratorInfo(JSContext* cx,
+ Handle<DebuggerFrame*> frame,
+ Handle<AbstractGeneratorObject*> genObj) {
+ cx->check(frame);
+
+ MOZ_ASSERT(!frame->hasGeneratorInfo());
+ MOZ_ASSERT(!genObj->isClosed());
+
+ // When we initialize the generator information, we do not need to adjust
+ // the stepper increment, because either it was already incremented when
+ // the step hook was added, or we're setting this into on a new DebuggerFrame
+ // that has not yet had the chance for a hook to be added to it.
+ MOZ_ASSERT_IF(frame->onStepHandler(), frame->frameIterData());
+ MOZ_ASSERT_IF(!frame->frameIterData(), !frame->onStepHandler());
+
+ // There are two relations we must establish:
+ //
+ // 1) The DebuggerFrame must point to the AbstractGeneratorObject.
+ //
+ // 2) The generator's script's observer count must be bumped.
+
+ RootedScript script(cx, genObj->callee().nonLazyScript());
+ Rooted<UniquePtr<GeneratorInfo>> info(
+ cx, cx->make_unique<GeneratorInfo>(genObj, script));
+ if (!info) {
+ return false;
+ }
+
+ AutoRealm ar(cx, script);
+
+ // All frames running a debuggee script must themselves be marked as
+ // debuggee frames. Bumping a script's generator observer count makes it a
+ // debuggee, so we need to mark all frames on the stack running it as
+ // debuggees as well, not just this one. This call takes care of all that.
+ if (!Debugger::ensureExecutionObservabilityOfScript(cx, script)) {
+ return false;
+ }
+
+ if (!DebugScript::incrementGeneratorObserverCount(cx, script)) {
+ return false;
+ }
+
+ InitReservedSlot(frame, GENERATOR_INFO_SLOT, info.release(),
+ MemoryUse::DebuggerFrameGeneratorInfo);
+ return true;
+}
+
+void DebuggerFrame::terminate(JS::GCContext* gcx, AbstractFramePtr frame) {
+ if (frameIterData()) {
+ // If no frame pointer was provided to decrement the stepper counter,
+ // then we must be terminating a generator, otherwise the stepper count
+ // would have no way to synchronize properly.
+ MOZ_ASSERT_IF(!frame, hasGeneratorInfo());
+
+ freeFrameIterData(gcx);
+ if (frame && !hasGeneratorInfo() && onStepHandler()) {
+ // If we are terminating a non-generator frame that had a step handler,
+ // we need to decrement the counter to keep things in sync.
+ decrementStepperCounter(gcx, frame);
+ }
+ }
+
+ if (!hasGeneratorInfo()) {
+ return;
+ }
+
+ GeneratorInfo* info = generatorInfo();
+
+ // 3) The generator's script's observer count must be dropped.
+ //
+ // For ordinary calls, Debugger.Frame objects drop the script's stepper count
+ // when the frame is popped, but for generators, they leave the stepper count
+ // incremented across suspensions. This means that, whereas ordinary calls
+ // never need to drop the stepper count from the D.F finalizer, generator
+ // calls may.
+ if (!info->isGeneratorScriptAboutToBeFinalized()) {
+ JSScript* generatorScript = info->generatorScript();
+ DebugScript::decrementGeneratorObserverCount(gcx, generatorScript);
+ if (onStepHandler()) {
+ // If we are terminating a generator frame that had a step handler,
+ // we need to decrement the counter to keep things in sync.
+ decrementStepperCounter(gcx, generatorScript);
+ }
+ }
+
+ // 1) The DebuggerFrame must no longer point to the AbstractGeneratorObject.
+ setReservedSlot(GENERATOR_INFO_SLOT, UndefinedValue());
+ gcx->delete_(this, info, MemoryUse::DebuggerFrameGeneratorInfo);
+}
+
+void DebuggerFrame::suspend(JS::GCContext* gcx) {
+ // There must be generator info because otherwise this would be the same
+ // overall behavior as terminate() except that here we do not properly
+ // adjust stepper counts.
+ MOZ_ASSERT(hasGeneratorInfo());
+
+ freeFrameIterData(gcx);
+}
+
+/* static */
+bool DebuggerFrame::getCallee(JSContext* cx, Handle<DebuggerFrame*> frame,
+ MutableHandle<DebuggerObject*> result) {
+ RootedObject callee(cx);
+ if (frame->isOnStack()) {
+ AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+ if (referent.isFunctionFrame()) {
+ callee = referent.callee();
+ }
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+
+ callee = &frame->generatorInfo()->unwrappedGenerator().callee();
+ }
+
+ return frame->owner()->wrapNullableDebuggeeObject(cx, callee, result);
+}
+
+/* static */
+bool DebuggerFrame::getIsConstructing(JSContext* cx,
+ Handle<DebuggerFrame*> frame,
+ bool& result) {
+ if (frame->isOnStack()) {
+ FrameIter iter = frame->getFrameIter(cx);
+
+ result = iter.isFunctionFrame() && iter.isConstructing();
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+
+ // Generators and async functions can't be constructed.
+ result = false;
+ }
+ return true;
+}
+
+static void UpdateFrameIterPc(FrameIter& iter) {
+ if (iter.abstractFramePtr().isWasmDebugFrame()) {
+ // Wasm debug frames don't need their pc updated -- it's null.
+ return;
+ }
+
+ if (iter.abstractFramePtr().isRematerializedFrame()) {
+#ifdef DEBUG
+ // Rematerialized frames don't need their pc updated. The reason we
+ // need to update pc is because we might get the same Debugger.Frame
+ // object for multiple re-entries into debugger code from debuggee
+ // code. This reentrancy is not possible with rematerialized frames,
+ // because when returning to debuggee code, we would have bailed out
+ // to baseline.
+ //
+ // We walk the stack to assert that it doesn't need updating.
+ jit::RematerializedFrame* frame =
+ iter.abstractFramePtr().asRematerializedFrame();
+ jit::JitFrameLayout* jsFrame = (jit::JitFrameLayout*)frame->top();
+ jit::JitActivation* activation = iter.activation()->asJit();
+
+ JSContext* cx = TlsContext.get();
+ MOZ_ASSERT(cx == activation->cx());
+
+ ActivationIterator activationIter(cx);
+ while (activationIter.activation() != activation) {
+ ++activationIter;
+ }
+
+ OnlyJSJitFrameIter jitIter(activationIter);
+ while (!jitIter.frame().isIonJS() || jitIter.frame().jsFrame() != jsFrame) {
+ ++jitIter;
+ }
+
+ jit::InlineFrameIterator ionInlineIter(cx, &jitIter.frame());
+ while (ionInlineIter.frameNo() != frame->frameNo()) {
+ ++ionInlineIter;
+ }
+
+ MOZ_ASSERT(ionInlineIter.pc() == iter.pc());
+#endif
+ return;
+ }
+
+ iter.updatePcQuadratic();
+}
+
+/* static */
+bool DebuggerFrame::getEnvironment(JSContext* cx, Handle<DebuggerFrame*> frame,
+ MutableHandle<DebuggerEnvironment*> result) {
+ Debugger* dbg = frame->owner();
+ Rooted<Env*> env(cx);
+
+ if (frame->isOnStack()) {
+ FrameIter iter = frame->getFrameIter(cx);
+
+ {
+ AutoRealm ar(cx, iter.abstractFramePtr().environmentChain());
+ UpdateFrameIterPc(iter);
+ env = GetDebugEnvironmentForFrame(cx, iter.abstractFramePtr(), iter.pc());
+ }
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+
+ AbstractGeneratorObject& genObj =
+ frame->generatorInfo()->unwrappedGenerator();
+ JSScript* script = frame->generatorInfo()->generatorScript();
+
+ {
+ AutoRealm ar(cx, &genObj.environmentChain());
+ env = GetDebugEnvironmentForSuspendedGenerator(cx, script, genObj);
+ }
+ }
+
+ if (!env) {
+ return false;
+ }
+
+ return dbg->wrapEnvironment(cx, env, result);
+}
+
+/* static */
+bool DebuggerFrame::getOffset(JSContext* cx, Handle<DebuggerFrame*> frame,
+ size_t& result) {
+ if (frame->isOnStack()) {
+ FrameIter iter = frame->getFrameIter(cx);
+
+ AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+ if (referent.isWasmDebugFrame()) {
+ iter.wasmUpdateBytecodeOffset();
+ result = iter.wasmBytecodeOffset();
+ } else {
+ JSScript* script = iter.script();
+ UpdateFrameIterPc(iter);
+ jsbytecode* pc = iter.pc();
+ result = script->pcToOffset(pc);
+ }
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+
+ AbstractGeneratorObject& genObj =
+ frame->generatorInfo()->unwrappedGenerator();
+ JSScript* script = frame->generatorInfo()->generatorScript();
+ result = script->resumeOffsets()[genObj.resumeIndex()];
+ }
+ return true;
+}
+
+/* static */
+bool DebuggerFrame::getOlder(JSContext* cx, Handle<DebuggerFrame*> frame,
+ MutableHandle<DebuggerFrame*> result) {
+ if (frame->isOnStack()) {
+ Debugger* dbg = frame->owner();
+ FrameIter iter = frame->getFrameIter(cx);
+
+ while (true) {
+ Activation& activation = *iter.activation();
+ ++iter;
+
+ // If the parent frame crosses an explicit async stack boundary, we
+ // treat that as an indication to stop traversing sync frames, so that
+ // the on-stack Debugger.Frame instances align with what you would
+ // see in a stringified stack trace.
+ if (iter.activation() != &activation && activation.asyncStack() &&
+ activation.asyncCallIsExplicit()) {
+ break;
+ }
+
+ // If there is no parent frame, we're done.
+ if (iter.done()) {
+ break;
+ }
+
+ if (dbg->observesFrame(iter)) {
+ if (iter.isIon() && !iter.ensureHasRematerializedFrame(cx)) {
+ return false;
+ }
+ return dbg->getFrame(cx, iter, result);
+ }
+ }
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+
+ // If the frame is suspended, there is no older frame.
+ }
+
+ result.set(nullptr);
+ return true;
+}
+
+/* static */
+bool DebuggerFrame::getAsyncPromise(JSContext* cx, Handle<DebuggerFrame*> frame,
+ MutableHandle<DebuggerObject*> result) {
+ MOZ_ASSERT(frame->isOnStack() || frame->isSuspended());
+
+ if (!frame->hasGeneratorInfo()) {
+ // An on-stack frame may not have an associated generator yet when the
+ // frame is initially entered.
+ result.set(nullptr);
+ return true;
+ }
+
+ RootedObject resultObject(cx);
+ AbstractGeneratorObject& generator = frame->unwrappedGenerator();
+ if (generator.is<AsyncFunctionGeneratorObject>()) {
+ resultObject = generator.as<AsyncFunctionGeneratorObject>().promise();
+ } else if (generator.is<AsyncGeneratorObject>()) {
+ Rooted<AsyncGeneratorObject*> asyncGen(
+ cx, &generator.as<AsyncGeneratorObject>());
+ // In initial function execution, there is no promise.
+ if (!asyncGen->isQueueEmpty()) {
+ resultObject = AsyncGeneratorObject::peekRequest(asyncGen)->promise();
+ }
+ } else {
+ MOZ_CRASH("Unknown async generator type");
+ }
+
+ return frame->owner()->wrapNullableDebuggeeObject(cx, resultObject, result);
+}
+
+/* static */
+bool DebuggerFrame::getThis(JSContext* cx, Handle<DebuggerFrame*> frame,
+ MutableHandleValue result) {
+ Debugger* dbg = frame->owner();
+
+ if (frame->isOnStack()) {
+ if (!requireScriptReferent(cx, frame)) {
+ return false;
+ }
+ FrameIter iter = frame->getFrameIter(cx);
+
+ {
+ AbstractFramePtr frame = iter.abstractFramePtr();
+ AutoRealm ar(cx, frame.environmentChain());
+
+ UpdateFrameIterPc(iter);
+
+ if (!GetThisValueForDebuggerFrameMaybeOptimizedOut(cx, frame, iter.pc(),
+ result)) {
+ return false;
+ }
+ }
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+
+ AbstractGeneratorObject& genObj =
+ frame->generatorInfo()->unwrappedGenerator();
+ AutoRealm ar(cx, &genObj);
+ JSScript* script = frame->generatorInfo()->generatorScript();
+
+ if (!GetThisValueForDebuggerSuspendedGeneratorMaybeOptimizedOut(
+ cx, genObj, script, result)) {
+ return false;
+ }
+ }
+
+ return dbg->wrapDebuggeeValue(cx, result);
+}
+
+/* static */
+DebuggerFrameType DebuggerFrame::getType(Handle<DebuggerFrame*> frame) {
+ if (frame->isOnStack()) {
+ AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+
+ // Indirect eval frames are both isGlobalFrame() and isEvalFrame(), so the
+ // order of checks here is significant.
+ if (referent.isEvalFrame()) {
+ return DebuggerFrameType::Eval;
+ }
+
+ if (referent.isGlobalFrame()) {
+ return DebuggerFrameType::Global;
+ }
+
+ if (referent.isFunctionFrame()) {
+ return DebuggerFrameType::Call;
+ }
+
+ if (referent.isModuleFrame()) {
+ return DebuggerFrameType::Module;
+ }
+
+ if (referent.isWasmDebugFrame()) {
+ return DebuggerFrameType::WasmCall;
+ }
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+
+ return DebuggerFrameType::Call;
+ }
+
+ MOZ_CRASH("Unknown frame type");
+}
+
+/* static */
+DebuggerFrameImplementation DebuggerFrame::getImplementation(
+ Handle<DebuggerFrame*> frame) {
+ AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+
+ if (referent.isBaselineFrame()) {
+ return DebuggerFrameImplementation::Baseline;
+ }
+
+ if (referent.isRematerializedFrame()) {
+ return DebuggerFrameImplementation::Ion;
+ }
+
+ if (referent.isWasmDebugFrame()) {
+ return DebuggerFrameImplementation::Wasm;
+ }
+
+ return DebuggerFrameImplementation::Interpreter;
+}
+
+/*
+ * If succesful, transfers the ownership of the given `handler` to this
+ * Debugger.Frame. Note that on failure, the ownership of `handler` is not
+ * transferred, and the caller is responsible for cleaning it up.
+ */
+/* static */
+bool DebuggerFrame::setOnStepHandler(JSContext* cx,
+ Handle<DebuggerFrame*> frame,
+ UniquePtr<OnStepHandler> handlerArg) {
+ // Handler has never been successfully associated with the frame so allow
+ // UniquePtr to delete it rather than calling drop() if we return early from
+ // this method..
+ Rooted<UniquePtr<OnStepHandler>> handler(cx, std::move(handlerArg));
+
+ OnStepHandler* prior = frame->onStepHandler();
+ if (handler.get() == prior) {
+ return true;
+ }
+
+ JS::GCContext* gcx = cx->gcContext();
+ if (frame->isOnStack()) {
+ AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+
+ // Adjust execution observability and step counts on whatever code (JS or
+ // Wasm) this frame is running.
+ if (handler && !prior) {
+ if (!frame->incrementStepperCounter(cx, referent)) {
+ return false;
+ }
+ } else if (!handler && prior) {
+ frame->decrementStepperCounter(cx->gcContext(), referent);
+ }
+ } else if (frame->isSuspended()) {
+ RootedScript script(cx, frame->generatorInfo()->generatorScript());
+
+ if (handler && !prior) {
+ if (!frame->incrementStepperCounter(cx, script)) {
+ return false;
+ }
+ } else if (!handler && prior) {
+ frame->decrementStepperCounter(cx->gcContext(), script);
+ }
+ } else {
+ // If the frame is entirely dead, we still allow setting the onStep
+ // handler, but it has no effect.
+ }
+
+ // Now that the stepper counts and observability are set correctly, we can
+ // actually switch the handler.
+ if (prior) {
+ prior->drop(gcx, frame);
+ }
+
+ if (handler) {
+ handler->hold(frame);
+ frame->setReservedSlot(ONSTEP_HANDLER_SLOT,
+ PrivateValue(handler.get().release()));
+ } else {
+ frame->setReservedSlot(ONSTEP_HANDLER_SLOT, UndefinedValue());
+ }
+
+ return true;
+}
+
+bool DebuggerFrame::incrementStepperCounter(JSContext* cx,
+ AbstractFramePtr referent) {
+ if (!referent.isWasmDebugFrame()) {
+ RootedScript script(cx, referent.script());
+ return incrementStepperCounter(cx, script);
+ }
+
+ wasm::Instance* instance = referent.asWasmDebugFrame()->instance();
+ wasm::DebugFrame* wasmFrame = referent.asWasmDebugFrame();
+ // Single stepping toggled off->on.
+ if (!instance->debug().incrementStepperCount(cx, instance,
+ wasmFrame->funcIndex())) {
+ return false;
+ }
+
+ return true;
+}
+
+bool DebuggerFrame::incrementStepperCounter(JSContext* cx,
+ HandleScript script) {
+ // Single stepping toggled off->on.
+ AutoRealm ar(cx, script);
+ // Ensure observability *before* incrementing the step mode count.
+ // Calling this function after calling incrementStepperCount
+ // will make it a no-op.
+ if (!Debugger::ensureExecutionObservabilityOfScript(cx, script)) {
+ return false;
+ }
+ if (!DebugScript::incrementStepperCount(cx, script)) {
+ return false;
+ }
+
+ return true;
+}
+
+void DebuggerFrame::decrementStepperCounter(JS::GCContext* gcx,
+ AbstractFramePtr referent) {
+ if (!referent.isWasmDebugFrame()) {
+ decrementStepperCounter(gcx, referent.script());
+ return;
+ }
+
+ wasm::Instance* instance = referent.asWasmDebugFrame()->instance();
+ wasm::DebugFrame* wasmFrame = referent.asWasmDebugFrame();
+ // Single stepping toggled on->off.
+ instance->debug().decrementStepperCount(gcx, instance,
+ wasmFrame->funcIndex());
+}
+
+void DebuggerFrame::decrementStepperCounter(JS::GCContext* gcx,
+ JSScript* script) {
+ // Single stepping toggled on->off.
+ DebugScript::decrementStepperCount(gcx, script);
+}
+
+/* static */
+bool DebuggerFrame::getArguments(JSContext* cx, Handle<DebuggerFrame*> frame,
+ MutableHandle<DebuggerArguments*> result) {
+ Value argumentsv = frame->getReservedSlot(ARGUMENTS_SLOT);
+ if (!argumentsv.isUndefined()) {
+ result.set(argumentsv.isObject()
+ ? &argumentsv.toObject().as<DebuggerArguments>()
+ : nullptr);
+ return true;
+ }
+
+ AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+
+ Rooted<DebuggerArguments*> arguments(cx);
+ if (referent.hasArgs()) {
+ Rooted<GlobalObject*> global(cx, &frame->global());
+ RootedObject proto(cx, GlobalObject::getOrCreateArrayPrototype(cx, global));
+ if (!proto) {
+ return false;
+ }
+ arguments = DebuggerArguments::create(cx, proto, frame);
+ if (!arguments) {
+ return false;
+ }
+ } else {
+ arguments = nullptr;
+ }
+
+ result.set(arguments);
+ frame->setReservedSlot(ARGUMENTS_SLOT, ObjectOrNullValue(result));
+ return true;
+}
+
+/*
+ * Evaluate |chars[0..length-1]| in the environment |env|, treating that
+ * source as appearing starting at |lineno| in |filename|. Store the return
+ * value in |*rval|. Use |thisv| as the 'this' value.
+ *
+ * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env|
+ * must be either |frame|'s DebugScopeObject, or some extension of that
+ * environment; either way, |frame|'s scope is where newly declared variables
+ * go. In this case, |frame| must have a computed 'this' value, equal to
+ * |thisv|.
+ */
+static bool EvaluateInEnv(JSContext* cx, Handle<Env*> env,
+ AbstractFramePtr frame,
+ mozilla::Range<const char16_t> chars,
+ const EvalOptions& evalOptions,
+ MutableHandleValue rval) {
+ cx->check(env, frame);
+
+ CompileOptions options(cx);
+ const char* filename =
+ evalOptions.filename() ? evalOptions.filename() : "debugger eval code";
+ options.setIsRunOnce(true)
+ .setNoScriptRval(false)
+ .setFileAndLine(filename, evalOptions.lineno())
+ .setHideScriptFromDebugger(evalOptions.hideFromDebugger())
+ .setIntroductionType("debugger eval")
+ /* Do not perform the Javascript filename validation security check for
+ * javascript executions sent through the debugger. Besides making up
+ * a filename for these codepaths, we must allow arbitrary JS execution
+ * for the Browser toolbox to function. */
+ .setSkipFilenameValidation(true)
+ /* Don't lazy parse. We need full-parsing to correctly support bytecode
+ * emission for private fields/methods. See EmitterScope::lookupPrivate.
+ */
+ .setForceFullParse();
+
+ if (frame && frame.hasScript() && frame.script()->strict()) {
+ options.setForceStrictMode();
+ }
+
+ SourceText<char16_t> srcBuf;
+ if (!srcBuf.init(cx, chars.begin().get(), chars.length(),
+ SourceOwnership::Borrowed)) {
+ return false;
+ }
+
+ RootedScript callerScript(
+ cx, frame && frame.hasScript() ? frame.script() : nullptr);
+ RootedScript script(cx);
+
+ ScopeKind scopeKind;
+ if (IsGlobalLexicalEnvironment(env)) {
+ scopeKind = ScopeKind::Global;
+ } else {
+ scopeKind = ScopeKind::NonSyntactic;
+ options.setNonSyntacticScope(true);
+ }
+
+ if (frame) {
+ MOZ_ASSERT(scopeKind == ScopeKind::NonSyntactic);
+ Rooted<Scope*> scope(cx,
+ GlobalScope::createEmpty(cx, ScopeKind::NonSyntactic));
+ if (!scope) {
+ return false;
+ }
+
+ script = frontend::CompileEvalScript(cx, options, srcBuf, scope, env);
+ if (!script) {
+ return false;
+ }
+ } else {
+ // Do not consider executeInGlobal{WithBindings} as an eval, but instead
+ // as executing a series of statements at the global level. This is to
+ // circumvent the fresh lexical scope that all eval have, so that the
+ // users of executeInGlobal, like the web console, may add new bindings to
+ // the global scope.
+
+ MOZ_ASSERT(scopeKind == ScopeKind::Global ||
+ scopeKind == ScopeKind::NonSyntactic);
+
+ AutoReportFrontendContext fc(cx);
+ script = frontend::CompileGlobalScript(cx, &fc,
+ cx->stackLimitForCurrentPrincipal(),
+ options, srcBuf, scopeKind);
+ if (!script) {
+ return false;
+ }
+ }
+
+ return ExecuteKernel(cx, script, env, frame, rval);
+}
+
+Result<Completion> js::DebuggerGenericEval(
+ JSContext* cx, const mozilla::Range<const char16_t> chars,
+ HandleObject bindings, const EvalOptions& options, Debugger* dbg,
+ HandleObject envArg, FrameIter* iter) {
+ // Either we're specifying the frame, or a global.
+ MOZ_ASSERT_IF(iter, !envArg);
+ MOZ_ASSERT_IF(!iter, envArg && IsGlobalLexicalEnvironment(envArg));
+
+ // Gather keys and values of bindings, if any. This must be done in the
+ // debugger compartment, since that is where any exceptions must be thrown.
+ RootedIdVector keys(cx);
+ RootedValueVector values(cx);
+ if (bindings) {
+ if (!GetPropertyKeys(cx, bindings, JSITER_OWNONLY, &keys) ||
+ !values.growBy(keys.length())) {
+ return cx->alreadyReportedError();
+ }
+ for (size_t i = 0; i < keys.length(); i++) {
+ MutableHandleValue valp = values[i];
+ if (!GetProperty(cx, bindings, bindings, keys[i], valp) ||
+ !dbg->unwrapDebuggeeValue(cx, valp)) {
+ return cx->alreadyReportedError();
+ }
+ }
+ }
+
+ Maybe<AutoRealm> ar;
+ if (iter) {
+ ar.emplace(cx, iter->environmentChain(cx));
+ } else {
+ ar.emplace(cx, envArg);
+ }
+
+ Rooted<Env*> env(cx);
+ if (iter) {
+ env = GetDebugEnvironmentForFrame(cx, iter->abstractFramePtr(), iter->pc());
+ if (!env) {
+ return cx->alreadyReportedError();
+ }
+ } else {
+ env = envArg;
+ }
+
+ // If evalWithBindings, create the inner environment.
+ if (bindings) {
+ Rooted<PlainObject*> nenv(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!nenv) {
+ return cx->alreadyReportedError();
+ }
+ RootedId id(cx);
+ for (size_t i = 0; i < keys.length(); i++) {
+ id = keys[i];
+ cx->markId(id);
+ MutableHandleValue val = values[i];
+ if (!cx->compartment()->wrap(cx, val) ||
+ !NativeDefineDataProperty(cx, nenv, id, val, 0)) {
+ return cx->alreadyReportedError();
+ }
+ }
+
+ RootedObjectVector envChain(cx);
+ if (!envChain.append(nenv)) {
+ return cx->alreadyReportedError();
+ }
+
+ RootedObject newEnv(cx);
+ if (!CreateObjectsForEnvironmentChain(cx, envChain, env, &newEnv)) {
+ return cx->alreadyReportedError();
+ }
+
+ env = newEnv;
+ }
+
+ // Note whether we are in an evaluation that might invoke the OnNativeCall
+ // hook, so that the JITs will be disabled.
+ AutoNoteDebuggerEvaluationWithOnNativeCallHook noteEvaluation(
+ cx, dbg->observesNativeCalls() ? dbg : nullptr);
+
+ // Run the code and produce the completion value.
+ LeaveDebuggeeNoExecute nnx(cx);
+ RootedValue rval(cx);
+ AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr();
+
+ bool ok = EvaluateInEnv(cx, env, frame, chars, options, &rval);
+ Rooted<Completion> completion(cx, Completion::fromJSResult(cx, ok, rval));
+ ar.reset();
+ return completion.get();
+}
+
+/* static */
+Result<Completion> DebuggerFrame::eval(JSContext* cx,
+ Handle<DebuggerFrame*> frame,
+ mozilla::Range<const char16_t> chars,
+ HandleObject bindings,
+ const EvalOptions& options) {
+ MOZ_ASSERT(frame->isOnStack());
+
+ Debugger* dbg = frame->owner();
+ FrameIter iter = frame->getFrameIter(cx);
+
+ UpdateFrameIterPc(iter);
+
+ return DebuggerGenericEval(cx, chars, bindings, options, dbg, nullptr, &iter);
+}
+
+bool DebuggerFrame::isOnStack() const {
+ // Note: this is equivalent to checking frameIterData() != nullptr, but works
+ // also when called from the trace hook during a moving GC.
+ return !getFixedSlot(FRAME_ITER_SLOT).isUndefined();
+}
+
+OnStepHandler* DebuggerFrame::onStepHandler() const {
+ return maybePtrFromReservedSlot<OnStepHandler>(ONSTEP_HANDLER_SLOT);
+}
+
+OnPopHandler* DebuggerFrame::onPopHandler() const {
+ return maybePtrFromReservedSlot<OnPopHandler>(ONPOP_HANDLER_SLOT);
+}
+
+void DebuggerFrame::setOnPopHandler(JSContext* cx, OnPopHandler* handler) {
+ OnPopHandler* prior = onPopHandler();
+ if (handler == prior) {
+ return;
+ }
+
+ JS::GCContext* gcx = cx->gcContext();
+
+ if (prior) {
+ prior->drop(gcx, this);
+ }
+
+ if (handler) {
+ setReservedSlot(ONPOP_HANDLER_SLOT, PrivateValue(handler));
+ handler->hold(this);
+ } else {
+ setReservedSlot(ONPOP_HANDLER_SLOT, UndefinedValue());
+ }
+}
+
+FrameIter::Data* DebuggerFrame::frameIterData() const {
+ return maybePtrFromReservedSlot<FrameIter::Data>(FRAME_ITER_SLOT);
+}
+
+/* static */
+AbstractFramePtr DebuggerFrame::getReferent(Handle<DebuggerFrame*> frame) {
+ FrameIter iter(*frame->frameIterData());
+ return iter.abstractFramePtr();
+}
+
+FrameIter DebuggerFrame::getFrameIter(JSContext* cx) {
+ FrameIter::Data* data = frameIterData();
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->cx_ == cx);
+
+ return FrameIter(*data);
+}
+
+/* static */
+bool DebuggerFrame::requireScriptReferent(JSContext* cx,
+ Handle<DebuggerFrame*> frame) {
+ AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+ if (!referent.hasScript()) {
+ RootedValue frameobj(cx, ObjectValue(*frame));
+ ReportValueError(cx, JSMSG_DEBUG_BAD_REFERENT, JSDVG_SEARCH_STACK, frameobj,
+ nullptr, "a script frame");
+ return false;
+ }
+ return true;
+}
+
+void DebuggerFrame::setFrameIterData(FrameIter::Data* data) {
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(!frameIterData());
+ InitReservedSlot(this, FRAME_ITER_SLOT, data,
+ MemoryUse::DebuggerFrameIterData);
+}
+
+void DebuggerFrame::freeFrameIterData(JS::GCContext* gcx) {
+ if (FrameIter::Data* data = frameIterData()) {
+ gcx->delete_(this, data, MemoryUse::DebuggerFrameIterData);
+ setReservedSlot(FRAME_ITER_SLOT, UndefinedValue());
+ }
+}
+
+bool DebuggerFrame::replaceFrameIterData(JSContext* cx, const FrameIter& iter) {
+ FrameIter::Data* data = iter.copyData();
+ if (!data) {
+ return false;
+ }
+ freeFrameIterData(cx->gcContext());
+ setFrameIterData(data);
+ return true;
+}
+
+/* static */
+void DebuggerFrame::finalize(JS::GCContext* gcx, JSObject* obj) {
+ MOZ_ASSERT(gcx->onMainThread());
+
+ DebuggerFrame& frameobj = obj->as<DebuggerFrame>();
+
+ // Connections between dying Debugger.Frames and their
+ // AbstractGeneratorObjects, as well as the frame's stack data should have
+ // been by a call to terminate() from sweepAll or some other place.
+ MOZ_ASSERT(!frameobj.hasGeneratorInfo());
+ MOZ_ASSERT(!frameobj.frameIterData());
+ OnStepHandler* onStepHandler = frameobj.onStepHandler();
+ if (onStepHandler) {
+ onStepHandler->drop(gcx, &frameobj);
+ }
+ OnPopHandler* onPopHandler = frameobj.onPopHandler();
+ if (onPopHandler) {
+ onPopHandler->drop(gcx, &frameobj);
+ }
+}
+
+void DebuggerFrame::trace(JSTracer* trc) {
+ OnStepHandler* onStepHandler = this->onStepHandler();
+ if (onStepHandler) {
+ onStepHandler->trace(trc);
+ }
+ OnPopHandler* onPopHandler = this->onPopHandler();
+ if (onPopHandler) {
+ onPopHandler->trace(trc);
+ }
+
+ if (hasGeneratorInfo()) {
+ generatorInfo()->trace(trc, *this);
+ }
+}
+
+/* static */
+DebuggerFrame* DebuggerFrame::check(JSContext* cx, HandleValue thisv) {
+ JSObject* thisobj = RequireObject(cx, thisv);
+ if (!thisobj) {
+ return nullptr;
+ }
+ if (!thisobj->is<DebuggerFrame>()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INCOMPATIBLE_PROTO, "Debugger.Frame",
+ "method", thisobj->getClass()->name);
+ return nullptr;
+ }
+
+ return &thisobj->as<DebuggerFrame>();
+}
+
+struct MOZ_STACK_CLASS DebuggerFrame::CallData {
+ JSContext* cx;
+ const CallArgs& args;
+
+ Handle<DebuggerFrame*> frame;
+
+ CallData(JSContext* cx, const CallArgs& args, Handle<DebuggerFrame*> frame)
+ : cx(cx), args(args), frame(frame) {}
+
+ bool argumentsGetter();
+ bool calleeGetter();
+ bool constructingGetter();
+ bool environmentGetter();
+ bool generatorGetter();
+ bool asyncPromiseGetter();
+ bool olderSavedFrameGetter();
+ bool liveGetter();
+ bool onStackGetter();
+ bool terminatedGetter();
+ bool offsetGetter();
+ bool olderGetter();
+ bool getScript();
+ bool thisGetter();
+ bool typeGetter();
+ bool implementationGetter();
+ bool onStepGetter();
+ bool onStepSetter();
+ bool onPopGetter();
+ bool onPopSetter();
+ bool evalMethod();
+ bool evalWithBindingsMethod();
+
+ using Method = bool (CallData::*)();
+
+ template <Method MyMethod>
+ static bool ToNative(JSContext* cx, unsigned argc, Value* vp);
+
+ bool ensureOnStack() const;
+ bool ensureOnStackOrSuspended() const;
+};
+
+template <DebuggerFrame::CallData::Method MyMethod>
+/* static */
+bool DebuggerFrame::CallData::ToNative(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<DebuggerFrame*> frame(cx, DebuggerFrame::check(cx, args.thisv()));
+ if (!frame) {
+ return false;
+ }
+
+ CallData data(cx, args, frame);
+ return (data.*MyMethod)();
+}
+
+static bool EnsureOnStack(JSContext* cx, Handle<DebuggerFrame*> frame) {
+ MOZ_ASSERT(frame);
+ if (!frame->isOnStack()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEBUG_NOT_ON_STACK, "Debugger.Frame");
+ return false;
+ }
+
+ return true;
+}
+static bool EnsureOnStackOrSuspended(JSContext* cx,
+ Handle<DebuggerFrame*> frame) {
+ MOZ_ASSERT(frame);
+ if (!frame->isOnStack() && !frame->isSuspended()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEBUG_NOT_ON_STACK_OR_SUSPENDED,
+ "Debugger.Frame");
+ return false;
+ }
+
+ return true;
+}
+
+bool DebuggerFrame::CallData::ensureOnStack() const {
+ return EnsureOnStack(cx, frame);
+}
+bool DebuggerFrame::CallData::ensureOnStackOrSuspended() const {
+ return EnsureOnStackOrSuspended(cx, frame);
+}
+
+bool DebuggerFrame::CallData::typeGetter() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ DebuggerFrameType type = DebuggerFrame::getType(frame);
+
+ JSString* str;
+ switch (type) {
+ case DebuggerFrameType::Eval:
+ str = cx->names().eval;
+ break;
+ case DebuggerFrameType::Global:
+ str = cx->names().global;
+ break;
+ case DebuggerFrameType::Call:
+ str = cx->names().call;
+ break;
+ case DebuggerFrameType::Module:
+ str = cx->names().module;
+ break;
+ case DebuggerFrameType::WasmCall:
+ str = cx->names().wasmcall;
+ break;
+ default:
+ MOZ_CRASH("bad DebuggerFrameType value");
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+bool DebuggerFrame::CallData::implementationGetter() {
+ if (!ensureOnStack()) {
+ return false;
+ }
+
+ DebuggerFrameImplementation implementation =
+ DebuggerFrame::getImplementation(frame);
+
+ const char* s;
+ switch (implementation) {
+ case DebuggerFrameImplementation::Baseline:
+ s = "baseline";
+ break;
+ case DebuggerFrameImplementation::Ion:
+ s = "ion";
+ break;
+ case DebuggerFrameImplementation::Interpreter:
+ s = "interpreter";
+ break;
+ case DebuggerFrameImplementation::Wasm:
+ s = "wasm";
+ break;
+ default:
+ MOZ_CRASH("bad DebuggerFrameImplementation value");
+ }
+
+ JSAtom* str = Atomize(cx, s, strlen(s));
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+bool DebuggerFrame::CallData::environmentGetter() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ Rooted<DebuggerEnvironment*> result(cx);
+ if (!DebuggerFrame::getEnvironment(cx, frame, &result)) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+bool DebuggerFrame::CallData::calleeGetter() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ Rooted<DebuggerObject*> result(cx);
+ if (!DebuggerFrame::getCallee(cx, frame, &result)) {
+ return false;
+ }
+
+ args.rval().setObjectOrNull(result);
+ return true;
+}
+
+bool DebuggerFrame::CallData::generatorGetter() {
+ JS_ReportErrorASCII(cx,
+ "Debugger.Frame.prototype.generator has been removed. "
+ "Use frame.script.isGeneratorFunction instead.");
+ return false;
+}
+
+bool DebuggerFrame::CallData::constructingGetter() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ bool result;
+ if (!DebuggerFrame::getIsConstructing(cx, frame, result)) {
+ return false;
+ }
+
+ args.rval().setBoolean(result);
+ return true;
+}
+
+bool DebuggerFrame::CallData::asyncPromiseGetter() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ RootedScript script(cx);
+ if (frame->isOnStack()) {
+ FrameIter iter = frame->getFrameIter(cx);
+ AbstractFramePtr framePtr = iter.abstractFramePtr();
+
+ if (!framePtr.isWasmDebugFrame()) {
+ script = framePtr.script();
+ }
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+ script = frame->generatorInfo()->generatorScript();
+ }
+ // The async promise value is only provided for async functions and
+ // async generator functions.
+ if (!script || !script->isAsync()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ Rooted<DebuggerObject*> result(cx);
+ if (!DebuggerFrame::getAsyncPromise(cx, frame, &result)) {
+ return false;
+ }
+
+ args.rval().setObjectOrNull(result);
+ return true;
+}
+
+bool DebuggerFrame::CallData::olderSavedFrameGetter() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ Rooted<SavedFrame*> result(cx);
+ if (!DebuggerFrame::getOlderSavedFrame(cx, frame, &result)) {
+ return false;
+ }
+
+ args.rval().setObjectOrNull(result);
+ return true;
+}
+
+/* static */
+bool DebuggerFrame::getOlderSavedFrame(JSContext* cx,
+ Handle<DebuggerFrame*> frame,
+ MutableHandle<SavedFrame*> result) {
+ if (frame->isOnStack()) {
+ Debugger* dbg = frame->owner();
+ FrameIter iter = frame->getFrameIter(cx);
+
+ while (true) {
+ Activation& activation = *iter.activation();
+ ++iter;
+
+ // If the parent frame crosses an explicit async stack boundary, or we
+ // have hit the end of the synchronous frames, we want to switch over
+ // to using SavedFrames.
+ if (iter.activation() != &activation && activation.asyncStack() &&
+ (activation.asyncCallIsExplicit() || iter.done())) {
+ const char* cause = activation.asyncCause();
+ Rooted<JSAtom*> causeAtom(cx,
+ AtomizeUTF8Chars(cx, cause, strlen(cause)));
+ if (!causeAtom) {
+ return false;
+ }
+ Rooted<SavedFrame*> stackObj(cx, activation.asyncStack());
+
+ return cx->realm()->savedStacks().copyAsyncStack(
+ cx, stackObj, causeAtom, result, mozilla::Nothing());
+ }
+
+ // If there are no more parent frames, we're done.
+ if (iter.done()) {
+ break;
+ }
+
+ // If we hit another frame that we observe, then there is no saved
+ // frame that we'd want to return.
+ if (dbg->observesFrame(iter)) {
+ break;
+ }
+ }
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+ }
+
+ result.set(nullptr);
+ return true;
+}
+
+bool DebuggerFrame::CallData::thisGetter() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ return DebuggerFrame::getThis(cx, frame, args.rval());
+}
+
+bool DebuggerFrame::CallData::olderGetter() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ Rooted<DebuggerFrame*> result(cx);
+ if (!DebuggerFrame::getOlder(cx, frame, &result)) {
+ return false;
+ }
+
+ args.rval().setObjectOrNull(result);
+ return true;
+}
+
+// The getter used for each element of frame.arguments.
+// See DebuggerFrame::getArguments.
+static bool DebuggerArguments_getArg(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ int32_t i = args.callee().as<JSFunction>().getExtendedSlot(0).toInt32();
+
+ // Check that the this value is an Arguments object.
+ RootedObject argsobj(cx, RequireObject(cx, args.thisv()));
+ if (!argsobj) {
+ return false;
+ }
+ if (argsobj->getClass() != &DebuggerArguments::class_) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INCOMPATIBLE_PROTO, "Arguments",
+ "getArgument", argsobj->getClass()->name);
+ return false;
+ }
+
+ RootedValue framev(cx, argsobj->as<NativeObject>().getReservedSlot(
+ JSSLOT_DEBUGARGUMENTS_FRAME));
+ Rooted<DebuggerFrame*> thisobj(cx, DebuggerFrame::check(cx, framev));
+ if (!thisobj || !EnsureOnStack(cx, thisobj)) {
+ return false;
+ }
+
+ FrameIter iter = thisobj->getFrameIter(cx);
+ AbstractFramePtr frame = iter.abstractFramePtr();
+
+ // TODO handle wasm frame arguments -- they are not yet reflectable.
+ MOZ_ASSERT(!frame.isWasmDebugFrame(), "a wasm frame args");
+
+ // Since getters can be extracted and applied to other objects,
+ // there is no guarantee this object has an ith argument.
+ MOZ_ASSERT(i >= 0);
+ RootedValue arg(cx);
+ RootedScript script(cx);
+ if (unsigned(i) < frame.numActualArgs()) {
+ script = frame.script();
+ if (unsigned(i) < frame.numFormalArgs()) {
+ for (PositionalFormalParameterIter fi(script); fi; fi++) {
+ if (fi.argumentSlot() == unsigned(i)) {
+ // We might've been called before the CallObject was
+ // created.
+ if (fi.closedOver() && frame.hasInitialEnvironment()) {
+ arg = frame.callObj().aliasedBinding(fi);
+ } else {
+ arg = frame.unaliasedActual(i, DONT_CHECK_ALIASING);
+ }
+ break;
+ }
+ }
+ } else if (script->argsObjAliasesFormals() && frame.hasArgsObj()) {
+ arg = frame.argsObj().arg(i);
+ } else {
+ arg = frame.unaliasedActual(i, DONT_CHECK_ALIASING);
+ }
+ } else {
+ arg.setUndefined();
+ }
+
+ if (!thisobj->owner()->wrapDebuggeeValue(cx, &arg)) {
+ return false;
+ }
+ args.rval().set(arg);
+ return true;
+}
+
+/* static */
+DebuggerArguments* DebuggerArguments::create(JSContext* cx, HandleObject proto,
+ Handle<DebuggerFrame*> frame) {
+ AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+
+ Rooted<DebuggerArguments*> obj(
+ cx, NewObjectWithGivenProto<DebuggerArguments>(cx, proto));
+ if (!obj) {
+ return nullptr;
+ }
+
+ JS::SetReservedSlot(obj, FRAME_SLOT, ObjectValue(*frame));
+
+ MOZ_ASSERT(referent.numActualArgs() <= 0x7fffffff);
+ unsigned fargc = referent.numActualArgs();
+ RootedValue fargcVal(cx, Int32Value(fargc));
+ if (!NativeDefineDataProperty(cx, obj, cx->names().length, fargcVal,
+ JSPROP_PERMANENT | JSPROP_READONLY)) {
+ return nullptr;
+ }
+
+ Rooted<jsid> id(cx);
+ for (unsigned i = 0; i < fargc; i++) {
+ RootedFunction getobj(cx);
+ getobj = NewNativeFunction(cx, DebuggerArguments_getArg, 0, nullptr,
+ gc::AllocKind::FUNCTION_EXTENDED);
+ if (!getobj) {
+ return nullptr;
+ }
+ id = PropertyKey::Int(i);
+ if (!NativeDefineAccessorProperty(cx, obj, id, getobj, nullptr,
+ JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ getobj->setExtendedSlot(0, Int32Value(i));
+ }
+
+ return obj;
+}
+
+bool DebuggerFrame::CallData::argumentsGetter() {
+ if (!ensureOnStack()) {
+ return false;
+ }
+
+ Rooted<DebuggerArguments*> result(cx);
+ if (!DebuggerFrame::getArguments(cx, frame, &result)) {
+ return false;
+ }
+
+ args.rval().setObjectOrNull(result);
+ return true;
+}
+
+bool DebuggerFrame::CallData::getScript() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ Rooted<DebuggerScript*> scriptObject(cx);
+
+ Debugger* debug = frame->owner();
+ if (frame->isOnStack()) {
+ FrameIter iter = frame->getFrameIter(cx);
+ AbstractFramePtr framePtr = iter.abstractFramePtr();
+
+ if (framePtr.isWasmDebugFrame()) {
+ Rooted<WasmInstanceObject*> instance(cx,
+ framePtr.wasmInstance()->object());
+ scriptObject = debug->wrapWasmScript(cx, instance);
+ } else {
+ RootedScript script(cx, framePtr.script());
+ scriptObject = debug->wrapScript(cx, script);
+ }
+ } else {
+ MOZ_ASSERT(frame->isSuspended());
+ RootedScript script(cx, frame->generatorInfo()->generatorScript());
+ scriptObject = debug->wrapScript(cx, script);
+ }
+ if (!scriptObject) {
+ return false;
+ }
+
+ args.rval().setObject(*scriptObject);
+ return true;
+}
+
+bool DebuggerFrame::CallData::offsetGetter() {
+ if (!ensureOnStackOrSuspended()) {
+ return false;
+ }
+
+ size_t result;
+ if (!DebuggerFrame::getOffset(cx, frame, result)) {
+ return false;
+ }
+
+ args.rval().setNumber(double(result));
+ return true;
+}
+
+bool DebuggerFrame::CallData::liveGetter() {
+ JS_ReportErrorASCII(
+ cx, "Debugger.Frame.prototype.live has been renamed to .onStack");
+ return false;
+}
+
+bool DebuggerFrame::CallData::onStackGetter() {
+ args.rval().setBoolean(frame->isOnStack());
+ return true;
+}
+
+bool DebuggerFrame::CallData::terminatedGetter() {
+ args.rval().setBoolean(!frame->isOnStack() && !frame->isSuspended());
+ return true;
+}
+
+static bool IsValidHook(const Value& v) {
+ return v.isUndefined() || (v.isObject() && v.toObject().isCallable());
+}
+
+bool DebuggerFrame::CallData::onStepGetter() {
+ OnStepHandler* handler = frame->onStepHandler();
+ RootedValue value(
+ cx, handler ? ObjectOrNullValue(handler->object()) : UndefinedValue());
+ MOZ_ASSERT(IsValidHook(value));
+ args.rval().set(value);
+ return true;
+}
+
+bool DebuggerFrame::CallData::onStepSetter() {
+ if (!args.requireAtLeast(cx, "Debugger.Frame.set onStep", 1)) {
+ return false;
+ }
+ if (!IsValidHook(args[0])) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_CALLABLE_OR_UNDEFINED);
+ return false;
+ }
+
+ UniquePtr<ScriptedOnStepHandler> handler;
+ if (!args[0].isUndefined()) {
+ handler = cx->make_unique<ScriptedOnStepHandler>(&args[0].toObject());
+ if (!handler) {
+ return false;
+ }
+ }
+
+ if (!DebuggerFrame::setOnStepHandler(cx, frame, std::move(handler))) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+bool DebuggerFrame::CallData::onPopGetter() {
+ OnPopHandler* handler = frame->onPopHandler();
+ RootedValue value(
+ cx, handler ? ObjectValue(*handler->object()) : UndefinedValue());
+ MOZ_ASSERT(IsValidHook(value));
+ args.rval().set(value);
+ return true;
+}
+
+bool DebuggerFrame::CallData::onPopSetter() {
+ if (!args.requireAtLeast(cx, "Debugger.Frame.set onPop", 1)) {
+ return false;
+ }
+ if (!IsValidHook(args[0])) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_CALLABLE_OR_UNDEFINED);
+ return false;
+ }
+
+ ScriptedOnPopHandler* handler = nullptr;
+ if (!args[0].isUndefined()) {
+ handler = cx->new_<ScriptedOnPopHandler>(&args[0].toObject());
+ if (!handler) {
+ return false;
+ }
+ }
+
+ frame->setOnPopHandler(cx, handler);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+bool DebuggerFrame::CallData::evalMethod() {
+ if (!ensureOnStack()) {
+ return false;
+ }
+
+ if (!args.requireAtLeast(cx, "Debugger.Frame.prototype.eval", 1)) {
+ return false;
+ }
+
+ AutoStableStringChars stableChars(cx);
+ if (!ValueToStableChars(cx, "Debugger.Frame.prototype.eval", args[0],
+ stableChars)) {
+ return false;
+ }
+ mozilla::Range<const char16_t> chars = stableChars.twoByteRange();
+
+ EvalOptions options;
+ if (!ParseEvalOptions(cx, args.get(1), options)) {
+ return false;
+ }
+
+ Rooted<Completion> comp(cx);
+ JS_TRY_VAR_OR_RETURN_FALSE(
+ cx, comp, DebuggerFrame::eval(cx, frame, chars, nullptr, options));
+ return comp.get().buildCompletionValue(cx, frame->owner(), args.rval());
+}
+
+bool DebuggerFrame::CallData::evalWithBindingsMethod() {
+ if (!ensureOnStack()) {
+ return false;
+ }
+
+ if (!args.requireAtLeast(cx, "Debugger.Frame.prototype.evalWithBindings",
+ 2)) {
+ return false;
+ }
+
+ AutoStableStringChars stableChars(cx);
+ if (!ValueToStableChars(cx, "Debugger.Frame.prototype.evalWithBindings",
+ args[0], stableChars)) {
+ return false;
+ }
+ mozilla::Range<const char16_t> chars = stableChars.twoByteRange();
+
+ RootedObject bindings(cx, RequireObject(cx, args[1]));
+ if (!bindings) {
+ return false;
+ }
+
+ EvalOptions options;
+ if (!ParseEvalOptions(cx, args.get(2), options)) {
+ return false;
+ }
+
+ Rooted<Completion> comp(cx);
+ JS_TRY_VAR_OR_RETURN_FALSE(
+ cx, comp, DebuggerFrame::eval(cx, frame, chars, bindings, options));
+ return comp.get().buildCompletionValue(cx, frame->owner(), args.rval());
+}
+
+/* static */
+bool DebuggerFrame::construct(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
+ "Debugger.Frame");
+ return false;
+}
+
+const JSPropertySpec DebuggerFrame::properties_[] = {
+ JS_DEBUG_PSG("arguments", argumentsGetter),
+ JS_DEBUG_PSG("callee", calleeGetter),
+ JS_DEBUG_PSG("constructing", constructingGetter),
+ JS_DEBUG_PSG("environment", environmentGetter),
+ JS_DEBUG_PSG("generator", generatorGetter),
+ JS_DEBUG_PSG("live", liveGetter),
+ JS_DEBUG_PSG("onStack", onStackGetter),
+ JS_DEBUG_PSG("terminated", terminatedGetter),
+ JS_DEBUG_PSG("offset", offsetGetter),
+ JS_DEBUG_PSG("older", olderGetter),
+ JS_DEBUG_PSG("olderSavedFrame", olderSavedFrameGetter),
+ JS_DEBUG_PSG("script", getScript),
+ JS_DEBUG_PSG("this", thisGetter),
+ JS_DEBUG_PSG("asyncPromise", asyncPromiseGetter),
+ JS_DEBUG_PSG("type", typeGetter),
+ JS_DEBUG_PSG("implementation", implementationGetter),
+ JS_DEBUG_PSGS("onStep", onStepGetter, onStepSetter),
+ JS_DEBUG_PSGS("onPop", onPopGetter, onPopSetter),
+ JS_PS_END};
+
+const JSFunctionSpec DebuggerFrame::methods_[] = {
+ JS_DEBUG_FN("eval", evalMethod, 1),
+ JS_DEBUG_FN("evalWithBindings", evalWithBindingsMethod, 1), JS_FS_END};
+
+JSObject* js::IdVectorToArray(JSContext* cx, HandleIdVector ids) {
+ if (MOZ_UNLIKELY(ids.length() > UINT32_MAX)) {
+ ReportAllocationOverflow(cx);
+ return nullptr;
+ }
+
+ Rooted<ArrayObject*> arr(cx, NewDenseFullyAllocatedArray(cx, ids.length()));
+ if (!arr) {
+ return nullptr;
+ }
+
+ arr->ensureDenseInitializedLength(0, ids.length());
+
+ for (size_t i = 0, len = ids.length(); i < len; i++) {
+ jsid id = ids[i];
+ Value v;
+ if (id.isInt()) {
+ JSString* str = Int32ToString<CanGC>(cx, id.toInt());
+ if (!str) {
+ return nullptr;
+ }
+ v = StringValue(str);
+ } else if (id.isAtom()) {
+ v = StringValue(id.toAtom());
+ } else if (id.isSymbol()) {
+ v = SymbolValue(id.toSymbol());
+ } else {
+ MOZ_CRASH("IdVector must contain only string, int, and Symbol jsids");
+ }
+
+ arr->initDenseElement(i, v);
+ }
+
+ return arr;
+}