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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "debugger/Frame-inl.h"
#include "mozilla/Assertions.h" // for MOZ_ASSERT, MOZ_ASSERT_IF, MOZ_CRASH
#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/BytecodeCompiler.h" // for CompileGlobalScript, CompileGlobalScriptWithExtraBindings, 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/GCVector.h" // for JS::StackGCVector
#include "js/Object.h" // for SetReservedSlot
#include "js/Proxy.h" // for PrivateValue
#include "js/RootingAPI.h" // for JS::Rooted, JS::Handle, JS::MutableHandle
#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/JSAtomUtils.h" // for Atomize, AtomizeUTF8Chars
#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::onGeneratorClosed(JS::GCContext* gcx) {
GeneratorInfo* info = generatorInfo();
// If the generator is closed, eagerly drop the onStep handler, to make sure
// the stepper counter matches in the assertion in DebugAPI::onSingleStep.
// Also clear the slot in order to suppress the decrementStepperCounter in
// DebuggerFrame::terminate.
if (!info->isGeneratorScriptAboutToBeFinalized()) {
JSScript* generatorScript = info->generatorScript();
OnStepHandler* handler = onStepHandler();
if (handler) {
decrementStepperCounter(gcx, generatorScript);
setReservedSlot(ONSTEP_HANDLER_SLOT, UndefinedValue());
handler->drop(gcx, this);
}
}
}
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;
}
static JSObject* CreateBindingsEnv(
JSContext* cx, JS::Handle<JSObject*> enclosingEnv,
JS::Handle<JS::StackGCVector<JS::PropertyKey>> bindingKeys,
JS::Handle<JS::StackGCVector<JS::Value>> bindingValues) {
JS::Rooted<PlainObject*> bindingsObj(cx,
NewPlainObjectWithProto(cx, nullptr));
if (!bindingsObj) {
return nullptr;
}
JS::Rooted<JS::PropertyKey> id(cx);
JS::Rooted<JS::Value> val(cx);
for (size_t i = 0; i < bindingKeys.length(); i++) {
id = bindingKeys[i];
cx->markId(id);
val = bindingValues[i];
if (!cx->compartment()->wrap(cx, &val) ||
!NativeDefineDataProperty(cx, bindingsObj, id, val, 0)) {
return nullptr;
}
}
RootedObjectVector envChain(cx);
if (!envChain.append(bindingsObj)) {
return nullptr;
}
JS::Rooted<JSObject*> newEnv(cx);
if (!CreateObjectsForEnvironmentChain(cx, envChain, enclosingEnv, &newEnv)) {
return nullptr;
}
return newEnv;
}
/*
* Evaluate |chars| in the environment |envArg|, treating that source as
* appearing starting at |evalOptions.lineno()| in |evalOptions.filename()|.
* Store the return value in |*rval|.
*
* If |evalOptions.kind()| is either `Frame` or |FrameWithExtraBindings|,
* evaluate as for a direct eval in |frame|; |envArg| must be |frame|'s
* DebugScopeObject; either way,
* |frame|'s scope is where newly declared variables go. In this case,
* |frame| must have a computed 'this' value.
*
* If |evalOptions.kind()| is one of the following, |bindingKeys| and
* |bindingValues| should represent the bindings, and a new
* |WithEnvironmentObject| is created with |envArg| as enclosing environment,
* and used for the evaluation.
* - FrameWithExtraBindings
* - GlobalWithExtraInnerBindings
* - GlobalWithExtraOuterBindings
*/
static bool EvaluateInEnv(
JSContext* cx, Handle<Env*> envArg, AbstractFramePtr frame,
mozilla::Range<const char16_t> chars, const EvalOptions& evalOptions,
JS::Handle<JS::StackGCVector<JS::PropertyKey>> bindingKeys,
JS::Handle<JS::StackGCVector<JS::Value>> bindingValues,
MutableHandleValue rval) {
#ifdef DEBUG
switch (evalOptions.kind()) {
case EvalOptions::EnvKind::Frame:
case EvalOptions::EnvKind::FrameWithExtraBindings:
MOZ_ASSERT(frame);
break;
case EvalOptions::EnvKind::Global:
MOZ_ASSERT(!frame);
break;
case EvalOptions::EnvKind::GlobalWithExtraInnerBindings:
case EvalOptions::EnvKind::GlobalWithExtraOuterBindings:
MOZ_ASSERT(!frame);
break;
}
#endif
cx->check(envArg, 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;
}
JS::Rooted<JSObject*> env(cx, envArg);
if (evalOptions.kind() == EvalOptions::EnvKind::FrameWithExtraBindings ||
evalOptions.kind() ==
EvalOptions::EnvKind::GlobalWithExtraInnerBindings) {
// NOTE: GlobalWithExtraOuterBindings case is handled in separately below.
env = CreateBindingsEnv(cx, env, bindingKeys, bindingValues);
if (!env) {
return false;
}
}
if (evalOptions.kind() == EvalOptions::EnvKind::Frame ||
evalOptions.kind() == EvalOptions::EnvKind::FrameWithExtraBindings) {
options.setNonSyntacticScope(true);
Rooted<Scope*> scope(cx,
GlobalScope::createEmpty(cx, ScopeKind::NonSyntactic));
if (!scope) {
return false;
}
RootedScript script(
cx, frontend::CompileEvalScript(cx, options, srcBuf, scope, env));
if (!script) {
return false;
}
return ExecuteKernel(cx, script, env, frame, rval);
}
// 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{,WithBindings}, like the web console, may add new
// bindings to the global scope.
if (evalOptions.kind() ==
EvalOptions::EnvKind::GlobalWithExtraOuterBindings) {
options.setNonSyntacticScope(true);
MOZ_ASSERT(env == &cx->global()->lexicalEnvironment());
JS::Rooted<JSObject*> bindingsEnv(cx);
AutoReportFrontendContext fc(cx);
RootedScript script(cx, frontend::CompileGlobalScriptWithExtraBindings(
cx, &fc, options, srcBuf, bindingKeys,
bindingValues, &bindingsEnv));
if (!script) {
return false;
}
return ExecuteKernel(cx, script, bindingsEnv, frame, rval);
}
if (evalOptions.kind() ==
EvalOptions::EnvKind::GlobalWithExtraInnerBindings) {
options.setNonSyntacticScope(true);
AutoReportFrontendContext fc(cx);
RootedScript script(cx,
frontend::CompileGlobalScript(cx, &fc, options, srcBuf,
ScopeKind::NonSyntactic));
if (!script) {
return false;
}
return ExecuteKernel(cx, script, env, frame, rval);
}
MOZ_ASSERT(evalOptions.kind() == EvalOptions::EnvKind::Global);
AutoReportFrontendContext fc(cx);
RootedScript script(cx, frontend::CompileGlobalScript(
cx, &fc, options, srcBuf, ScopeKind::Global));
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) {
#ifdef DEBUG
switch (options.kind()) {
case EvalOptions::EnvKind::Frame:
MOZ_ASSERT(iter);
MOZ_ASSERT(!envArg);
MOZ_ASSERT(!bindings);
break;
case EvalOptions::EnvKind::FrameWithExtraBindings:
MOZ_ASSERT(iter);
MOZ_ASSERT(!envArg);
MOZ_ASSERT(bindings);
break;
case EvalOptions::EnvKind::Global:
MOZ_ASSERT(!iter);
MOZ_ASSERT(envArg);
MOZ_ASSERT(IsGlobalLexicalEnvironment(envArg));
MOZ_ASSERT(!bindings);
break;
case EvalOptions::EnvKind::GlobalWithExtraInnerBindings:
case EvalOptions::EnvKind::GlobalWithExtraOuterBindings:
MOZ_ASSERT(!iter);
MOZ_ASSERT(envArg);
MOZ_ASSERT(IsGlobalLexicalEnvironment(envArg));
MOZ_ASSERT(bindings);
break;
}
#endif
// 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 (iter && iter->hasScript() && iter->script()->allowRelazify()) {
// This is a function that was first lazy, and delazified, and it's marked
// as relazifyable because there was no inner function.
//
// Evaluating a script can create inner function, which should prevent the
// relazification.
iter->script()->clearAllowRelazify();
}
// Note whether we are in an evaluation that might invoke the OnNativeCall
// hook, so that the JITs will be disabled.
Maybe<AutoNoteExclusiveDebuggerOnEval> noteEvaluation;
if (dbg->isExclusiveDebuggerOnEval()) {
noteEvaluation.emplace(cx, dbg);
}
// 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, keys, values, &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(options.kind() == EvalOptions::EnvKind::Frame ||
options.kind() == EvalOptions::EnvKind::FrameWithExtraBindings);
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 or
// initialized in the prologue.
if (fi.closedOver() && frame.hasInitialEnvironment() &&
iter.pc() >= script->main()) {
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(EvalOptions::EnvKind::Frame);
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(EvalOptions::EnvKind::FrameWithExtraBindings);
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;
}