Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  Frame.cpp   Sprache: C

 
/* -*- 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 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/EnvironmentChain.h"        // JS::EnvironmentChain
#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 GlobalLexicalEnvironmentObject
#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::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;
}

void DebuggerFrame::suspendWasmFrame(JS::GCContext* gcx) {
  freeFrameIterData(gcx);
}

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 WithEnvironmentObject* 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;
    }
  }

  JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No);
  if (!envChain.append(bindingsObj)) {
    return nullptr;
  }

  return CreateObjectsForEnvironmentChain(cx, envChain, enclosingEnv);
}

/*
 * 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;
  }

  // Compile the script and compute the environment object.
  JS::Rooted<JSScript*> script(cx);
  JS::Rooted<JSObject*> env(cx);
  switch (evalOptions.kind()) {
    case EvalOptions::EnvKind::FrameWithExtraBindings: {
      env = CreateBindingsEnv(cx, envArg, bindingKeys, bindingValues);
      if (!env) {
        return false;
      }
      [[fallthrough]];
    }
    case EvalOptions::EnvKind::Frame: {
      // Default to |envArg| when no extra bindings are used.
      if (!env) {
        env = envArg;
      }

      options.setNonSyntacticScope(true);

      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;
      }
      break;
    }
    case EvalOptions::EnvKind::Global: {
      AutoReportFrontendContext fc(cx);
      script = frontend::CompileGlobalScript(cx, &fc, options, srcBuf,
                                             ScopeKind::Global);
      if (!script) {
        return false;
      }

      env = envArg;
      break;
    }
    case EvalOptions::EnvKind::GlobalWithExtraOuterBindings: {
      // 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.

      MOZ_ASSERT(envArg == &cx->global()->lexicalEnvironment());

      options.setNonSyntacticScope(true);

      AutoReportFrontendContext fc(cx);
      script = frontend::CompileGlobalScriptWithExtraBindings(
          cx, &fc, options, srcBuf, bindingKeys, bindingValues, &env);
      if (!script) {
        return false;
      }
      break;
    }
    case EvalOptions::EnvKind::GlobalWithExtraInnerBindings: {
      env = CreateBindingsEnv(cx, envArg, bindingKeys, bindingValues);
      if (!env) {
        return false;
      }

      options.setNonSyntacticScope(true);

      AutoReportFrontendContext fc(cx);
      script = frontend::CompileGlobalScript(cx, &fc, options, srcBuf,
                                             ScopeKind::NonSyntactic);
      if (!script) {
        return false;
      }
      break;
    }
  }

  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(envArg->is<GlobalLexicalEnvironmentObject>());
      MOZ_ASSERT(!bindings);
      break;
    case EvalOptions::EnvKind::GlobalWithExtraInnerBindings:
    case EvalOptions::EnvKind::GlobalWithExtraOuterBindings:
      MOZ_ASSERT(!iter);
      MOZ_ASSERT(envArg);
      MOZ_ASSERT(envArg->is<GlobalLexicalEnvironmentObject>());
      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.
  return !getFixedSlot(FRAME_ITER_SLOT).isUndefined();
}

bool DebuggerFrame::isOnStackOrSuspendedWasmStack() const {
  // Note: this is equivalent to checking `frameIterData() != nullptr &&
  // !hasGeneratorInfo()`, but works also when called from the trace hook
  // during a moving GC.
  return !getFixedSlot(FRAME_ITER_SLOT).isUndefined() ||
         getFixedSlot(GENERATOR_INFO_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),
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=92 G=91

¤ Dauer der Verarbeitung: 0.22 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge