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


Quelle  Debugger.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/Debugger-inl.h"

#include "mozilla/Attributes.h"        // for MOZ_STACK_CLASS, MOZ_RAII
#include "mozilla/DebugOnly.h"         // for DebugOnly
#include "mozilla/DoublyLinkedList.h"  // for DoublyLinkedList<>::Iterator
#include "mozilla/HashTable.h"         // for HashSet<>::Range, HashMapEntry
#include "mozilla/Maybe.h"             // for Maybe, Nothing, Some
#include "mozilla/ScopeExit.h"         // for MakeScopeExit, ScopeExit
#include "mozilla/Sprintf.h"           // for SprintfLiteral
#include "mozilla/ThreadLocal.h"       // for ThreadLocal
#include "mozilla/TimeStamp.h"         // for TimeStamp
#include "mozilla/UniquePtr.h"         // for UniquePtr
#include "mozilla/Variant.h"           // for AsVariant, AsVariantTemporary
#include "mozilla/Vector.h"            // for Vector, Vector<>::ConstRange

#include <algorithm>    // for std::find, std::max
#include <functional>   // for function
#include <stddef.h>     // for size_t
#include <stdint.h>     // for uint32_t, uint64_t, int32_t
#include <string.h>     // for strlen, strcmp
#include <type_traits>  // for std::underlying_type_t
#include <utility>      // for std::move

#include "jsapi.h"    // for CallArgs, CallArgsFromVp
#include "jstypes.h"  // for JS_PUBLIC_API

#include "builtin/Array.h"            // for NewDenseFullyAllocatedArray
#include "debugger/DebugAPI.h"        // for ResumeMode, DebugAPI
#include "debugger/DebuggerMemory.h"  // for DebuggerMemory
#include "debugger/DebugScript.h"     // for DebugScript
#include "debugger/Environment.h"     // for DebuggerEnvironment
#ifdef MOZ_EXECUTION_TRACING
#  include "debugger/ExecutionTracer.h"  // for ExecutionTracer::onEnterFrame, ExecutionTracer::onLeaveFrame
#endif
#include "debugger/Frame.h"               // for DebuggerFrame
#include "debugger/NoExecute.h"           // for EnterDebuggeeNoExecute
#include "debugger/Object.h"              // for DebuggerObject
#include "debugger/Script.h"              // for DebuggerScript
#include "debugger/Source.h"              // for DebuggerSource
#include "frontend/CompilationStencil.h"  // for CompilationStencil
#include "frontend/FrontendContext.h"     // for AutoReportFrontendContext
#include "frontend/Parser.h"              // for Parser
#include "gc/GC.h"                        // for IterateScripts
#include "gc/GCContext.h"                 // for JS::GCContext
#include "gc/GCMarker.h"                  // for GCMarker
#include "gc/GCRuntime.h"                 // for GCRuntime, AutoEnterIteration
#include "gc/HashUtil.h"                  // for DependentAddPtr
#include "gc/Marking.h"                   // for IsAboutToBeFinalized
#include "gc/PublicIterators.h"           // for RealmsIter, CompartmentsIter
#include "gc/Statistics.h"                // for Statistics::SliceData
#include "gc/Tracer.h"                    // for TraceEdge
#include "gc/Zone.h"                      // for Zone
#include "gc/ZoneAllocator.h"             // for ZoneAllocPolicy
#include "jit/BaselineDebugModeOSR.h"  // for RecompileOnStackBaselineScriptsForDebugMode
#include "jit/BaselineJIT.h"           // for FinishDiscardBaselineScript
#include "jit/Invalidation.h"         // for RecompileInfoVector
#include "jit/JitContext.h"           // for JitContext
#include "jit/JitOptions.h"           // for fuzzingSafe
#include "jit/JitScript.h"            // for JitScript
#include "jit/JSJitFrameIter.h"       // for InlineFrameIterator
#include "jit/RematerializedFrame.h"  // for RematerializedFrame
#include "js/CallAndConstruct.h"      // JS::IsCallable
#include "js/Conversions.h"           // for ToBoolean, ToUint32
#include "js/Debug.h"                 // for Builder::Object, Builder
#include "js/friend/ErrorMessages.h"  // for GetErrorMessage, JSMSG_*
#include "js/GCAPI.h"                 // for GarbageCollectionEvent
#include "js/GCVariant.h"             // for GCVariant
#include "js/HeapAPI.h"               // for ExposeObjectToActiveJS
#include "js/Promise.h"               // for AutoDebuggerJobQueueInterruption
#include "js/PropertyAndElement.h"    // for JS_GetProperty
#include "js/Proxy.h"                 // for PropertyDescriptor
#include "js/SourceText.h"            // for SourceText
#include "js/StableStringChars.h"     // for AutoStableStringChars
#include "js/UbiNode.h"               // for Node, RootList, Edge
#include "js/UbiNodeBreadthFirst.h"   // for BreadthFirst
#include "js/Wrapper.h"               // for CheckedUnwrapStatic
#include "util/Identifier.h"          // for IsIdentifier
#include "util/Text.h"                // for DuplicateString, js_strlen
#include "vm/ArrayObject.h"           // for ArrayObject
#include "vm/AsyncFunction.h"         // for AsyncFunctionGeneratorObject
#include "vm/AsyncIteration.h"        // for AsyncGeneratorObject
#include "vm/BytecodeUtil.h"          // for JSDVG_IGNORE_STACK
#include "vm/Compartment.h"           // for CrossCompartmentKey
#include "vm/EnvironmentObject.h"     // for IsSyntacticEnvironment
#include "vm/ErrorReporting.h"        // for ReportErrorToGlobal
#include "vm/GeneratorObject.h"       // for AbstractGeneratorObject
#include "vm/GlobalObject.h"          // for GlobalObject
#include "vm/Interpreter.h"           // for Call, ReportIsNotFunction
#include "vm/Iteration.h"             // for CreateIterResultObject
#include "vm/JSAtomUtils.h"  // for Atomize, AtomizeUTF8Chars, AtomIsMarked, AtomToId, ClassName
#include "vm/JSContext.h"         // for JSContext
#include "vm/JSFunction.h"        // for JSFunction
#include "vm/JSObject.h"          // for JSObject, RequireObject,
#include "vm/JSScript.h"          // for BaseScript, ScriptSourceObject
#include "vm/ObjectOperations.h"  // for DefineDataProperty
#include "vm/PlainObject.h"       // for js::PlainObject
#include "vm/PromiseObject.h"     // for js::PromiseObject
#include "vm/ProxyObject.h"       // for ProxyObject, JSObject::is
#include "vm/Realm.h"             // for AutoRealm, Realm
#include "vm/Runtime.h"           // for ReportOutOfMemory, JSRuntime
#include "vm/SavedFrame.h"        // for SavedFrame
#include "vm/SavedStacks.h"       // for SavedStacks
#include "vm/Scope.h"             // for Scope
#include "vm/StringType.h"        // for JSString, PropertyName
#include "vm/WrapperObject.h"     // for CrossCompartmentWrapperObject
#include "wasm/WasmDebug.h"       // for DebugState
#include "wasm/WasmInstance.h"    // for Instance
#include "wasm/WasmJS.h"          // for WasmInstanceObject
#include "wasm/WasmRealm.h"       // for Realm
#include "wasm/WasmTypeDecls.h"   // for WasmInstanceObjectVector

#include "debugger/DebugAPI-inl.h"
#include "debugger/Environment-inl.h"  // for DebuggerEnvironment::owner
#include "debugger/Frame-inl.h"        // for DebuggerFrame::hasGeneratorInfo
#include "debugger/Object-inl.h"  // for DebuggerObject::owner and isInstance.
#include "debugger/Script-inl.h"  // for DebuggerScript::getReferent
#include "gc/GC-inl.h"            // for ZoneCellIter
#include "gc/Marking-inl.h"       // for MaybeForwarded
#include "gc/StableCellHasher-inl.h"
#include "gc/WeakMap-inl.h"        // for DebuggerWeakMap::trace
#include "vm/Compartment-inl.h"    // for Compartment::wrap
#include "vm/GeckoProfiler-inl.h"  // for AutoSuppressProfilerSampling
#include "vm/JSAtomUtils-inl.h"    // for AtomToId, ValueToId
#include "vm/JSContext-inl.h"      // for JSContext::check
#include "vm/JSObject-inl.h"  // for JSObject::isCallable, NewTenuredObjectWithGivenProto
#include "vm/JSScript-inl.h"      // for JSScript::isDebuggee, JSScript
#include "vm/NativeObject-inl.h"  // for NativeObject::ensureDenseInitializedLength
#include "vm/ObjectOperations-inl.h"  // for GetProperty, HasProperty
#include "vm/Realm-inl.h"             // for AutoRealm::AutoRealm
#include "vm/Stack-inl.h"             // for AbstractFramePtr::script

namespace js {

namespace frontend {
class FullParseHandler;
}

namespace gc {
struct Cell;
}

namespace jit {
class BaselineFrame;
}

/* namespace js */

using namespace js;

using JS::AutoStableStringChars;
using JS::CompileOptions;
using JS::dbg::Builder;
using mozilla::AsVariant;
using mozilla::DebugOnly;
using mozilla::MakeScopeExit;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::Some;
using mozilla::TimeStamp;

/*** Utils ******************************************************************/

bool js::IsInterpretedNonSelfHostedFunction(JSFunction* fun) {
  return fun->isInterpreted() && !fun->isSelfHostedBuiltin();
}

JSScript* js::GetOrCreateFunctionScript(JSContext* cx, HandleFunction fun) {
  MOZ_ASSERT(IsInterpretedNonSelfHostedFunction(fun));
  AutoRealm ar(cx, fun);
  return JSFunction::getOrCreateScript(cx, fun);
}

ArrayObject* js::GetFunctionParameterNamesArray(JSContext* cx,
                                                HandleFunction fun) {
  RootedValueVector names(cx);

  // The default value for each argument is |undefined|.
  if (!names.growBy(fun->nargs())) {
    return nullptr;
  }

  if (IsInterpretedNonSelfHostedFunction(fun) && fun->nargs() > 0) {
    RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
    if (!script) {
      return nullptr;
    }

    MOZ_ASSERT(fun->nargs() == script->numArgs());

    PositionalFormalParameterIter fi(script);
    for (size_t i = 0; i < fun->nargs(); i++, fi++) {
      MOZ_ASSERT(fi.argumentSlot() == i);
      if (JSAtom* atom = fi.name()) {
        // Skip any internal, non-identifier names, like for example ".args".
        if (IsIdentifier(atom)) {
          cx->markAtom(atom);
          names[i].setString(atom);
        }
      }
    }
  }

  return NewDenseCopiedArray(cx, names.length(), names.begin());
}

bool js::ValueToIdentifier(JSContext* cx, HandleValue v, MutableHandleId id) {
  if (!ToPropertyKey(cx, v, id)) {
    return false;
  }
  if (!id.isAtom() || !IsIdentifier(id.toAtom())) {
    RootedValue val(cx, v);
    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, val,
                     nullptr, "not an identifier");
    return false;
  }
  return true;
}

class js::AutoRestoreRealmDebugMode {
  Realm* realm_;
  unsigned bits_;

 public:
  explicit AutoRestoreRealmDebugMode(Realm* realm)
      : realm_(realm), bits_(realm->debugModeBits_) {
    MOZ_ASSERT(realm_);
  }

  ~AutoRestoreRealmDebugMode() {
    if (realm_) {
      realm_->debugModeBits_ = bits_;
    }
  }

  void release() { realm_ = nullptr; }
};

/* static */
bool DebugAPI::slowPathCheckNoExecute(JSContext* cx, HandleScript script) {
  MOZ_ASSERT(cx->realm()->isDebuggee());
  MOZ_ASSERT(cx->noExecuteDebuggerTop);
  return EnterDebuggeeNoExecute::reportIfFoundInStack(cx, script);
}

static void PropagateForcedReturn(JSContext* cx, AbstractFramePtr frame,
                                  HandleValue rval) {
  // The Debugger's hooks may return a value that affects the completion
  // value of the given frame. For example, a hook may return `{ return: 42 }`
  // to terminate the frame and return `42` as the final frame result.
  // To accomplish this, the debugger treats these return values as if
  // execution of the JS function has been terminated without a pending
  // exception, but with a special flag. When the error is handled by the
  // interpreter or JIT, the special flag and the error state will be cleared
  // and execution will continue from the end of the frame.
  MOZ_ASSERT(!cx->isExceptionPending());
  cx->setPropagatingForcedReturn();
  frame.setReturnValue(rval);
}

[[nodiscard]] static bool AdjustGeneratorResumptionValue(JSContext* cx,
                                                         AbstractFramePtr frame,
                                                         ResumeMode& resumeMode,
                                                         MutableHandleValue vp);

[[nodiscard]] static bool ApplyFrameResumeMode(JSContext* cx,
                                               AbstractFramePtr frame,
                                               ResumeMode resumeMode,
                                               HandleValue rv,
                                               Handle<SavedFrame*> exnStack) {
  RootedValue rval(cx, rv);

  // The value passed in here is unwrapped and has no guarantees about what
  // compartment it may be associated with, so we explicitly wrap it into the
  // debuggee compartment.
  if (!cx->compartment()->wrap(cx, &rval)) {
    return false;
  }

  if (!AdjustGeneratorResumptionValue(cx, frame, resumeMode, &rval)) {
    return false;
  }

  switch (resumeMode) {
    case ResumeMode::Continue:
      break;

    case ResumeMode::Throw:
      // If we have a stack from the original throw, use it instead of
      // associating the throw with the current execution point.
      if (exnStack) {
        cx->setPendingException(rval, exnStack);
      } else {
        cx->setPendingException(rval, ShouldCaptureStack::Always);
      }
      return false;

    case ResumeMode::Terminate:
      cx->reportUncatchableException();
      return false;

    case ResumeMode::Return:
      PropagateForcedReturn(cx, frame, rval);
      return false;

    default:
      MOZ_CRASH("bad Debugger::onEnterFrame resume mode");
  }

  return true;
}
static bool ApplyFrameResumeMode(JSContext* cx, AbstractFramePtr frame,
                                 ResumeMode resumeMode, HandleValue rval) {
  Rooted<SavedFrame*> nullStack(cx);
  return ApplyFrameResumeMode(cx, frame, resumeMode, rval, nullStack);
}

bool js::ValueToStableChars(JSContext* cx, const char* fnname,
                            HandleValue value,
                            AutoStableStringChars& stableChars) {
  if (!value.isString()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_NOT_EXPECTED_TYPE, fnname, "string",
                              InformalValueTypeName(value));
    return false;
  }
  Rooted<JSLinearString*> linear(cx, value.toString()->ensureLinear(cx));
  if (!linear) {
    return false;
  }
  if (!stableChars.initTwoByte(cx, linear)) {
    return false;
  }
  return true;
}

bool EvalOptions::setFilename(JSContext* cx, const char* filename) {
  JS::UniqueChars copy;
  if (filename) {
    copy = DuplicateString(cx, filename);
    if (!copy) {
      return false;
    }
  }

  filename_ = std::move(copy);
  return true;
}

bool js::ParseEvalOptions(JSContext* cx, HandleValue value,
                          EvalOptions& options) {
  if (!value.isObject()) {
    return true;
  }

  RootedObject opts(cx, &value.toObject());

  RootedValue v(cx);
  if (!JS_GetProperty(cx, opts, "url", &v)) {
    return false;
  }
  if (!v.isUndefined()) {
    RootedString url_str(cx, ToString<CanGC>(cx, v));
    if (!url_str) {
      return false;
    }
    UniqueChars url_bytes = JS_EncodeStringToUTF8(cx, url_str);
    if (!url_bytes) {
      return false;
    }
    if (!options.setFilename(cx, url_bytes.get())) {
      return false;
    }
  }

  if (!JS_GetProperty(cx, opts, "lineNumber", &v)) {
    return false;
  }
  if (!v.isUndefined()) {
    uint32_t lineno;
    if (!ToUint32(cx, v, &lineno)) {
      return false;
    }
    options.setLineno(lineno);
  }

  if (!JS_GetProperty(cx, opts, "hideFromDebugger", &v)) {
    return false;
  }
  options.setHideFromDebugger(ToBoolean(v));

  if (options.kind() == EvalOptions::EnvKind::GlobalWithExtraOuterBindings) {
    if (!JS_GetProperty(cx, opts, "useInnerBindings", &v)) {
      return false;
    }
    if (ToBoolean(v)) {
      options.setUseInnerBindings();
    }
  }

  return true;
}

/*** Breakpoints ************************************************************/

bool BreakpointSite::isEmpty() const { return breakpoints.isEmpty(); }

void BreakpointSite::trace(JSTracer* trc) {
  for (auto p = breakpoints.begin(); p; p++) {
    p->trace(trc);
  }
}

void BreakpointSite::finalize(JS::GCContext* gcx) {
  while (!breakpoints.isEmpty()) {
    breakpoints.begin()->delete_(gcx);
  }
}

Breakpoint* BreakpointSite::firstBreakpoint() const {
  if (isEmpty()) {
    return nullptr;
  }
  return &(*breakpoints.begin());
}

bool BreakpointSite::hasBreakpoint(Breakpoint* toFind) {
  const BreakpointList::Iterator bp(toFind);
  for (auto p = breakpoints.begin(); p; p++) {
    if (p == bp) {
      return true;
    }
  }
  return false;
}

Breakpoint::Breakpoint(Debugger* debugger, HandleObject wrappedDebugger,
                       BreakpointSite* site, HandleObject handler)
    : debugger(debugger),
      wrappedDebugger(wrappedDebugger),
      site(site),
      handler(handler) {
  MOZ_ASSERT(UncheckedUnwrap(wrappedDebugger) == debugger->object);
  MOZ_ASSERT(handler->compartment() == wrappedDebugger->compartment());

  debugger->breakpoints.pushBack(this);
  site->breakpoints.pushBack(this);
}

void Breakpoint::trace(JSTracer* trc) {
  TraceEdge(trc, &wrappedDebugger, "breakpoint owner");
  TraceEdge(trc, &handler, "breakpoint handler");
}

void Breakpoint::delete_(JS::GCContext* gcx) {
  debugger->breakpoints.remove(this);
  site->breakpoints.remove(this);
  gc::Cell* cell = site->owningCell();
  gcx->delete_(cell, this, MemoryUse::Breakpoint);
}

void Breakpoint::remove(JS::GCContext* gcx) {
  BreakpointSite* savedSite = site;
  delete_(gcx);

  savedSite->destroyIfEmpty(gcx);
}

Breakpoint* Breakpoint::nextInDebugger() { return debuggerLink.mNext; }

Breakpoint* Breakpoint::nextInSite() { return siteLink.mNext; }

JSBreakpointSite::JSBreakpointSite(JSScript* script, jsbytecode* pc)
    : script(script), pc(pc) {
  MOZ_ASSERT(!DebugAPI::hasBreakpointsAt(script, pc));
}

void JSBreakpointSite::remove(JS::GCContext* gcx) {
  DebugScript::destroyBreakpointSite(gcx, script, pc);
}

void JSBreakpointSite::trace(JSTracer* trc) {
  BreakpointSite::trace(trc);
  TraceEdge(trc, &script, "breakpoint script");
}

void JSBreakpointSite::delete_(JS::GCContext* gcx) {
  BreakpointSite::finalize(gcx);

  gcx->delete_(script, this, MemoryUse::BreakpointSite);
}

gc::Cell* JSBreakpointSite::owningCell() { return script; }

Realm* JSBreakpointSite::realm() const { return script->realm(); }

WasmBreakpointSite::WasmBreakpointSite(WasmInstanceObject* instanceObject_,
                                       uint32_t offset_)
    : instanceObject(instanceObject_), offset(offset_) {
  MOZ_ASSERT(instanceObject_);
  MOZ_ASSERT(instanceObject_->instance().debugEnabled());
}

void WasmBreakpointSite::trace(JSTracer* trc) {
  BreakpointSite::trace(trc);
  TraceEdge(trc, &instanceObject, "breakpoint Wasm instance");
}

void WasmBreakpointSite::remove(JS::GCContext* gcx) {
  instanceObject->instance().destroyBreakpointSite(gcx, offset);
}

void WasmBreakpointSite::delete_(JS::GCContext* gcx) {
  BreakpointSite::finalize(gcx);

  gcx->delete_(instanceObject, this, MemoryUse::BreakpointSite);
}

gc::Cell* WasmBreakpointSite::owningCell() { return instanceObject; }

Realm* WasmBreakpointSite::realm() const { return instanceObject->realm(); }

/*** Debugger hook dispatch *************************************************/

Debugger::Debugger(JSContext* cx, NativeObject* dbg)
    : object(dbg),
      debuggees(cx->zone()),
      uncaughtExceptionHook(nullptr),
      allowUnobservedAsmJS(false),
      allowUnobservedWasm(false),
      exclusiveDebuggerOnEval(false),
      inspectNativeCallArguments(false),
      collectCoverageInfo(false),
      shouldAvoidSideEffects(false),
      observedGCs(cx->zone()),
      allocationsLog(cx),
      trackingAllocationSites(false),
      allocationSamplingProbability(1.0),
      maxAllocationsLogLength(DEFAULT_MAX_LOG_LENGTH),
      allocationsLogOverflowed(false),
      frames(cx->zone()),
      generatorFrames(cx),
      scripts(cx),
      sources(cx),
      objects(cx),
      environments(cx),
      wasmInstanceScripts(cx),
      wasmInstanceSources(cx) {
  cx->check(dbg);

  cx->runtime()->debuggerList().insertBack(this);
}

template <typename ElementAccess>
static void RemoveDebuggerEntry(
    mozilla::DoublyLinkedList<Debugger, ElementAccess>& list, Debugger* dbg) {
  // The "probably" here is because there could technically be multiple lists
  // with this type signature and theoretically the debugger could be an entry
  // in a different one. That is not actually possible however because there
  // is only one list the debugger could be in.
  if (list.ElementProbablyInList(dbg)) {
    list.remove(dbg);
  }
}

Debugger::~Debugger() {
  MOZ_ASSERT(debuggees.empty());
  allocationsLog.clear();

  // Breakpoints should hold us alive, so any breakpoints remaining must be set
  // in dying JSScripts. We should clean them up, but this never asserts. I'm
  // not sure why.
  MOZ_ASSERT(breakpoints.isEmpty());

  // We don't have to worry about locking here since Debugger is not
  // background finalized.
  JSContext* cx = TlsContext.get();
  RemoveDebuggerEntry(cx->runtime()->onNewGlobalObjectWatchers(), this);
  RemoveDebuggerEntry(cx->runtime()->onGarbageCollectionWatchers(), this);
}

#ifdef DEBUG
/* static */
bool Debugger::isChildJSObject(JSObject* obj) {
  return obj->getClass() == &DebuggerFrame::class_ ||
         obj->getClass() == &DebuggerScript::class_ ||
         obj->getClass() == &DebuggerSource::class_ ||
         obj->getClass() == &DebuggerObject::class_ ||
         obj->getClass() == &DebuggerEnvironment::class_;
}
#endif

bool Debugger::hasMemory() const {
  return object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE).isObject();
}

DebuggerMemory& Debugger::memory() const {
  MOZ_ASSERT(hasMemory());
  return object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE)
      .toObject()
      .as<DebuggerMemory>();
}

/*** Debugger accessors *******************************************************/

bool Debugger::getFrame(JSContext* cx, const FrameIter& iter,
                        MutableHandleValue vp) {
  Rooted<DebuggerFrame*> result(cx);
  if (!Debugger::getFrame(cx, iter, &result)) {
    return false;
  }
  vp.setObject(*result);
  return true;
}

bool Debugger::getFrame(JSContext* cx, MutableHandle<DebuggerFrame*> result) {
  RootedObject proto(
      cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
  Rooted<NativeObject*> debugger(cx, object);

  // Since there is no frame/generator data to associate with this frame, this
  // will create a new, "terminated" Debugger.Frame object.
  Rooted<DebuggerFrame*> frame(
      cx, DebuggerFrame::create(cx, proto, debugger, nullptr, nullptr));
  if (!frame) {
    return false;
  }

  result.set(frame);
  return true;
}

bool Debugger::getFrame(JSContext* cx, const FrameIter& iter,
                        MutableHandle<DebuggerFrame*> result) {
  AbstractFramePtr referent = iter.abstractFramePtr();
  MOZ_ASSERT_IF(referent.hasScript(), !referent.script()->selfHosted());

  FrameMap::AddPtr p = frames.lookupForAdd(referent);
  if (!p) {
    Rooted<AbstractGeneratorObject*> genObj(cx);
    if (referent.isGeneratorFrame()) {
      if (referent.isFunctionFrame()) {
        AutoRealm ar(cx, referent.callee());
        genObj = GetGeneratorObjectForFrame(cx, referent);
      } else {
        MOZ_ASSERT(referent.isModuleFrame());
        AutoRealm ar(cx, referent.script()->module());
        genObj = GetGeneratorObjectForFrame(cx, referent);
      }

      // If this frame has a generator associated with it, but no on-stack
      // Debugger.Frame object was found, there should not be a suspended
      // Debugger.Frame either because otherwise slowPathOnResumeFrame would
      // have already populated the "frames" map with a Debugger.Frame.
      MOZ_ASSERT_IF(genObj, !generatorFrames.has(genObj));

      // If the frame's generator is closed, there is no way to associate the
      // generator with the frame successfully because there is no way to
      // get the generator's callee script, and even if we could, having it
      // there would in no way affect the behavior of the frame.
      if (genObj && genObj->isClosed()) {
        genObj = nullptr;
      }

      // If no AbstractGeneratorObject exists yet, we create a Debugger.Frame
      // below anyway, and Debugger::onNewGenerator() will associate it
      // with the AbstractGeneratorObject later when we hit JSOp::Generator.
    }

    // Create and populate the Debugger.Frame object.
    RootedObject proto(
        cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
    Rooted<NativeObject*> debugger(cx, object);

    Rooted<DebuggerFrame*> frame(
        cx, DebuggerFrame::create(cx, proto, debugger, &iter, genObj));
    if (!frame) {
      return false;
    }

    auto terminateDebuggerFrameGuard = MakeScopeExit([&] {
      terminateDebuggerFrame(cx->gcContext(), this, frame, referent);
    });

    if (genObj) {
      DependentAddPtr<GeneratorWeakMap> genPtr(cx, generatorFrames, genObj);
      if (!genPtr.add(cx, generatorFrames, genObj, frame)) {
        return false;
      }
    }

    if (!ensureExecutionObservabilityOfFrame(cx, referent)) {
      return false;
    }

    if (!frames.add(p, referent, frame)) {
      ReportOutOfMemory(cx);
      return false;
    }

    terminateDebuggerFrameGuard.release();
  }

  result.set(p->value());
  return true;
}

bool Debugger::getFrame(JSContext* cx, Handle<AbstractGeneratorObject*> genObj,
                        MutableHandle<DebuggerFrame*> result) {
  // To create a Debugger.Frame for a running generator, we'd also need a
  // FrameIter for its stack frame. We could make this work by searching the
  // stack for the generator's frame, but for the moment, we only need this
  // function to handle generators we've found on promises' reaction records,
  // which should always be suspended.
  MOZ_ASSERT(genObj->isSuspended());

  // Do we have an existing Debugger.Frame for this generator?
  DependentAddPtr<GeneratorWeakMap> p(cx, generatorFrames, genObj);
  if (p) {
    MOZ_ASSERT(&p->value()->unwrappedGenerator() == genObj);
    result.set(p->value());
    return true;
  }

  // Create a new Debugger.Frame.
  RootedObject proto(
      cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
  Rooted<NativeObject*> debugger(cx, object);

  result.set(DebuggerFrame::create(cx, proto, debugger, nullptr, genObj));
  if (!result) {
    return false;
  }

  if (!p.add(cx, generatorFrames, genObj, result)) {
    terminateDebuggerFrame(cx->gcContext(), this, result, NullFramePtr());
    return false;
  }

  return true;
}

static bool DebuggerExists(
    GlobalObject* global, const std::function<bool(Debugger* dbg)>& predicate) {
  // The GC analysis can't determine that the predicate can't GC, so let it know
  // explicitly.
  JS::AutoSuppressGCAnalysis nogc;

  for (Realm::DebuggerVectorEntry& entry : global->getDebuggers(nogc)) {
    // Callbacks should not create new references to the debugger, so don't
    // use a barrier. This allows this method to be called during GC.
    if (predicate(entry.dbg.unbarrieredGet())) {
      return true;
    }
  }
  return false;
}

/* static */
bool Debugger::hasLiveHook(GlobalObject* global, Hook which) {
  return DebuggerExists(global,
                        [=](Debugger* dbg) { return dbg->getHook(which); });
}

/* static */
bool DebugAPI::debuggerObservesAllExecution(GlobalObject* global) {
  return DebuggerExists(
      global, [=](Debugger* dbg) { return dbg->observesAllExecution(); });
}

/* static */
bool DebugAPI::debuggerObservesCoverage(GlobalObject* global) {
  return DebuggerExists(global,
                        [=](Debugger* dbg) { return dbg->observesCoverage(); });
}

/* static */
bool DebugAPI::debuggerObservesAsmJS(GlobalObject* global) {
  return DebuggerExists(global,
                        [=](Debugger* dbg) { return dbg->observesAsmJS(); });
}

/* static */
bool DebugAPI::debuggerObservesWasm(GlobalObject* global) {
  return DebuggerExists(global,
                        [=](Debugger* dbg) { return dbg->observesWasm(); });
}

/* static */
bool DebugAPI::debuggerObservesNativeCall(GlobalObject* global) {
  return DebuggerExists(
      global, [=](Debugger* dbg) { return dbg->observesNativeCalls(); });
}

/* static */
bool DebugAPI::hasExceptionUnwindHook(GlobalObject* global) {
  return Debugger::hasLiveHook(global, Debugger::OnExceptionUnwind);
}

/* static */
bool DebugAPI::hasDebuggerStatementHook(GlobalObject* global) {
  return Debugger::hasLiveHook(global, Debugger::OnDebuggerStatement);
}

template <typename HookIsEnabledFun /* bool (Debugger*) */>
bool DebuggerList<HookIsEnabledFun>::init(JSContext* cx) {
  // Determine which debuggers will receive this event, and in what order.
  // Make a copy of the list, since the original is mutable and we will be
  // calling into arbitrary JS.
  Handle<GlobalObject*> global = cx->global();
  JS::AutoAssertNoGC nogc;
  for (Realm::DebuggerVectorEntry& entry : global->getDebuggers(nogc)) {
    Debugger* dbg = entry.dbg;
    if (dbg->isHookCallAllowed(cx) && hookIsEnabled(dbg)) {
      if (!debuggers.append(ObjectValue(*dbg->toJSObject()))) {
        return false;
      }
    }
  }
  return true;
}

template <typename HookIsEnabledFun /* bool (Debugger*) */>
template <typename FireHookFun /* bool (Debugger*) */>
bool DebuggerList<HookIsEnabledFun>::dispatchHook(JSContext* cx,
                                                  FireHookFun fireHook) {
  // Preserve the debuggee's microtask event queue while we run the hooks, so
  // the debugger's microtask checkpoints don't run from the debuggee's
  // microtasks, and vice versa.
  JS::AutoDebuggerJobQueueInterruption adjqi;
  if (!adjqi.init(cx)) {
    return false;
  }

  // Deliver the event to each debugger, checking again to make sure it
  // should still be delivered.
  Handle<GlobalObject*> global = cx->global();
  for (Value* p = debuggers.begin(); p != debuggers.end(); p++) {
    Debugger* dbg = Debugger::fromJSObject(&p->toObject());
    EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);
    if (dbg->debuggees.has(global) && hookIsEnabled(dbg)) {
      bool result =
          dbg->enterDebuggerHook(cx, [&]() -> bool { return fireHook(dbg); });
      adjqi.runJobs();
      if (!result) {
        return false;
      }
    }
  }
  return true;
}

template <typename HookIsEnabledFun /* bool (Debugger*) */>
template <typename FireHookFun /* bool (Debugger*) */>
void DebuggerList<HookIsEnabledFun>::dispatchQuietHook(JSContext* cx,
                                                       FireHookFun fireHook) {
  bool result =
      dispatchHook(cx, [&](Debugger* dbg) -> bool { return fireHook(dbg); });

  // dispatchHook may fail due to OOM. This OOM is not handlable at the
  // callsites of dispatchQuietHook in the engine.
  if (!result) {
    cx->clearPendingException();
  }
}

template <typename HookIsEnabledFun /* bool (Debugger*) */>
template <typename FireHookFun /* bool (Debugger*, ResumeMode&, MutableHandleValue vp) */>
bool DebuggerList<HookIsEnabledFun>::dispatchResumptionHook(
    JSContext* cx, AbstractFramePtr frame, FireHookFun fireHook) {
  ResumeMode resumeMode = ResumeMode::Continue;
  RootedValue rval(cx);
  return dispatchHook(cx,
                      [&](Debugger* dbg) -> bool {
                        return fireHook(dbg, resumeMode, &rval);
                      }) &&
         ApplyFrameResumeMode(cx, frame, resumeMode, rval);
}

JSObject* Debugger::getHook(Hook hook) const {
  MOZ_ASSERT(hook >= 0 && hook < HookCount);
  const Value& v = object->getReservedSlot(JSSLOT_DEBUG_HOOK_START +
                                           std::underlying_type_t<Hook>(hook));
  return v.isUndefined() ? nullptr : &v.toObject();
}

bool Debugger::hasAnyLiveHooks() const {
  // A onNewGlobalObject hook does not hold its Debugger live, so its behavior
  // is nondeterministic. This behavior is not satisfying, but it is at least
  // documented.
  if (getHook(OnDebuggerStatement) || getHook(OnExceptionUnwind) ||
      getHook(OnNewScript) || getHook(OnEnterFrame)) {
    return true;
  }

  return false;
}

/* static */
bool DebugAPI::slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame) {
#ifdef MOZ_EXECUTION_TRACING
  if (cx->hasExecutionTracer()) {
    cx->getExecutionTracer().onEnterFrame(cx, frame);
  }
#endif
  return Debugger::dispatchResumptionHook(
      cx, frame,
      [frame](Debugger* dbg) -> bool {
        return dbg->observesFrame(frame) && dbg->observesEnterFrame();
      },
      [&](Debugger* dbg, ResumeMode& resumeMode, MutableHandleValue vp)
          -> bool { return dbg->fireEnterFrame(cx, resumeMode, vp); });
}

/* static */
bool DebugAPI::slowPathOnResumeFrame(JSContext* cx, AbstractFramePtr frame) {
#ifdef MOZ_EXECUTION_TRACING
  if (cx->hasExecutionTracer()) {
    cx->getExecutionTracer().onEnterFrame(cx, frame);
  }
#endif
  // Don't count on this method to be called every time a generator is
  // resumed! This is called only if the frame's debuggee bit is set,
  // i.e. the script has breakpoints or the frame is stepping.
  MOZ_ASSERT(frame.isGeneratorFrame());
  MOZ_ASSERT(frame.isDebuggee());

  Rooted<AbstractGeneratorObject*> genObj(
      cx, GetGeneratorObjectForFrame(cx, frame));
  MOZ_ASSERT(genObj);

  // If there is an OOM, we mark all of the Debugger.Frame objects terminated
  // because we want to ensure that none of the frames are in a partially
  // initialized state where they are in "generatorFrames" but not "frames".
  auto terminateDebuggerFramesGuard = MakeScopeExit([&] {
    Debugger::terminateDebuggerFrames(cx, frame);

    MOZ_ASSERT(!DebugAPI::inFrameMaps(frame));
  });

  // For each debugger, if there is an existing Debugger.Frame object for the
  // resumed `frame`, update it with the new frame pointer and make sure the
  // frame is observable.
  FrameIter iter(cx);
  MOZ_ASSERT(iter.abstractFramePtr() == frame);
  {
    JS::AutoAssertNoGC nogc;
    for (Realm::DebuggerVectorEntry& entry :
         frame.global()->getDebuggers(nogc)) {
      Debugger* dbg = entry.dbg;
      if (Debugger::GeneratorWeakMap::Ptr generatorEntry =
              dbg->generatorFrames.lookup(genObj)) {
        DebuggerFrame* frameObj = generatorEntry->value();
        MOZ_ASSERT(&frameObj->unwrappedGenerator() == genObj);
        if (!dbg->frames.putNew(frame, frameObj)) {
          ReportOutOfMemory(cx);
          return false;
        }
        if (!frameObj->resume(iter)) {
          return false;
        }
      }
    }
  }

  terminateDebuggerFramesGuard.release();

  return slowPathOnEnterFrame(cx, frame);
}

/* static */
NativeResumeMode DebugAPI::slowPathOnNativeCall(JSContext* cx,
                                                const CallArgs& args,
                                                CallReason reason) {
  if (!cx->realm()->debuggerObservesNativeCall()) {
    return NativeResumeMode::Continue;
  }

  DebuggerList debuggerList(cx, [](Debugger* dbg) -> bool {
    return dbg->getHook(Debugger::OnNativeCall);
  });

  if (!debuggerList.init(cx)) {
    return NativeResumeMode::Abort;
  }

  if (debuggerList.empty()) {
    return NativeResumeMode::Continue;
  }

  // The onNativeCall hook is fired when self hosted functions are called,
  // and any other self hosted function or C++ native that is directly called
  // by the self hosted function is considered to be part of the same
  // native call, except for the following 4 cases:
  //
  //  * callContentFunction and constructContentFunction,
  //    which uses CallReason::CallContent
  //  * Function.prototype.call and Function.prototype.apply,
  //    which uses CallReason::FunCall
  //  * Getter call which uses CallReason::Getter
  //  * Setter call which uses CallReason::Setter
  //
  // We check this only after checking that debuggerList has items in order
  // to avoid unnecessary calls to cx->currentScript(), which can be expensive
  // when the top frame is in jitcode.
  JSScript* script = cx->currentScript();
  if (script && script->selfHosted() && reason != CallReason::CallContent &&
      reason != CallReason::FunCall && reason != CallReason::Getter &&
      reason != CallReason::Setter) {
    return NativeResumeMode::Continue;
  }

  RootedValue rval(cx);
  ResumeMode resumeMode = ResumeMode::Continue;
  bool result = debuggerList.dispatchHook(cx, [&](Debugger* dbg) -> bool {
    return dbg->fireNativeCall(cx, args, reason, resumeMode, &rval);
  });
  if (!result) {
    return NativeResumeMode::Abort;
  }

  // Hook must follow normal native function conventions and not return
  // primitive values.
  if (resumeMode == ResumeMode::Return) {
    if (args.isConstructing() && !rval.isObject()) {
      JS_ReportErrorASCII(
          cx, "onNativeCall hook must return an object for constructor call");
      return NativeResumeMode::Abort;
    }
  }

  // The value is not in any particular compartment, so it needs to be
  // explicitly wrapped into the debuggee compartment.
  if (!cx->compartment()->wrap(cx, &rval)) {
    return NativeResumeMode::Abort;
  }

  switch (resumeMode) {
    case ResumeMode::Continue:
      break;

    case ResumeMode::Throw:
      cx->setPendingException(rval, ShouldCaptureStack::Always);
      return NativeResumeMode::Abort;

    case ResumeMode::Terminate:
      cx->reportUncatchableException();
      return NativeResumeMode::Abort;

    case ResumeMode::Return:
      args.rval().set(rval);
      return NativeResumeMode::Override;
  }

  return NativeResumeMode::Continue;
}

/* static */
bool DebugAPI::slowPathShouldAvoidSideEffects(JSContext* cx) {
  return DebuggerExists(
      cx->global(), [=](Debugger* dbg) { return dbg->shouldAvoidSideEffects; });
}

/*
 * RAII class to mark a generator as "running" temporarily while running
 * debugger code.
 *
 * When Debugger::slowPathOnLeaveFrame is called for a frame that is yielding
 * or awaiting, its generator is in the "suspended" state. Letting script
 * observe this state, with the generator on stack yet also reenterable, would
 * be bad, so we mark it running while we fire events.
 */

class MOZ_RAII AutoSetGeneratorRunning {
  int32_t resumeIndex_;
  AsyncGeneratorObject::State asyncGenState_;
  Rooted<AbstractGeneratorObject*> genObj_;

 public:
  AutoSetGeneratorRunning(JSContext* cx,
                          Handle<AbstractGeneratorObject*> genObj)
      : resumeIndex_(0),
        asyncGenState_(static_cast<AsyncGeneratorObject::State>(0)),
        genObj_(cx, genObj) {
    if (genObj) {
      if (!genObj->isClosed() && !genObj->isBeforeInitialYield() &&
          genObj->isSuspended()) {
        // Yielding or awaiting.
        resumeIndex_ = genObj->resumeIndex();
        genObj->setRunning();

        // Async generators have additionally bookkeeping which must be
        // adjusted when switching over to the running state.
        if (genObj->is<AsyncGeneratorObject>()) {
          auto* generator = &genObj->as<AsyncGeneratorObject>();
          asyncGenState_ = generator->state();
          generator->setExecuting();
        }
      } else {
        // Returning or throwing. The generator is already closed, if
        // it was ever exposed at all.
        genObj_ = nullptr;
      }
    }
  }

  ~AutoSetGeneratorRunning() {
    if (genObj_) {
      MOZ_ASSERT(genObj_->isRunning());
      genObj_->setResumeIndex(resumeIndex_);
      if (genObj_->is<AsyncGeneratorObject>()) {
        genObj_->as<AsyncGeneratorObject>().setState(asyncGenState_);
      }
    }
  }
};

/*
 * Handle leaving a frame with debuggers watching. |frameOk| indicates whether
 * the frame is exiting normally or abruptly. Set |cx|'s exception and/or
 * |cx->fp()|'s return value, and return a new success value.
 */

/* static */
bool DebugAPI::slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame,
                                    const jsbytecode* pc, bool frameOk) {
#ifdef MOZ_EXECUTION_TRACING
  if (cx->hasExecutionTracer()) {
    cx->getExecutionTracer().onLeaveFrame(cx, frame);
  }
#endif
  MOZ_ASSERT_IF(!frame.isWasmDebugFrame(), pc);

  mozilla::DebugOnly<Handle<GlobalObject*>> debuggeeGlobal = cx->global();

  // These are updated below, but consulted by the cleanup code we register now,
  // so declare them here, initialized to quiescent values.
  Rooted<Completion> completion(cx);
  bool success = false;

  auto frameMapsGuard = MakeScopeExit([&] {
    // Clean up all Debugger.Frame instances on exit. On suspending, pass the
    // flag that says to leave those frames `.live`. Note that if the completion
    // is a suspension but success is false, the generator gets closed, not
    // suspended.
    if (success && completion.get().suspending()) {
      Debugger::suspendGeneratorDebuggerFrames(cx, frame);
    } else {
      Debugger::terminateDebuggerFrames(cx, frame);
    }
  });

  // The onPop handler and associated clean up logic should not run multiple
  // times on the same frame. If slowPathOnLeaveFrame has already been
  // called, the frame will not be present in the Debugger frame maps.
  Rooted<Debugger::DebuggerFrameVector> frames(cx);
  if (!Debugger::getDebuggerFrames(frame, &frames)) {
    // There is at least one match Debugger.Frame we failed to process, so drop
    // the pending exception and raise an out-of-memory instead.
    if (!frameOk) {
      cx->clearPendingException();
    }
    ReportOutOfMemory(cx);
    return false;
  }
  if (frames.empty()) {
    return frameOk;
  }

  // Convert current exception state into a Completion and clear exception off
  // of the JSContext.
  completion = Completion::fromJSFramePop(cx, frame, pc, frameOk);

  ResumeMode resumeMode = ResumeMode::Continue;
  RootedValue rval(cx);

  {
    // Preserve the debuggee's microtask event queue while we run the hooks, so
    // the debugger's microtask checkpoints don't run from the debuggee's
    // microtasks, and vice versa.
    JS::AutoDebuggerJobQueueInterruption adjqi;
    if (!adjqi.init(cx)) {
      return false;
    }

    // This path can be hit via unwinding the stack due to over-recursion or
    // OOM. In those cases, don't fire the frames' onPop handlers, because
    // invoking JS will only trigger the same condition. See
    // slowPathOnExceptionUnwind.
    if (!cx->isThrowingOverRecursed() && !cx->isThrowingOutOfMemory()) {
      Rooted<AbstractGeneratorObject*> genObj(
          cx, frame.isGeneratorFrame() ? GetGeneratorObjectForFrame(cx, frame)
                                       : nullptr);

      // For each Debugger.Frame, fire its onPop handler, if any.
      for (size_t i = 0; i < frames.length(); i++) {
        Handle<DebuggerFrame*> frameobj = frames[i];
        Debugger* dbg = frameobj->owner();
        EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);

        // Removing a global from a Debugger's debuggee set kills all of that
        // Debugger's D.Fs in that global. This means that one D.F's onPop can
        // kill the next D.F. So we have to check whether frameobj is still "on
        // the stack".
        if (frameobj->isOnStack() && frameobj->onPopHandler()) {
          OnPopHandler* handler = frameobj->onPopHandler();

          bool result = dbg->enterDebuggerHook(cx, [&]() -> bool {
            ResumeMode nextResumeMode = ResumeMode::Continue;
            RootedValue nextValue(cx);

            // Call the onPop handler.
            bool success;
            {
              // Mark the generator as running, to prevent reentrance.
              //
              // At certain points in a generator's lifetime,
              // GetGeneratorObjectForFrame can return null even when the
              // generator exists, but at those points the generator has not yet
              // been exposed to JavaScript, so reentrance isn't possible
              // anyway. So there's no harm done if this has no effect in that
              // case.
              AutoSetGeneratorRunning asgr(cx, genObj);
              success = handler->onPop(cx, frameobj, completion, nextResumeMode,
                                       &nextValue);
            }

            return dbg->processParsedHandlerResult(cx, frame, pc, success,
                                                   nextResumeMode, nextValue,
                                                   resumeMode, &rval);
          });
          adjqi.runJobs();

          if (!result) {
            return false;
          }

          // At this point, we are back in the debuggee compartment, and
          // any error has been wrapped up as a completion value.
          MOZ_ASSERT(!cx->isExceptionPending());
        }
      }
    }
  }

  completion.get().updateFromHookResult(resumeMode, rval);

  // Now that we've run all the handlers, extract the final resumption mode. */
  ResumeMode completionResumeMode;
  RootedValue completionValue(cx);
  Rooted<SavedFrame*> completionStack(cx);
  completion.get().toResumeMode(completionResumeMode, &completionValue,
                                &completionStack);

  // If we are returning the original value used to create the completion, then
  // we don't want to treat the resumption value as a Return completion, because
  // that would cause us to apply AdjustGeneratorResumptionValue to the
  // already-adjusted value that the generator actually returned.
  if (resumeMode == ResumeMode::Continue &&
      completionResumeMode == ResumeMode::Return) {
    completionResumeMode = ResumeMode::Continue;
  }

  if (!ApplyFrameResumeMode(cx, frame, completionResumeMode, completionValue,
                            completionStack)) {
    if (!cx->isPropagatingForcedReturn()) {
      // If this is an exception or termination, we just propagate that along.
      return false;
    }

    // Since we are leaving the frame here, we can convert a forced return
    // into a normal return right away.
    cx->clearPropagatingForcedReturn();
  }
  success = true;
  return true;
}

/* static */
bool DebugAPI::slowPathOnNewGenerator(JSContext* cx, AbstractFramePtr frame,
                                      Handle<AbstractGeneratorObject*> genObj) {
  // This is called from JSOp::Generator, after default parameter expressions
  // are evaluated and well after onEnterFrame, so Debugger.Frame objects for
  // `frame` may already have been exposed to debugger code. The
  // AbstractGeneratorObject for this generator call, though, has just been
  // created. It must be associated with any existing Debugger.Frames.

  // Initializing frames with their associated generator is critical to the
  // functionality of the debugger, so if there is an OOM, we want to
  // cleanly terminate all of the frames.
  auto terminateDebuggerFramesGuard =
      MakeScopeExit([&] { Debugger::terminateDebuggerFrames(cx, frame); });

  bool ok = true;
  gc::AutoSuppressGC nogc(cx);
  Debugger::forEachOnStackDebuggerFrame(
      frame, nogc, [&](Debugger* dbg, DebuggerFrame* frameObjPtr) {
        if (!ok) {
          return;
        }

        Rooted<DebuggerFrame*> frameObj(cx, frameObjPtr);

        AutoRealm ar(cx, frameObj);

        if (!DebuggerFrame::setGeneratorInfo(cx, frameObj, genObj)) {
          // This leaves `genObj` and `frameObj` unassociated. It's OK
          // because we won't pause again with this generator on the stack:
          // the caller will immediately discard `genObj` and unwind `frame`.
          ok = false;
          return;
        }

        DependentAddPtr<Debugger::GeneratorWeakMap> genPtr(
            cx, dbg->generatorFrames, genObj);
        if (!genPtr.add(cx, dbg->generatorFrames, genObj, frameObj)) {
          ok = false;
        }
      });

  if (!ok) {
    return false;
  }

  terminateDebuggerFramesGuard.release();
  return true;
}

/* static */
bool DebugAPI::slowPathOnDebuggerStatement(JSContext* cx,
                                           AbstractFramePtr frame) {
  return Debugger::dispatchResumptionHook(
      cx, frame,
      [](Debugger* dbg) -> bool {
        return dbg->getHook(Debugger::OnDebuggerStatement);
      },
      [&](Debugger* dbg, ResumeMode& resumeMode, MutableHandleValue vp)
          -> bool { return dbg->fireDebuggerStatement(cx, resumeMode, vp); });
}

/* static */
bool DebugAPI::slowPathOnExceptionUnwind(JSContext* cx,
                                         AbstractFramePtr frame) {
  // Invoking more JS on an over-recursed stack or after OOM is only going
  // to result in more of the same error.
  if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory()) {
    return true;
  }

  // The Debugger API mustn't muck with frames from self-hosted scripts.
  if (frame.hasScript() && frame.script()->selfHosted()) {
    return true;
  }

  DebuggerList debuggerList(cx, [](Debugger* dbg) -> bool {
    return dbg->getHook(Debugger::OnExceptionUnwind);
  });

  if (!debuggerList.init(cx)) {
    return false;
  }

  if (debuggerList.empty()) {
    return true;
  }

  // We save and restore the exception once up front to avoid having to do it
  // for each 'onExceptionUnwind' hook that has been registered, and we also
  // only do it if the debuggerList contains items in order to avoid extra work.
  RootedValue exc(cx);
  Rooted<SavedFrame*> stack(cx, cx->getPendingExceptionStack());
  if (!cx->getPendingException(&exc)) {
    return false;
  }
  cx->clearPendingException();

  bool result = debuggerList.dispatchResumptionHook(
      cx, frame,
      [&](Debugger* dbg, ResumeMode& resumeMode,
          MutableHandleValue vp) -> bool {
        return dbg->fireExceptionUnwind(cx, exc, resumeMode, vp);
      });
  if (!result) {
    return false;
  }

  cx->setPendingException(exc, stack);
  return true;
}

// TODO: Remove Remove this function when all properties/methods returning a
///      DebuggerEnvironment have been given a C++ interface (bug 1271649).
bool Debugger::wrapEnvironment(JSContext* cx, Handle<Env*> env,
                               MutableHandleValue rval) {
  if (!env) {
    rval.setNull();
    return true;
  }

  Rooted<DebuggerEnvironment*> envobj(cx);

  if (!wrapEnvironment(cx, env, &envobj)) {
    return false;
  }

  rval.setObject(*envobj);
  return true;
}

bool Debugger::wrapEnvironment(JSContext* cx, Handle<Env*> env,
                               MutableHandle<DebuggerEnvironment*> result) {
  MOZ_ASSERT(env);

  // DebuggerEnv should only wrap a debug scope chain obtained (transitively)
  // from GetDebugEnvironmentFor(Frame|Function).
  MOZ_ASSERT(!IsSyntacticEnvironment(env));

  DependentAddPtr<EnvironmentWeakMap> p(cx, environments, env);
  if (p) {
    result.set(&p->value()->as<DebuggerEnvironment>());
  } else {
    // Create a new Debugger.Environment for env.
    RootedObject proto(
        cx, &object->getReservedSlot(JSSLOT_DEBUG_ENV_PROTO).toObject());
    Rooted<NativeObject*> debugger(cx, object);

    Rooted<DebuggerEnvironment*> envobj(
        cx, DebuggerEnvironment::create(cx, proto, env, debugger));
    if (!envobj) {
      return false;
    }

    if (!p.add(cx, environments, env, envobj)) {
      // We need to destroy the edge to the referent, to avoid trying to trace
      // it during untimely collections.
      envobj->clearReferent();
      return false;
    }

    result.set(envobj);
  }

  return true;
}

bool Debugger::wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp) {
  cx->check(object.get());

  if (vp.isObject()) {
    RootedObject obj(cx, &vp.toObject());
    Rooted<DebuggerObject*> dobj(cx);

    if (!wrapDebuggeeObject(cx, obj, &dobj)) {
      return false;
    }

    vp.setObject(*dobj);
  } else if (vp.isMagic()) {
    Rooted<PlainObject*> optObj(cx, NewPlainObject(cx));
    if (!optObj) {
      return false;
    }

    // We handle three sentinel values: missing arguments
    // (JS_MISSING_ARGUMENTS), optimized out slots (JS_OPTIMIZED_OUT),
    // and uninitialized bindings (JS_UNINITIALIZED_LEXICAL).
    //
    // Other magic values should not have escaped.
    PropertyName* name;
    switch (vp.whyMagic()) {
      case JS_MISSING_ARGUMENTS:
        name = cx->names().missingArguments;
        break;
      case JS_OPTIMIZED_OUT:
        name = cx->names().optimizedOut;
        break;
      case JS_UNINITIALIZED_LEXICAL:
        name = cx->names().uninitialized;
        break;
      default:
        MOZ_CRASH("Unsupported magic value escaped to Debugger");
    }

    RootedValue trueVal(cx, BooleanValue(true));
    if (!DefineDataProperty(cx, optObj, name, trueVal)) {
      return false;
    }

    vp.setObject(*optObj);
  } else if (!cx->compartment()->wrap(cx, vp)) {
    vp.setUndefined();
    return false;
  }

  return true;
}

bool Debugger::wrapNullableDebuggeeObject(
    JSContext* cx, HandleObject obj, MutableHandle<DebuggerObject*> result) {
  if (!obj) {
    result.set(nullptr);
    return true;
  }

  return wrapDebuggeeObject(cx, obj, result);
}

bool Debugger::wrapDebuggeeObject(JSContext* cx, HandleObject obj,
                                  MutableHandle<DebuggerObject*> result) {
  MOZ_ASSERT(obj);

  DependentAddPtr<ObjectWeakMap> p(cx, objects, obj);
  if (p) {
    result.set(&p->value()->as<DebuggerObject>());
  } else {
    // Create a new Debugger.Object for obj.
    Rooted<NativeObject*> debugger(cx, object);
    RootedObject proto(
        cx, &object->getReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO).toObject());
    Rooted<DebuggerObject*> dobj(
        cx, DebuggerObject::create(cx, proto, obj, debugger));
    if (!dobj) {
      return false;
    }

    if (!p.add(cx, objects, obj, dobj)) {
      // We need to destroy the edge to the referent, to avoid trying to trace
      // it during untimely collections.
      dobj->clearReferent();
      return false;
    }

    result.set(dobj);
  }

  return true;
}

static DebuggerObject* ToNativeDebuggerObject(JSContext* cx,
                                              MutableHandleObject obj) {
  if (!obj->is<DebuggerObject>()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_NOT_EXPECTED_TYPE, "Debugger",
                              "Debugger.Object", obj->getClass()->name);
    return nullptr;
  }

  return &obj->as<DebuggerObject>();
}

bool Debugger::unwrapDebuggeeObject(JSContext* cx, MutableHandleObject obj) {
  DebuggerObject* ndobj = ToNativeDebuggerObject(cx, obj);
  if (!ndobj) {
    return false;
  }

  if (ndobj->owner() != Debugger::fromJSObject(object)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_WRONG_OWNER, "Debugger.Object");
    return false;
  }

  obj.set(ndobj->referent());
  return true;
}

bool Debugger::unwrapDebuggeeValue(JSContext* cx, MutableHandleValue vp) {
  cx->check(object.get(), vp);
  if (vp.isObject()) {
    RootedObject dobj(cx, &vp.toObject());
    if (!unwrapDebuggeeObject(cx, &dobj)) {
      return false;
    }
    vp.setObject(*dobj);
  }
  return true;
}

static bool CheckArgCompartment(JSContext* cx, JSObject* obj, JSObject* arg,
                                const char* methodname, const char* propname) {
  if (arg->compartment() != obj->compartment()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_COMPARTMENT_MISMATCH, methodname,
                              propname);
    return false;
  }
  return true;
}

static bool CheckArgCompartment(JSContext* cx, JSObject* obj, HandleValue v,
                                const char* methodname, const char* propname) {
  if (v.isObject()) {
    return CheckArgCompartment(cx, obj, &v.toObject(), methodname, propname);
  }
  return true;
}

bool Debugger::unwrapPropertyDescriptor(
    JSContext* cx, HandleObject obj, MutableHandle<PropertyDescriptor> desc) {
  if (desc.hasValue()) {
    RootedValue value(cx, desc.value());
    if (!unwrapDebuggeeValue(cx, &value) ||
        !CheckArgCompartment(cx, obj, value, "defineProperty""value")) {
      return false;
    }
    desc.setValue(value);
  }

  if (desc.hasGetter()) {
    RootedObject get(cx, desc.getter());
    if (get) {
      if (!unwrapDebuggeeObject(cx, &get)) {
        return false;
      }
      if (!CheckArgCompartment(cx, obj, get, "defineProperty""get")) {
        return false;
      }
    }
    desc.setGetter(get);
  }

  if (desc.hasSetter()) {
    RootedObject set(cx, desc.setter());
    if (set) {
      if (!unwrapDebuggeeObject(cx, &set)) {
        return false;
      }
      if (!CheckArgCompartment(cx, obj, set, "defineProperty""set")) {
        return false;
      }
    }
    desc.setSetter(set);
  }

  return true;
}

/*** Debuggee resumption values and debugger error handling *****************/

static bool GetResumptionProperty(JSContext* cx, HandleObject obj,
                                  Handle<PropertyName*> name,
                                  ResumeMode namedMode, ResumeMode& resumeMode,
                                  MutableHandleValue vp, int* hits) {
  bool found;
  if (!HasProperty(cx, obj, name, &found)) {
    return false;
  }
  if (found) {
    ++*hits;
    resumeMode = namedMode;
    if (!GetProperty(cx, obj, obj, name, vp)) {
      return false;
    }
  }
  return true;
}

bool js::ParseResumptionValue(JSContext* cx, HandleValue rval,
                              ResumeMode& resumeMode, MutableHandleValue vp) {
  if (rval.isUndefined()) {
    resumeMode = ResumeMode::Continue;
    vp.setUndefined();
    return true;
  }
  if (rval.isNull()) {
    resumeMode = ResumeMode::Terminate;
    vp.setUndefined();
    return true;
  }

  int hits = 0;
  if (rval.isObject()) {
    RootedObject obj(cx, &rval.toObject());
    if (!GetResumptionProperty(cx, obj, cx->names().return_, ResumeMode::Return,
                               resumeMode, vp, &hits)) {
      return false;
    }
    if (!GetResumptionProperty(cx, obj, cx->names().throw_, ResumeMode::Throw,
                               resumeMode, vp, &hits)) {
      return false;
    }
  }

  if (hits != 1) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_BAD_RESUMPTION);
    return false;
  }
  return true;
}

static bool CheckResumptionValue(JSContext* cx, AbstractFramePtr frame,
                                 const jsbytecode* pc, ResumeMode resumeMode,
                                 MutableHandleValue vp) {
  // Only forced returns from a frame need to be validated because forced
  // throw values behave just like debuggee `throw` statements. Since
  // forced-return is all custom logic within SpiderMonkey itself, we need
  // our own custom validation for it to conform with what is expected.
  if (resumeMode != ResumeMode::Return || !frame) {
    return true;
  }

  // This replicates the ECMA spec's behavior for [[Construct]] in derived
  // class constructors (section 9.2.2 of ECMA262-2020), where returning a
  // non-undefined primitive causes an exception tobe thrown.
  if (frame.debuggerNeedsCheckPrimitiveReturn() && vp.isPrimitive()) {
    if (!vp.isUndefined()) {
      ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, vp,
                       nullptr);
      return false;
    }

    RootedValue thisv(cx);
    {
      AutoRealm ar(cx, frame.environmentChain());
      if (!GetThisValueForDebuggerFrameMaybeOptimizedOut(cx, frame, pc,
                                                         &thisv)) {
        return false;
      }
    }

    if (thisv.isMagic(JS_UNINITIALIZED_LEXICAL)) {
      return ThrowUninitializedThis(cx);
    }
    MOZ_ASSERT(!thisv.isMagic());

    if (!cx->compartment()->wrap(cx, &thisv)) {
      return false;
    }
    vp.set(thisv);
  }

  // Check for forcing return from a generator before the initial yield. This
  // is not supported because some engine-internal code assumes a call to a
  // generator will return a GeneratorObject; see bug 1477084.
  if (frame.isFunctionFrame() && frame.callee()->isGenerator()) {
    Rooted<AbstractGeneratorObject*> genObj(cx);
    {
      AutoRealm ar(cx, frame.callee());
      genObj = GetGeneratorObjectForFrame(cx, frame);
    }

    if (!genObj || genObj->isBeforeInitialYield()) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_DEBUG_FORCED_RETURN_DISALLOWED);
      return false;
    }
  }

  return true;
}

// Last-minute sanity adjustments to resumption.
//
// This is called last, as we leave the debugger. It must happen outside the
// control of the uncaughtExceptionHook, because this code assumes we won't
// change our minds and continue execution--we must not close the generator
// object unless we're really going to force-return.
[[nodiscard]] static bool AdjustGeneratorResumptionValue(
    JSContext* cx, AbstractFramePtr frame, ResumeMode& resumeMode,
    MutableHandleValue vp) {
  if (resumeMode != ResumeMode::Return && resumeMode != ResumeMode::Throw) {
    return true;
  }

  if (!frame) {
    return true;
  }
  // Async modules need to be handled separately, as they do not have a callee.
  // frame.callee will throw if it is called on a moduleFrame.
  bool isAsyncModule = frame.isModuleFrame() && frame.script()->isAsync();
  if (!frame.isFunctionFrame() && !isAsyncModule) {
    return true;
  }

  // Treat `{return: <value>}` like a `return` statement. Simulate what the
  // debuggee would do for an ordinary `return` statement, using a few bytecode
  // instructions. It's simpler to do the work manually than to count on that
  // bytecode sequence existing in the debuggee, somehow jump to it, and then
  // avoid re-entering the debugger from it.
  //
  // Similarly treat `{throw: <value>}` like a `throw` statement.
  //
  // Note: Async modules use the same handling as async functions.
  if (frame.isFunctionFrame() && frame.callee()->isGenerator()) {
    // Throw doesn't require any special processing for (async) generators.
    if (resumeMode == ResumeMode::Throw) {
      return true;
    }

    // Forcing return from a (possibly async) generator.
    Rooted<AbstractGeneratorObject*> genObj(
        cx, GetGeneratorObjectForFrame(cx, frame));

    // We already went through CheckResumptionValue, which would have replaced
    // this invalid resumption value with an error if we were trying to force
    // return before the initial yield.
    MOZ_RELEASE_ASSERT(genObj && !genObj->isBeforeInitialYield());

    // 1.  `return <value>` creates and returns a new object,
    //     `{value: <value>, done: true}`.
    //
    // For non-async generators, the iterator result object is created in
    // bytecode, so we have to simulate that here. For async generators, our
    // C++ implementation of AsyncGeneratorResolve will do this. So don't do it
    // twice:
    if (!genObj->is<AsyncGeneratorObject>()) {
      PlainObject* pair = CreateIterResultObject(cx, vp, true);
      if (!pair) {
        return false;
      }
      vp.setObject(*pair);
    }

    // 2.  The generator must be closed.
    genObj->setClosed(cx);

    // Async generators have additionally bookkeeping which must be adjusted
    // when switching over to the closed state.
    if (genObj->is<AsyncGeneratorObject>()) {
      genObj->as<AsyncGeneratorObject>().setCompleted();
    }
  } else if (isAsyncModule || frame.callee()->isAsync()) {
    if (AbstractGeneratorObject* genObj =
            GetGeneratorObjectForFrame(cx, frame)) {
      // Throw doesn't require any special processing for async functions when
      // the internal generator object is already present.
      if (resumeMode == ResumeMode::Throw) {
        return true;
      }

      Rooted<AsyncFunctionGeneratorObject*> generator(
          cx, &genObj->as<AsyncFunctionGeneratorObject>());

      // 1.  `return <value>` fulfills and returns the async function's promise.
      Rooted<PromiseObject*> promise(cx, generator->promise());
      if (promise->state() == JS::PromiseState::Pending) {
        if (!AsyncFunctionResolve(cx, generator, vp)) {
          return false;
        }
      }
      vp.setObject(*promise);

      // 2.  The generator must be closed.
      generator->setClosed(cx);
    } else {
      // We're before entering the actual function code.

      // 1.  `throw <value>` creates a promise rejected with the value *vp.
      // 1.  `return <value>` creates a promise resolved with the value *vp.
      JSObject* promise = resumeMode == ResumeMode::Throw
                              ? PromiseObject::unforgeableReject(cx, vp)
                              : PromiseObject::unforgeableResolve(cx, vp);
      if (!promise) {
        return false;
      }
      vp.setObject(*promise);

      // 2.  Return normally in both cases.
      resumeMode = ResumeMode::Return;
    }
  }

  return true;
}

bool Debugger::processParsedHandlerResult(JSContext* cx, AbstractFramePtr frame,
                                          const jsbytecode* pc, bool success,
                                          ResumeMode resumeMode,
                                          HandleValue value,
                                          ResumeMode& resultMode,
                                          MutableHandleValue vp) {
  RootedValue rootValue(cx, value);
  if (!success || !prepareResumption(cx, frame, pc, resumeMode, &rootValue)) {
    RootedValue exceptionRv(cx);
    if (!callUncaughtExceptionHandler(cx, &exceptionRv) ||
--> --------------------

--> maximum size reached

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

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

¤ Dauer der Verarbeitung: 0.29 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