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) ||
        !ParseResumptionValue(cx, exceptionRv, resumeMode, &rootValue) ||
        !prepareResumption(cx, frame, pc, resumeMode, &rootValue)) {
      return false;
    }
  }

  // Since debugger hooks accumulate into the same final value handle, we
  // use that to throw if multiple hooks try to set a resumption value.
  if (resumeMode != ResumeMode::Continue) {
    if (resultMode != ResumeMode::Continue) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_DEBUG_RESUMPTION_CONFLICT);
      return false;
    }

    vp.set(rootValue);
    resultMode = resumeMode;
  }

  return true;
}

bool Debugger::processHandlerResult(JSContext* cx, bool success, HandleValue rv,
                                    AbstractFramePtr frame, jsbytecode* pc,
                                    ResumeMode& resultMode,
                                    MutableHandleValue vp) {
  ResumeMode resumeMode = ResumeMode::Continue;
  RootedValue value(cx);
  if (success) {
    success = ParseResumptionValue(cx, rv, resumeMode, &value);
  }
  return processParsedHandlerResult(cx, frame, pc, success, resumeMode, value,
                                    resultMode, vp);
}

bool Debugger::prepareResumption(JSContext* cx, AbstractFramePtr frame,
                                 const jsbytecode* pc, ResumeMode& resumeMode,
                                 MutableHandleValue vp) {
  return unwrapDebuggeeValue(cx, vp) &&
         CheckResumptionValue(cx, frame, pc, resumeMode, vp);
}

bool Debugger::callUncaughtExceptionHandler(JSContext* cx,
                                            MutableHandleValue vp) {
  // Uncaught exceptions arise from Debugger code, and so we must already be in
  // an NX section. This also establishes that we are already within the scope
  // of an AutoDebuggerJobQueueInterruption object.
  MOZ_ASSERT(EnterDebuggeeNoExecute::isLockedInStack(cx, *this));

  if (cx->isExceptionPending() && uncaughtExceptionHook) {
    RootedValue exc(cx);
    if (!cx->getPendingException(&exc)) {
      return false;
    }
    cx->clearPendingException();

    RootedValue fval(cx, ObjectValue(*uncaughtExceptionHook));
    if (js::Call(cx, fval, object, exc, vp)) {
      return true;
    }
  }
  return false;
}

bool Debugger::handleUncaughtException(JSContext* cx) {
  RootedValue rv(cx);

  return callUncaughtExceptionHandler(cx, &rv);
}

void Debugger::reportUncaughtException(JSContext* cx) {
  // Uncaught exceptions arise from Debugger code, and so we must already be
  // in an NX section.
  MOZ_ASSERT(EnterDebuggeeNoExecute::isLockedInStack(cx, *this));

  if (cx->isExceptionPending()) {
    // We want to report the pending exception, but we want to let the
    // embedding handle it however it wants to.  So pretend like we're
    // starting a new script execution on our current compartment (which
    // is the debugger compartment, so reported errors won't get
    // reported to various onerror handlers in debuggees) and as part of
    // that "execution" simply throw our exception so the embedding can
    // deal.
    RootedValue exn(cx);
    if (cx->getPendingException(&exn)) {
      // Clear the exception, because ReportErrorToGlobal will assert that
      // we don't have one.
      cx->clearPendingException();
      ReportErrorToGlobal(cx, cx->global(), exn);
    }

    // And if not, or if PrepareScriptEnvironmentAndInvoke somehow left an
    // exception on cx (which it totally shouldn't do), just give up.
    cx->clearPendingException();
  }
}

/*** Debuggee completion values *********************************************/

/* static */
Completion Completion::fromJSResult(JSContext* cx, bool ok, const Value& rv) {
  MOZ_ASSERT_IF(ok, !cx->isExceptionPending());

  if (ok) {
    return Completion(Return(rv));
  }

  if (!cx->isExceptionPending()) {
    return Completion(Terminate());
  }

  RootedValue exception(cx);
  Rooted<SavedFrame*> stack(cx, cx->getPendingExceptionStack());
  bool getSucceeded = cx->getPendingException(&exception);
  cx->clearPendingException();
  if (!getSucceeded) {
    return Completion(Terminate());
  }

  return Completion(Throw(exception, stack));
}

/* static */
Completion Completion::fromJSFramePop(JSContext* cx, AbstractFramePtr frame,
                                      const jsbytecode* pc, bool ok) {
  // Only Wasm frames get a null pc.
  MOZ_ASSERT_IF(!frame.isWasmDebugFrame(), pc);

  // If this isn't a generator suspension, then that's already handled above.
  if (!ok || !frame.isGeneratorFrame()) {
    return fromJSResult(cx, ok, frame.returnValue());
  }

  // A generator is being suspended or returning.

  // Since generators are never wasm, we can assume pc is not nullptr, and
  // that analyzing bytecode is meaningful.
  MOZ_ASSERT(!frame.isWasmDebugFrame());

  // If we're leaving successfully at a yield opcode, we're probably
  // suspending; the `isClosed()` check detects a debugger forced return from
  // an `onStep` handler, which looks almost the same.
  //
  // GetGeneratorObjectForFrame can return nullptr even when a generator
  // object does exist, if the frame is paused between the Generator and
  // SetAliasedVar opcodes. But by checking the opcode first we eliminate that
  // possibility, so it's fine to call genObj->isClosed().
  Rooted<AbstractGeneratorObject*> generatorObj(
      cx, GetGeneratorObjectForFrame(cx, frame));
  switch (JSOp(*pc)) {
    case JSOp::InitialYield:
      MOZ_ASSERT(!generatorObj->isClosed());
      return Completion(InitialYield(generatorObj));

    case JSOp::Yield:
      MOZ_ASSERT(!generatorObj->isClosed());
      return Completion(Yield(generatorObj, frame.returnValue()));

    case JSOp::Await:
      MOZ_ASSERT(!generatorObj->isClosed());
      return Completion(Await(generatorObj, frame.returnValue()));

    default:
      return Completion(Return(frame.returnValue()));
  }
}

void Completion::trace(JSTracer* trc) {
  variant.match([=](auto& var) { var.trace(trc); });
}

struct MOZ_STACK_CLASS Completion::BuildValueMatcher {
  JSContext* cx;
  Debugger* dbg;
  MutableHandleValue result;

  BuildValueMatcher(JSContext* cx, Debugger* dbg, MutableHandleValue result)
      : cx(cx), dbg(dbg), result(result) {
    cx->check(dbg->toJSObject());
  }

  bool operator()(const Completion::Return& ret) {
    Rooted<NativeObject*> obj(cx, newObject());
    RootedValue retval(cx, ret.value);
    if (!obj || !wrap(&retval) || !add(obj, cx->names().return_, retval)) {
      return false;
    }
    result.setObject(*obj);
    return true;
  }

  bool operator()(const Completion::Throw& thr) {
    Rooted<NativeObject*> obj(cx, newObject());
    RootedValue exc(cx, thr.exception);
    if (!obj || !wrap(&exc) || !add(obj, cx->names().throw_, exc)) {
      return false;
    }
    if (thr.stack) {
      RootedValue stack(cx, ObjectValue(*thr.stack));
      if (!wrapStack(&stack) || !add(obj, cx->names().stack, stack)) {
        return false;
      }
    }
    result.setObject(*obj);
    return true;
  }

  bool operator()(const Completion::Terminate& term) {
    result.setNull();
    return true;
  }

  bool operator()(const Completion::InitialYield& initialYield) {
    Rooted<NativeObject*> obj(cx, newObject());
    RootedValue gen(cx, ObjectValue(*initialYield.generatorObject));
    if (!obj || !wrap(&gen) || !add(obj, cx->names().return_, gen) ||
        !add(obj, cx->names().yield, TrueHandleValue) ||
        !add(obj, cx->names().initial, TrueHandleValue)) {
      return false;
    }
    result.setObject(*obj);
    return true;
  }

  bool operator()(const Completion::Yield& yield) {
    Rooted<NativeObject*> obj(cx, newObject());
    RootedValue iteratorResult(cx, yield.iteratorResult);
    if (!obj || !wrap(&iteratorResult) ||
        !add(obj, cx->names().return_, iteratorResult) ||
        !add(obj, cx->names().yield, TrueHandleValue)) {
      return false;
    }
    result.setObject(*obj);
    return true;
  }

  bool operator()(const Completion::Await& await) {
    Rooted<NativeObject*> obj(cx, newObject());
    RootedValue awaitee(cx, await.awaitee);
    if (!obj || !wrap(&awaitee) || !add(obj, cx->names().return_, awaitee) ||
        !add(obj, cx->names().await, TrueHandleValue)) {
      return false;
    }
    result.setObject(*obj);
    return true;
  }

 private:
  NativeObject* newObject() const { return NewPlainObject(cx); }

  bool add(Handle<NativeObject*> obj, PropertyName* name,
           HandleValue value) const {
    return NativeDefineDataProperty(cx, obj, name, value, JSPROP_ENUMERATE);
  }

  bool wrap(MutableHandleValue v) const {
    return dbg->wrapDebuggeeValue(cx, v);
  }

  // Saved stacks are wrapped for direct consumption by debugger code.
  bool wrapStack(MutableHandleValue stack) const {
    return cx->compartment()->wrap(cx, stack);
  }
};

bool Completion::buildCompletionValue(JSContext* cx, Debugger* dbg,
                                      MutableHandleValue result) const {
  return variant.match(BuildValueMatcher(cx, dbg, result));
}

void Completion::updateFromHookResult(ResumeMode resumeMode,
                                      HandleValue value) {
  switch (resumeMode) {
    case ResumeMode::Continue:
      // No change to how we'll resume.
      break;

    case ResumeMode::Throw:
      // Since this is a new exception, the stack for the old one may not apply.
      // If we extend resumption values to specify stacks, we could revisit
      // this.
      variant = Variant(Throw(value, nullptr));
      break;

    case ResumeMode::Terminate:
      variant = Variant(Terminate());
      break;

    case ResumeMode::Return:
      variant = Variant(Return(value));
      break;

    default:
      MOZ_CRASH("invalid resumeMode value");
  }
}

struct MOZ_STACK_CLASS Completion::ToResumeModeMatcher {
  MutableHandleValue value;
  MutableHandle<SavedFrame*> exnStack;
  ToResumeModeMatcher(MutableHandleValue value,
                      MutableHandle<SavedFrame*> exnStack)
      : value(value), exnStack(exnStack) {}

  ResumeMode operator()(const Return& ret) {
    value.set(ret.value);
    return ResumeMode::Return;
  }

  ResumeMode operator()(const Throw& thr) {
    value.set(thr.exception);
    exnStack.set(thr.stack);
    return ResumeMode::Throw;
  }

  ResumeMode operator()(const Terminate& term) {
    value.setUndefined();
    return ResumeMode::Terminate;
  }

  ResumeMode operator()(const InitialYield& initialYield) {
    value.setObject(*initialYield.generatorObject);
    return ResumeMode::Return;
  }

  ResumeMode operator()(const Yield& yield) {
    value.set(yield.iteratorResult);
    return ResumeMode::Return;
  }

  ResumeMode operator()(const Await& await) {
    value.set(await.awaitee);
    return ResumeMode::Return;
  }
};

void Completion::toResumeMode(ResumeMode& resumeMode, MutableHandleValue value,
                              MutableHandle<SavedFrame*> exnStack) const {
  resumeMode = variant.match(ToResumeModeMatcher(value, exnStack));
}

/*** Firing debugger hooks **************************************************/

static bool CallMethodIfPresent(JSContext* cx, HandleObject obj,
                                const char* name, size_t argc, Value* argv,
                                MutableHandleValue rval) {
  rval.setUndefined();
  JSAtom* atom = Atomize(cx, name, strlen(name));
  if (!atom) {
    return false;
  }

  RootedId id(cx, AtomToId(atom));
  RootedValue fval(cx);
  if (!GetProperty(cx, obj, obj, id, &fval)) {
    return false;
  }

  if (!IsCallable(fval)) {
    return true;
  }

  InvokeArgs args(cx);
  if (!args.init(cx, argc)) {
    return false;
  }

  for (size_t i = 0; i < argc; i++) {
    args[i].set(argv[i]);
  }

  rval.setObject(*obj);  // overwritten by successful Call
  return js::Call(cx, fval, rval, args, rval);
}

bool Debugger::fireDebuggerStatement(JSContext* cx, ResumeMode& resumeMode,
                                     MutableHandleValue vp) {
  RootedObject hook(cx, getHook(OnDebuggerStatement));
  MOZ_ASSERT(hook);
  MOZ_ASSERT(hook->isCallable());

  ScriptFrameIter iter(cx);
  RootedValue scriptFrame(cx);
  if (!getFrame(cx, iter, &scriptFrame)) {
    return false;
  }

  RootedValue fval(cx, ObjectValue(*hook));
  RootedValue rv(cx);
  bool ok = js::Call(cx, fval, object, scriptFrame, &rv);
  return processHandlerResult(cx, ok, rv, iter.abstractFramePtr(), iter.pc(),
                              resumeMode, vp);
}

bool Debugger::fireExceptionUnwind(JSContext* cx, HandleValue exc,
                                   ResumeMode& resumeMode,
                                   MutableHandleValue vp) {
  RootedObject hook(cx, getHook(OnExceptionUnwind));
  MOZ_ASSERT(hook);
  MOZ_ASSERT(hook->isCallable());

  RootedValue scriptFrame(cx);
  RootedValue wrappedExc(cx, exc);

  FrameIter iter(cx);
  if (!getFrame(cx, iter, &scriptFrame) ||
      !wrapDebuggeeValue(cx, &wrappedExc)) {
    return false;
  }

  RootedValue fval(cx, ObjectValue(*hook));
  RootedValue rv(cx);
  bool ok = js::Call(cx, fval, object, scriptFrame, wrappedExc, &rv);
  return processHandlerResult(cx, ok, rv, iter.abstractFramePtr(), iter.pc(),
                              resumeMode, vp);
}

bool Debugger::fireEnterFrame(JSContext* cx, ResumeMode& resumeMode,
                              MutableHandleValue vp) {
  RootedObject hook(cx, getHook(OnEnterFrame));
  MOZ_ASSERT(hook);
  MOZ_ASSERT(hook->isCallable());

  RootedValue scriptFrame(cx);

  FrameIter iter(cx);

#if DEBUG
  // Assert that the hook won't be able to re-enter the generator.
  if (iter.hasScript() && JSOp(*iter.pc()) == JSOp::AfterYield) {
    AutoRealm ar(cx, iter.script());
    auto* genObj = GetGeneratorObjectForFrame(cx, iter.abstractFramePtr());
    MOZ_ASSERT(genObj->isRunning());
  }
#endif

  if (!getFrame(cx, iter, &scriptFrame)) {
    return false;
  }

  RootedValue fval(cx, ObjectValue(*hook));
  RootedValue rv(cx);
  bool ok = js::Call(cx, fval, object, scriptFrame, &rv);

  return processHandlerResult(cx, ok, rv, iter.abstractFramePtr(), iter.pc(),
                              resumeMode, vp);
}

bool Debugger::fireNativeCall(JSContext* cx, const CallArgs& args,
                              CallReason reason, ResumeMode& resumeMode,
                              MutableHandleValue vp) {
  RootedObject hook(cx, getHook(OnNativeCall));
  MOZ_ASSERT(hook);
  MOZ_ASSERT(hook->isCallable());

  RootedValue fval(cx, ObjectValue(*hook));
  RootedValue calleeval(cx, args.calleev());
  if (!wrapDebuggeeValue(cx, &calleeval)) {
    return false;
  }

  JSAtom* reasonAtom = nullptr;
  switch (reason) {
    case CallReason::Call:
      reasonAtom = cx->names().call;
      break;
    case CallReason::CallContent:
      reasonAtom = cx->names().call;
      break;
    case CallReason::FunCall:
      reasonAtom = cx->names().call;
      break;
    case CallReason::Getter:
      reasonAtom = cx->names().get;
      break;
    case CallReason::Setter:
      reasonAtom = cx->names().set;
      break;
  }
  MOZ_ASSERT(AtomIsMarked(cx->zone(), reasonAtom));

  RootedValue reasonval(cx, StringValue(reasonAtom));

  bool ok = false;
  RootedValue rv(cx);
  if (inspectNativeCallArguments) {
    RootedValue thisVal(cx, args.thisv());
    // Ignore anything that may make wrapDebuggeeValue to throw
    if (thisVal.isMagic() && thisVal.whyMagic() != JS_MISSING_ARGUMENTS &&
        thisVal.whyMagic() != JS_UNINITIALIZED_LEXICAL) {
      thisVal.setMagic(JS_OPTIMIZED_OUT);
    }
    if (!wrapDebuggeeValue(cx, &thisVal)) {
      return false;
    }

    unsigned arrsize = args.length();
    Rooted<ArrayObject*> arrobj(cx, NewDenseFullyAllocatedArray(cx, arrsize));
    if (!arrobj) {
      return false;
    }
    arrobj->ensureDenseInitializedLength(0, arrsize);
    for (unsigned i = 0; i < arrsize; i++) {
      RootedValue v(cx, args.get(i));
      if (!wrapDebuggeeValue(cx, &v)) {
        return false;
      }
      arrobj->setDenseElement(i, v);
    }
    RootedValue arrayval(cx, ObjectValue(*arrobj));
    if (!wrapDebuggeeValue(cx, &arrayval)) {
      return false;
    }

    FixedInvokeArgs<4> iargs(cx);
    iargs[0].set(calleeval);
    iargs[1].set(reasonval);
    iargs[2].set(thisVal);
    iargs[3].set(arrayval);

    RootedValue thisv(cx, ObjectOrNullValue(object));
    ok = js::Call(cx, fval, thisv, iargs, &rv);
  } else {
    ok = js::Call(cx, fval, object, calleeval, reasonval, &rv);
  }

  return processHandlerResult(cx, ok, rv, NullFramePtr(), nullptr, resumeMode,
                              vp);
}

bool Debugger::fireNewScript(JSContext* cx,
                             Handle<DebuggerScriptReferent> scriptReferent) {
  RootedObject hook(cx, getHook(OnNewScript));
  MOZ_ASSERT(hook);
  MOZ_ASSERT(hook->isCallable());

  JSObject* dsobj = wrapVariantReferent(cx, scriptReferent);
  if (!dsobj) {
    return false;
  }

  RootedValue fval(cx, ObjectValue(*hook));
  RootedValue dsval(cx, ObjectValue(*dsobj));
  RootedValue rv(cx);
  return js::Call(cx, fval, object, dsval, &rv) || handleUncaughtException(cx);
}

bool Debugger::fireOnGarbageCollectionHook(
    JSContext* cx, const JS::dbg::GarbageCollectionEvent::Ptr& gcData) {
  MOZ_ASSERT(observedGC(gcData->majorGCNumber()));
  observedGCs.remove(gcData->majorGCNumber());

  RootedObject hook(cx, getHook(OnGarbageCollection));
  MOZ_ASSERT(hook);
  MOZ_ASSERT(hook->isCallable());

  JSObject* dataObj = gcData->toJSObject(cx);
  if (!dataObj) {
    return false;
  }

  RootedValue fval(cx, ObjectValue(*hook));
  RootedValue dataVal(cx, ObjectValue(*dataObj));
  RootedValue rv(cx);
  return js::Call(cx, fval, object, dataVal, &rv) ||
         handleUncaughtException(cx);
}

template <typename HookIsEnabledFun /* bool (Debugger*) */,
          typename FireHookFun /* bool (Debugger*) */>
/* static */
void Debugger::dispatchQuietHook(JSContext* cx, HookIsEnabledFun hookIsEnabled,
                                 FireHookFun fireHook) {
  DebuggerList<HookIsEnabledFun> debuggerList(cx, hookIsEnabled);

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

  debuggerList.dispatchQuietHook(cx, fireHook);
}

template <typename HookIsEnabledFun /* bool (Debugger*) */, typename FireHookFun /* bool (Debugger*, ResumeMode&, MutableHandleValue vp) */>
/* static */
bool Debugger::dispatchResumptionHook(JSContext* cx, AbstractFramePtr frame,
                                      HookIsEnabledFun hookIsEnabled,
                                      FireHookFun fireHook) {
  DebuggerList<HookIsEnabledFun> debuggerList(cx, hookIsEnabled);

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

  return debuggerList.dispatchResumptionHook(cx, frame, fireHook);
}

// Maximum length for source URLs that can be remembered.
static const size_t SourceURLMaxLength = 1024;

// Maximum number of source URLs that can be remembered in a realm.
static const size_t SourceURLRealmLimit = 100;

static bool RememberSourceURL(JSContext* cx, HandleScript script) {
  cx->check(script);

  // Sources introduced dynamically are not remembered.
  if (script->sourceObject()->unwrappedIntroductionScript()) {
    return true;
  }

  const char* filename = script->filename();
  if (!filename ||
      strnlen(filename, SourceURLMaxLength + 1) > SourceURLMaxLength) {
    return true;
  }

  Rooted<ArrayObject*> holder(cx, script->global().getSourceURLsHolder());
  if (!holder) {
    holder = NewDenseEmptyArray(cx);
    if (!holder) {
      return false;
    }
    script->global().setSourceURLsHolder(holder);
  }

  if (holder->length() >= SourceURLRealmLimit) {
    return true;
  }

  RootedString filenameString(cx,
                              AtomizeUTF8Chars(cx, filename, strlen(filename)));
  if (!filenameString) {
    return false;
  }

  // The source URLs holder never escapes to script, so we can treat it as a
  // newborn array for the purpose of adding elements.
  return NewbornArrayPush(cx, holder, StringValue(filenameString));
}

void DebugAPI::onNewScript(JSContext* cx, HandleScript script) {
  if (!script->realm()->isDebuggee()) {
    // Remember the URLs associated with scripts in non-system realms,
    // in case the debugger is attached later.
    if (!script->realm()->isSystem()) {
      if (!RememberSourceURL(cx, script)) {
        cx->clearPendingException();
      }
    }
    return;
  }

  Debugger::dispatchQuietHook(
      cx,
      [script](Debugger* dbg) -> bool {
        return dbg->observesNewScript() && dbg->observesScript(script);
      },
      [&](Debugger* dbg) -> bool {
        BaseScript* base = script.get();
        Rooted<DebuggerScriptReferent> scriptReferent(cx, base);
        return dbg->fireNewScript(cx, scriptReferent);
      });
}

/* static */
void DebugAPI::onSuspendWasmFrame(JSContext* cx, wasm::DebugFrame* debugFrame) {
  AbstractFramePtr frame = AbstractFramePtr(debugFrame);
  JS::AutoAssertNoGC nogc;
  for (Realm::DebuggerVectorEntry& entry : frame.global()->getDebuggers(nogc)) {
    Debugger* dbg = entry.dbg;
    if (Debugger::FrameMap::Ptr p = dbg->frames.lookup(frame)) {
      DebuggerFrame* frameObj = p->value();
      frameObj->suspendWasmFrame(cx->gcContext());
    }
  }
}

/* static */
void DebugAPI::onResumeWasmFrame(JSContext* cx, const FrameIter& iter) {
  AbstractFramePtr frame = iter.abstractFramePtr();
  MOZ_RELEASE_ASSERT(frame.isWasmDebugFrame());
  JS::AutoAssertNoGC nogc;
  for (Realm::DebuggerVectorEntry& entry : frame.global()->getDebuggers(nogc)) {
    Debugger* dbg = entry.dbg;
    if (Debugger::FrameMap::Ptr p = dbg->frames.lookup(frame)) {
      DebuggerFrame* frameObj = p->value();
      AutoEnterOOMUnsafeRegion oomUnsafe;
      if (!frameObj->resume(iter)) {
        oomUnsafe.crash("DebugAPI::onResumeWasmFrame");
      }
    }
  }
}

void DebugAPI::slowPathOnNewWasmInstance(
    JSContext* cx, Handle<WasmInstanceObject*> wasmInstance) {
  Debugger::dispatchQuietHook(
      cx,
      [wasmInstance](Debugger* dbg) -> bool {
        return dbg->observesNewScript() &&
               dbg->observesGlobal(&wasmInstance->global());
      },
      [&](Debugger* dbg) -> bool {
        Rooted<DebuggerScriptReferent> scriptReferent(cx, wasmInstance.get());
        return dbg->fireNewScript(cx, scriptReferent);
      });
}

/* static */
bool DebugAPI::onTrap(JSContext* cx) {
  FrameIter iter(cx);
  JS::AutoSaveExceptionState savedExc(cx);
  Rooted<GlobalObject*> global(cx);
  BreakpointSite* site;
  bool isJS;       // true when iter.hasScript(), false when iter.isWasm()
  jsbytecode* pc;  // valid when isJS == true
  uint32_t bytecodeOffset;  // valid when isJS == false
  if (iter.hasScript()) {
    RootedScript script(cx, iter.script());
    MOZ_ASSERT(script->isDebuggee());
    global.set(&script->global());
    isJS = true;
    pc = iter.pc();
    bytecodeOffset = 0;
    site = DebugScript::getBreakpointSite(script, pc);
  } else {
    MOZ_ASSERT(iter.isWasm());
    global.set(&iter.wasmInstance()->object()->global());
    isJS = false;
    pc = nullptr;
    bytecodeOffset = iter.wasmBytecodeOffset();
    site = iter.wasmInstance()->debug().getBreakpointSite(bytecodeOffset);
  }

  // Build list of breakpoint handlers.
  //
  // This does not need to be rooted: since the JSScript/WasmInstance is on the
  // stack, the Breakpoints will not be GC'd. However, they may be deleted, and
  // we check for that case below.
  Vector<Breakpoint*> triggered(cx);
  for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
    if (!triggered.append(bp)) {
      return false;
    }
  }

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

  if (triggered.length() > 0) {
    // 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;
    }

    for (Breakpoint* bp : triggered) {
      // Handlers can clear breakpoints. Check that bp still exists.
      if (!site || !site->hasBreakpoint(bp)) {
        continue;
      }

      // We have to check whether dbg is debugging this global here: a
      // breakpoint handler can disable other Debuggers or remove debuggees.
      Debugger* dbg = bp->debugger;
      if (dbg->debuggees.has(global)) {
        EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);

        bool result = dbg->enterDebuggerHook(cx, [&]() -> bool {
          RootedValue scriptFrame(cx);
          if (!dbg->getFrame(cx, iter, &scriptFrame)) {
            return false;
          }

          // Re-wrap the breakpoint's handler for the Debugger's compartment.
          // When the handler and the Debugger are in the same compartment (the
          // usual case), this actually unwraps it, but there's no requirement
          // that they be in the same compartment, so we can't be sure.
          Rooted<JSObject*> handler(cx, bp->handler);
          if (!cx->compartment()->wrap(cx, &handler)) {
            return false;
          }

          RootedValue rv(cx);
          bool ok = CallMethodIfPresent(cx, handler, "hit", 1,
                                        scriptFrame.address(), &rv);

          return dbg->processHandlerResult(cx, ok, rv, iter.abstractFramePtr(),
                                           iter.pc(), resumeMode, &rval);
        });
        adjqi.runJobs();

        if (!result) {
          return false;
        }

        // Calling JS code invalidates site. Reload it.
        if (isJS) {
          site = DebugScript::getBreakpointSite(iter.script(), pc);
        } else {
          site = iter.wasmInstance()->debug().getBreakpointSite(bytecodeOffset);
        }
      }
    }
  }

  if (!ApplyFrameResumeMode(cx, iter.abstractFramePtr(), resumeMode, rval)) {
    savedExc.drop();
    return false;
  }
  return true;
}

/* static */
bool DebugAPI::onSingleStep(JSContext* cx) {
  FrameIter iter(cx);

  // We may be stepping over a JSOp::Exception, that pushes the context's
  // pending exception for a 'catch' clause to handle. Don't let the onStep
  // handlers mess with that (other than by returning a resumption value).
  JS::AutoSaveExceptionState savedExc(cx);

  // Build list of Debugger.Frame instances referring to this frame with
  // onStep handlers.
  Rooted<Debugger::DebuggerFrameVector> frames(cx);
  if (!Debugger::getDebuggerFrames(iter.abstractFramePtr(), &frames)) {
    ReportOutOfMemory(cx);
    return false;
  }

#ifdef DEBUG
  // Validate the single-step count on this frame's script, to ensure that
  // we're not receiving traps we didn't ask for. Even when frames is
  // non-empty (and thus we know this trap was requested), do the check
  // anyway, to make sure the count has the correct non-zero value.
  //
  // The converse --- ensuring that we do receive traps when we should --- can
  // be done with unit tests.
  if (iter.hasScript()) {
    uint32_t liveStepperCount = 0;
    uint32_t suspendedStepperCount = 0;
    JSScript* trappingScript = iter.script();
    JS::AutoAssertNoGC nogc;
    for (Realm::DebuggerVectorEntry& entry : cx->global()->getDebuggers(nogc)) {
      Debugger* dbg = entry.dbg;
      for (Debugger::FrameMap::Range r = dbg->frames.all(); !r.empty();
           r.popFront()) {
        AbstractFramePtr frame = r.front().key();
        NativeObject* frameobj = r.front().value();
        if (frame.isWasmDebugFrame()) {
          continue;
        }
        if (frame.script() == trappingScript &&
            !frameobj->getReservedSlot(DebuggerFrame::ONSTEP_HANDLER_SLOT)
                 .isUndefined()) {
          liveStepperCount++;
        }
      }

      // Also count hooks set on suspended generator frames.
      for (Debugger::GeneratorWeakMap::Range r = dbg->generatorFrames.all();
           !r.empty(); r.popFront()) {
        AbstractGeneratorObject& genObj = *r.front().key();
        DebuggerFrame& frameObj = *r.front().value();
        MOZ_ASSERT(&frameObj.unwrappedGenerator() == &genObj);

        // Live Debugger.Frames were already counted in dbg->frames loop.
        if (frameObj.isOnStack()) {
          continue;
        }

        // A closed generator no longer has a callee so it will not be able to
        // compare with the trappingScript.
        if (genObj.isClosed()) {
          continue;
        }

        // If a frame isn't live, but it has an entry in generatorFrames,
        // it had better be suspended.
        MOZ_ASSERT(genObj.isSuspended());

        if (genObj.callee().hasBaseScript() &&
            genObj.callee().baseScript() == trappingScript &&
            !frameObj.getReservedSlot(DebuggerFrame::ONSTEP_HANDLER_SLOT)
                 .isUndefined()) {
          suspendedStepperCount++;
        }
      }
    }

    MOZ_ASSERT(liveStepperCount + suspendedStepperCount ==
               DebugScript::getStepperCount(trappingScript));
  }
#endif

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

  if (frames.length() > 0) {
    // 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;
    }

    // Call onStep for frames that have the handler set.
    for (size_t i = 0; i < frames.length(); i++) {
      Handle<DebuggerFrame*> frame = frames[i];
      OnStepHandler* handler = frame->onStepHandler();
      if (!handler) {
        continue;
      }

      Debugger* dbg = frame->owner();
      EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);

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

        bool success = handler->onStep(cx, frame, nextResumeMode, &nextValue);
        return dbg->processParsedHandlerResult(
            cx, iter.abstractFramePtr(), iter.pc(), success, nextResumeMode,
            nextValue, resumeMode, &rval);
      });
      adjqi.runJobs();

      if (!result) {
        return false;
      }
    }
  }

  if (!ApplyFrameResumeMode(cx, iter.abstractFramePtr(), resumeMode, rval)) {
    savedExc.drop();
    return false;
  }
  return true;
}

bool Debugger::fireNewGlobalObject(JSContext* cx,
                                   Handle<GlobalObject*> global) {
  RootedObject hook(cx, getHook(OnNewGlobalObject));
  MOZ_ASSERT(hook);
  MOZ_ASSERT(hook->isCallable());

  RootedValue wrappedGlobal(cx, ObjectValue(*global));
  if (!wrapDebuggeeValue(cx, &wrappedGlobal)) {
    return false;
  }

  // onNewGlobalObject is infallible, and thus is only allowed to return
  // undefined as a resumption value. If it returns anything else, we throw.
  // And if that happens, or if the hook itself throws, we invoke the
  // uncaughtExceptionHook so that we never leave an exception pending on the
  // cx. This allows JS_NewGlobalObject to avoid handling failures from
  // debugger hooks.
  RootedValue rv(cx);
  RootedValue fval(cx, ObjectValue(*hook));
  bool ok = js::Call(cx, fval, object, wrappedGlobal, &rv);
  if (ok && !rv.isUndefined()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED);
    ok = false;
  }

  return ok || handleUncaughtException(cx);
}

void DebugAPI::slowPathOnNewGlobalObject(JSContext* cx,
                                         Handle<GlobalObject*> global) {
  MOZ_ASSERT(!cx->runtime()->onNewGlobalObjectWatchers().isEmpty());
  if (global->realm()->creationOptions().invisibleToDebugger()) {
    return;
  }

  // Make a copy of the runtime's onNewGlobalObjectWatchers before running the
  // handlers. Since one Debugger's handler can disable another's, the list
  // can be mutated while we're walking it.
  RootedObjectVector watchers(cx);
  for (auto& dbg : cx->runtime()->onNewGlobalObjectWatchers()) {
    MOZ_ASSERT(dbg.observesNewGlobalObject());
    JSObject* obj = dbg.object;
    JS::ExposeObjectToActiveJS(obj);
    if (!watchers.append(obj)) {
      if (cx->isExceptionPending()) {
        cx->clearPendingException();
      }
      return;
    }
  }

  // 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)) {
    cx->clearPendingException();
    return;
  }

  for (size_t i = 0; i < watchers.length(); i++) {
    Debugger* dbg = Debugger::fromJSObject(watchers[i]);
    EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);

    if (dbg->observesNewGlobalObject()) {
      bool result = dbg->enterDebuggerHook(
          cx, [&]() -> bool { return dbg->fireNewGlobalObject(cx, global); });
      adjqi.runJobs();

      if (!result) {
        // Like other quiet hooks using dispatchQuietHook, this hook
        // silently ignores all errors that propagate out of it and aren't
        // already handled by the hook error reporting.
        cx->clearPendingException();
        break;
      }
    }
  }
  MOZ_ASSERT(!cx->isExceptionPending());
}

/* static */
void DebugAPI::slowPathOnGeneratorClosed(JSContext* cx,
                                         AbstractGeneratorObject* genObj) {
  JS::AutoAssertNoGC nogc;
  for (Realm::DebuggerVectorEntry& entry : cx->global()->getDebuggers(nogc)) {
    Debugger* dbg = entry.dbg;
    if (Debugger::GeneratorWeakMap::Ptr frameEntry =
            dbg->generatorFrames.lookup(genObj)) {
      DebuggerFrame* frameObj = frameEntry->value();
      frameObj->onGeneratorClosed(cx->gcContext());
    }
  }
}

/* static */
void DebugAPI::slowPathNotifyParticipatesInGC(uint64_t majorGCNumber,
                                              Realm::DebuggerVector& dbgs,
                                              const JS::AutoRequireNoGC& nogc) {
  for (Realm::DebuggerVector::Range r = dbgs.all(); !r.empty(); r.popFront()) {
    if (!r.front().dbg.unbarrieredGet()->debuggeeIsBeingCollected(
            majorGCNumber)) {
#ifdef DEBUG
      fprintf(stderr,
              "OOM while notifying observing Debuggers of a GC: The "
              "onGarbageCollection\n"
              "hook will not be fired for this GC for some Debuggers!\n");
#endif
      return;
    }
  }
}

/* static */
Maybe<double> DebugAPI::allocationSamplingProbability(GlobalObject* global) {
  JS::AutoAssertNoGC nogc;
  Realm::DebuggerVector& dbgs = global->getDebuggers(nogc);
  if (dbgs.empty()) {
    return Nothing();
  }

  DebugOnly<Realm::DebuggerVectorEntry*> begin = dbgs.begin();

  double probability = 0;
  bool foundAnyDebuggers = false;
  for (auto p = dbgs.begin(); p < dbgs.end(); p++) {
    // The set of debuggers had better not change while we're iterating,
    // such that the vector gets reallocated.
    MOZ_ASSERT(dbgs.begin() == begin);
    // Use unbarrieredGet() to prevent triggering read barrier while collecting,
    // this is safe as long as dbgp does not escape.
    Debugger* dbgp = p->dbg.unbarrieredGet();

    if (dbgp->trackingAllocationSites) {
      foundAnyDebuggers = true;
      probability = std::max(dbgp->allocationSamplingProbability, probability);
    }
  }

  return foundAnyDebuggers ? Some(probability) : Nothing();
}

/* static */
bool DebugAPI::slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj,
                                           Handle<SavedFrame*> frame,
                                           mozilla::TimeStamp when,
                                           Realm::DebuggerVector& dbgs,
                                           const gc::AutoSuppressGC& nogc) {
  MOZ_ASSERT(!dbgs.empty());
  mozilla::DebugOnly<Realm::DebuggerVectorEntry*> begin = dbgs.begin();

  // GC is suppressed so we can iterate over the debuggers; appendAllocationSite
  // calls Compartment::wrap, and thus could GC.

  for (auto p = dbgs.begin(); p < dbgs.end(); p++) {
    // The set of debuggers had better not change while we're iterating,
    // such that the vector gets reallocated.
    MOZ_ASSERT(dbgs.begin() == begin);

    if (p->dbg->trackingAllocationSites &&
        !p->dbg->appendAllocationSite(cx, obj, frame, when)) {
      return false;
    }
  }

  return true;
}

bool Debugger::isDebuggeeUnbarriered(const Realm* realm) const {
  MOZ_ASSERT(realm);
  return realm->isDebuggee() &&
         debuggees.has(realm->unsafeUnbarrieredMaybeGlobal());
}

bool Debugger::appendAllocationSite(JSContext* cx, HandleObject obj,
                                    Handle<SavedFrame*> frame,
                                    mozilla::TimeStamp when) {
  MOZ_ASSERT(trackingAllocationSites);

  AutoRealm ar(cx, object);
  RootedObject wrappedFrame(cx, frame);
  if (!cx->compartment()->wrap(cx, &wrappedFrame)) {
    return false;
  }

  auto className = obj->getClass()->name;
  auto size =
      JS::ubi::Node(obj.get()).size(cx->runtime()->debuggerMallocSizeOf);
  auto inNursery = gc::IsInsideNursery(obj);

  if (!allocationsLog.emplaceBack(wrappedFrame, when, className, size,
                                  inNursery)) {
    ReportOutOfMemory(cx);
    return false;
  }

  if (allocationsLog.length() > maxAllocationsLogLength) {
    allocationsLog.popFront();
    MOZ_ASSERT(allocationsLog.length() == maxAllocationsLogLength);
    allocationsLogOverflowed = true;
  }

  return true;
}

bool Debugger::firePromiseHook(JSContext* cx, Hook hook, HandleObject promise) {
  MOZ_ASSERT(hook == OnNewPromise || hook == OnPromiseSettled);

  RootedObject hookObj(cx, getHook(hook));
  MOZ_ASSERT(hookObj);
  MOZ_ASSERT(hookObj->isCallable());

  RootedValue dbgObj(cx, ObjectValue(*promise));
  if (!wrapDebuggeeValue(cx, &dbgObj)) {
    return false;
  }

  // Like onNewGlobalObject, the Promise hooks are infallible and the comments
  // in |Debugger::fireNewGlobalObject| apply here as well.
  RootedValue fval(cx, ObjectValue(*hookObj));
  RootedValue rv(cx);
  bool ok = js::Call(cx, fval, object, dbgObj, &rv);
  if (ok && !rv.isUndefined()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED);
    ok = false;
  }

  return ok || handleUncaughtException(cx);
}

/* static */
void Debugger::slowPathPromiseHook(JSContext* cx, Hook hook,
                                   Handle<PromiseObject*> promise) {
  MOZ_ASSERT(hook == OnNewPromise || hook == OnPromiseSettled);

  if (hook == OnPromiseSettled) {
    // We should be in the right compartment, but for simplicity always enter
    // the promise's realm below.
    cx->check(promise);
  }

  AutoRealm ar(cx, promise);

  Debugger::dispatchQuietHook(
      cx, [hook](Debugger* dbg) -> bool { return dbg->getHook(hook); },
      [&](Debugger* dbg) -> bool {
        return dbg->firePromiseHook(cx, hook, promise);
      });
}

/* static */
void DebugAPI::slowPathOnNewPromise(JSContext* cx,
                                    Handle<PromiseObject*> promise) {
  Debugger::slowPathPromiseHook(cx, Debugger::OnNewPromise, promise);
}

/* static */
void DebugAPI::slowPathOnPromiseSettled(JSContext* cx,
                                        Handle<PromiseObject*> promise) {
  Debugger::slowPathPromiseHook(cx, Debugger::OnPromiseSettled, promise);
}

/*** Debugger code invalidation for observing execution *********************/

class MOZ_RAII ExecutionObservableRealms
    : public DebugAPI::ExecutionObservableSet {
  HashSet<Realm*> realms_;
  HashSet<Zone*> zones_;

 public:
  explicit ExecutionObservableRealms(JSContext* cx) : realms_(cx), zones_(cx) {}

  bool add(Realm* realm) {
    return realms_.put(realm) && zones_.put(realm->zone());
  }

  using RealmRange = HashSet<Realm*>::Range;
  const HashSet<Realm*>* realms() const { return &realms_; }

  const HashSet<Zone*>* zones() const override { return &zones_; }
  bool shouldRecompileOrInvalidate(JSScript* script) const override {
    return script->hasBaselineScript() && realms_.has(script->realm());
  }
  bool shouldMarkAsDebuggee(FrameIter& iter) const override {
    // AbstractFramePtr can't refer to non-remateralized Ion frames or
    // non-debuggee wasm frames, so if iter refers to one such, we know we
    // don't match.
    return iter.hasUsableAbstractFramePtr() && realms_.has(iter.realm());
  }
};

// Given a particular AbstractFramePtr F that has become observable, this
// represents the stack frames that need to be bailed out or marked as
// debuggees, and the scripts that need to be recompiled, taking inlining into
// account.
class MOZ_RAII ExecutionObservableFrame
    : public DebugAPI::ExecutionObservableSet {
  AbstractFramePtr frame_;

 public:
  explicit ExecutionObservableFrame(AbstractFramePtr frame) : frame_(frame) {}

  Zone* singleZone() const override {
    // We never inline across realms, let alone across zones, so
    // frames_'s script's zone is the only one of interest.
    return frame_.script()->zone();
  }

  JSScript* singleScriptForZoneInvalidation() const override {
    MOZ_CRASH(
        "ExecutionObservableFrame shouldn't need zone-wide invalidation.");
    return nullptr;
  }

  bool shouldRecompileOrInvalidate(JSScript* script) const override {
    // Normally, *this represents exactly one script: the one frame_ is
    // running.
    //
    // However, debug-mode OSR uses *this for both invalidating Ion frames,
    // and recompiling the Baseline scripts that those Ion frames will bail
    // out into. Suppose frame_ is an inline frame, executing a copy of its
    // JSScript, S_inner, that has been inlined into the IonScript of some
    // other JSScript, S_outer. We must match S_outer, to decide which Ion
    // frame to invalidate; and we must match S_inner, to decide which
    // Baseline script to recompile.
    //
    // Note that this does not, by design, invalidate *all* inliners of
    // frame_.script(), as only frame_ is made observable, not
    // frame_.script().
    if (!script->hasBaselineScript()) {
      return false;
    }

    if (frame_.hasScript() && script == frame_.script()) {
      return true;
    }

    return frame_.isRematerializedFrame() &&
           script == frame_.asRematerializedFrame()->outerScript();
  }

  bool shouldMarkAsDebuggee(FrameIter& iter) const override {
    // AbstractFramePtr can't refer to non-remateralized Ion frames or
    // non-debuggee wasm frames, so if iter refers to one such, we know we
    // don't match.
    //
    // We never use this 'has' overload for frame invalidation, only for
    // frame debuggee marking; so this overload doesn't need a parallel to
    // the just-so inlining logic above.
    return iter.hasUsableAbstractFramePtr() &&
           iter.abstractFramePtr() == frame_;
  }
};

class MOZ_RAII ExecutionObservableScript
    : public DebugAPI::ExecutionObservableSet {
  RootedScript script_;

 public:
  ExecutionObservableScript(JSContext* cx, JSScript* script)
      : script_(cx, script) {}

  Zone* singleZone() const override { return script_->zone(); }
  JSScript* singleScriptForZoneInvalidation() const override { return script_; }
  bool shouldRecompileOrInvalidate(JSScript* script) const override {
    return script->hasBaselineScript() && script == script_;
  }
  bool shouldMarkAsDebuggee(FrameIter& iter) const override {
    // AbstractFramePtr can't refer to non-remateralized Ion frames, and
    // while a non-rematerialized Ion frame may indeed be running script_,
    // we cannot mark them as debuggees until they bail out.
    //
    // Upon bailing out, any newly constructed Baseline frames that came
    // from Ion frames with scripts that are isDebuggee() is marked as
    // debuggee. This is correct in that the only other way a frame may be
    // marked as debuggee is via Debugger.Frame reflection, which would
    // have rematerialized any Ion frames.
    //
    // Also AbstractFramePtr can't refer to non-debuggee wasm frames, so if
    // iter refers to one such, we know we don't match.
    return iter.hasUsableAbstractFramePtr() && !iter.isWasm() &&
           iter.abstractFramePtr().script() == script_;
  }
};

/* static */
bool Debugger::updateExecutionObservabilityOfFrames(
    JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
    IsObserving observing) {
  AutoSuppressProfilerSampling suppressProfilerSampling(cx);

  if (!jit::RecompileOnStackBaselineScriptsForDebugMode(cx, obs, observing)) {
    return false;
  }

  AbstractFramePtr oldestEnabledFrame;
  for (AllFramesIter iter(cx); !iter.done(); ++iter) {
    if (obs.shouldMarkAsDebuggee(iter)) {
      if (observing) {
        if (!iter.abstractFramePtr().isDebuggee()) {
          oldestEnabledFrame = iter.abstractFramePtr();
          oldestEnabledFrame.setIsDebuggee();
        }
        if (iter.abstractFramePtr().isWasmDebugFrame()) {
          iter.abstractFramePtr().asWasmDebugFrame()->observe(cx);
        }
      } else {
#ifdef DEBUG
        // Debugger.Frame lifetimes are managed by the debug epilogue,
        // so in general it's unsafe to unmark a frame if it has a
        // Debugger.Frame associated with it.
        MOZ_ASSERT(!DebugAPI::inFrameMaps(iter.abstractFramePtr()));
#endif
        iter.abstractFramePtr().unsetIsDebuggee();
      }
    }
  }

  // See comment in unsetPrevUpToDateUntil.
  if (oldestEnabledFrame) {
    AutoRealm ar(cx, oldestEnabledFrame.environmentChain());
    DebugEnvironments::unsetPrevUpToDateUntil(cx, oldestEnabledFrame);
  }

  return true;
}

static inline void MarkJitScriptActiveIfObservable(
    JSScript* script, const DebugAPI::ExecutionObservableSet& obs) {
  if (obs.shouldRecompileOrInvalidate(script)) {
    script->jitScript()->icScript()->setActive();
  }
}

static bool AppendAndInvalidateScript(JSContext* cx, Zone* zone,
                                      JSScript* script,
                                      jit::RecompileInfoVector& invalid,
                                      Vector<JSScript*>& scripts) {
  // Enter the script's realm as AddPendingInvalidation attempts to
  // cancel off-thread compilations, whose books are kept on the
  // script's realm.
  MOZ_ASSERT(script->zone() == zone);
  AutoRealm ar(cx, script);
  AddPendingInvalidation(invalid, script);
  return scripts.append(script);
}

static bool UpdateExecutionObservabilityOfScriptsInZone(
    JSContext* cx, Zone* zone, const DebugAPI::ExecutionObservableSet& obs,
    Debugger::IsObserving observing) {
  using namespace js::jit;

  AutoSuppressProfilerSampling suppressProfilerSampling(cx);

  CancelOffThreadBaselineCompile(zone);

  JS::GCContext* gcx = cx->gcContext();

  Vector<JSScript*> scripts(cx);

  // Iterate through observable scripts, invalidating their Ion scripts and
  // appending them to a vector for discarding their baseline scripts later.
  {
    RecompileInfoVector invalid;
    if (JSScript* script = obs.singleScriptForZoneInvalidation()) {
      if (obs.shouldRecompileOrInvalidate(script)) {
        if (!AppendAndInvalidateScript(cx, zone, script, invalid, scripts)) {
          return false;
        }
      }
    } else {
      for (auto base = zone->cellIter<BaseScript>(); !base.done();
           base.next()) {
        if (!base->hasJitScript()) {
          continue;
        }
        JSScript* script = base->asJSScript();
        if (obs.shouldRecompileOrInvalidate(script)) {
          if (!AppendAndInvalidateScript(cx, zone, script, invalid, scripts)) {
            return false;
          }
        }
      }
    }
    Invalidate(cx, invalid);
  }

  for (size_t i = 0; i < scripts.length(); i++) {
    MOZ_ASSERT(!scripts[i]->jitScript()->icScript()->active());
  }

  // Code below this point must be infallible to ensure the active bit of
  // BaselineScripts is in a consistent state.
  //
  // Mark active baseline scripts in the observable set so that they don't
  // get discarded. They will be recompiled.
  for (JitActivationIterator actIter(cx); !actIter.done(); ++actIter) {
    if (actIter->compartment()->zone() != zone) {
      continue;
    }

    for (OnlyJSJitFrameIter iter(actIter); !iter.done(); ++iter) {
      const JSJitFrameIter& frame = iter.frame();
      switch (frame.type()) {
        case FrameType::BaselineJS:
          MarkJitScriptActiveIfObservable(frame.script(), obs);
          break;
        case FrameType::IonJS:
          MarkJitScriptActiveIfObservable(frame.script(), obs);
          for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more();
               ++inlineIter) {
            MarkJitScriptActiveIfObservable(inlineIter.script(), obs);
          }
          break;
        default:;
      }
    }
  }

  // Iterate through the scripts again and finish discarding
  // BaselineScripts. This must be done as a separate phase as we can only
  // discard the BaselineScript on scripts that have no IonScript.
  for (size_t i = 0; i < scripts.length(); i++) {
    MOZ_ASSERT_IF(scripts[i]->isDebuggee(), observing);
    if (!scripts[i]->jitScript()->icScript()->active()) {
      FinishDiscardBaselineScript(gcx, scripts[i]);
    }
    scripts[i]->jitScript()->icScript()->resetActive();
  }

  // Iterate through all wasm instances to find ones that need to be updated.
  for (RealmsInZoneIter r(zone); !r.done(); r.next()) {
    for (wasm::Instance* instance : r->wasm.instances()) {
      if (!instance->debugEnabled()) {
        continue;
      }

      bool enableTrap = observing == Debugger::Observing;
      instance->debug().ensureEnterFrameTrapsState(cx, instance, enableTrap);
    }
  }

  return true;
}

/* static */
bool Debugger::updateExecutionObservabilityOfScripts(
    JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
    IsObserving observing) {
  if (Zone* zone = obs.singleZone()) {
    return UpdateExecutionObservabilityOfScriptsInZone(cx, zone, obs,
                                                       observing);
  }

  using ZoneRange = DebugAPI::ExecutionObservableSet::ZoneRange;
  for (ZoneRange r = obs.zones()->all(); !r.empty(); r.popFront()) {
    if (!UpdateExecutionObservabilityOfScriptsInZone(cx, r.front(), obs,
                                                     observing)) {
      return false;
    }
  }

  return true;
}

template <typename FrameFn>
/* static */
void Debugger::forEachOnStackDebuggerFrame(AbstractFramePtr frame,
                                           const JS::AutoRequireNoGC& nogc,
                                           FrameFn fn) {
  for (Realm::DebuggerVectorEntry& entry : frame.global()->getDebuggers(nogc)) {
    Debugger* dbg = entry.dbg;
    if (FrameMap::Ptr frameEntry = dbg->frames.lookup(frame)) {
      fn(dbg, frameEntry->value());
    }
  }
}

template <typename FrameFn>
/* static */
void Debugger::forEachOnStackOrSuspendedDebuggerFrame(
    JSContext* cx, AbstractFramePtr frame, const JS::AutoRequireNoGC& nogc,
    FrameFn fn) {
  Rooted<AbstractGeneratorObject*> genObj(
      cx, frame.isGeneratorFrame() ? GetGeneratorObjectForFrame(cx, frame)
                                   : nullptr);

  for (Realm::DebuggerVectorEntry& entry : frame.global()->getDebuggers(nogc)) {
    Debugger* dbg = entry.dbg;

    DebuggerFrame* frameObj = nullptr;
    if (FrameMap::Ptr frameEntry = dbg->frames.lookup(frame)) {
      frameObj = frameEntry->value();
    } else if (GeneratorWeakMap::Ptr frameEntry =
                   dbg->generatorFrames.lookup(genObj)) {
      frameObj = frameEntry->value();
    }

    if (frameObj) {
      fn(dbg, frameObj);
    }
  }
}

/* static */
bool Debugger::getDebuggerFrames(AbstractFramePtr frame,
                                 MutableHandle<DebuggerFrameVector> frames) {
  bool hadOOM = false;
  JS::AutoAssertNoGC nogc;
  forEachOnStackDebuggerFrame(frame, nogc,
                              [&](Debugger*, DebuggerFrame* frameobj) {
                                if (!hadOOM && !frames.append(frameobj)) {
                                  hadOOM = true;
                                }
                              });
  return !hadOOM;
}

/* static */
bool Debugger::updateExecutionObservability(
    JSContext* cx, DebugAPI::ExecutionObservableSet& obs,
    IsObserving observing) {
  if (!obs.singleZone() && obs.zones()->empty()) {
    return true;
  }

  // Invalidate scripts first so we can set the needsArgsObj flag on scripts
  // before patching frames.
  return updateExecutionObservabilityOfScripts(cx, obs, observing) &&
         updateExecutionObservabilityOfFrames(cx, obs, observing);
}

/* static */
bool Debugger::ensureExecutionObservabilityOfScript(JSContext* cx,
                                                    JSScript* script) {
  if (script->isDebuggee()) {
    return true;
  }
  ExecutionObservableScript obs(cx, script);
  return updateExecutionObservability(cx, obs, Observing);
}

/* static */
bool DebugAPI::ensureExecutionObservabilityOfOsrFrame(
    JSContext* cx, AbstractFramePtr osrSourceFrame) {
  MOZ_ASSERT(osrSourceFrame.isDebuggee());
  if (osrSourceFrame.script()->hasBaselineScript() &&
      osrSourceFrame.script()->baselineScript()->hasDebugInstrumentation()) {
    return true;
  }
  ExecutionObservableFrame obs(osrSourceFrame);
  return Debugger::updateExecutionObservabilityOfFrames(cx, obs, Observing);
}

/* static */
bool Debugger::ensureExecutionObservabilityOfFrame(JSContext* cx,
                                                   AbstractFramePtr frame) {
  MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(),
                frame.isDebuggee());
  MOZ_ASSERT_IF(frame.isWasmDebugFrame(), frame.wasmInstance()->debugEnabled());
  if (frame.isDebuggee()) {
    return true;
  }
  ExecutionObservableFrame obs(frame);
  return updateExecutionObservabilityOfFrames(cx, obs, Observing);
}

/* static */
bool Debugger::ensureExecutionObservabilityOfRealm(JSContext* cx,
                                                   Realm* realm) {
  if (realm->debuggerObservesAllExecution()) {
    return true;
  }
  ExecutionObservableRealms obs(cx);
  if (!obs.add(realm)) {
    return false;
  }
  realm->updateDebuggerObservesAllExecution();
  return updateExecutionObservability(cx, obs, Observing);
}

/* static */
bool Debugger::hookObservesAllExecution(Hook which) {
  return which == OnEnterFrame;
}

Debugger::IsObserving Debugger::observesAllExecution() const {
  if (!!getHook(OnEnterFrame)) {
    return Observing;
  }
  return NotObserving;
}

Debugger::IsObserving Debugger::observesAsmJS() const {
  if (!allowUnobservedAsmJS) {
    return Observing;
  }
  return NotObserving;
}

Debugger::IsObserving Debugger::observesWasm() const {
  if (!allowUnobservedWasm) {
    return Observing;
  }
  return NotObserving;
}

Debugger::IsObserving Debugger::observesCoverage() const {
  if (collectCoverageInfo) {
    return Observing;
  }
  return NotObserving;
}

Debugger::IsObserving Debugger::observesNativeCalls() const {
  if (getHook(Debugger::OnNativeCall)) {
    return Observing;
  }
  return NotObserving;
}

bool Debugger::isExclusiveDebuggerOnEval() const {
  return exclusiveDebuggerOnEval;
}

// Toggle whether this Debugger's debuggees observe all execution. This is
// called when a hook that observes all execution is set or unset. See
// hookObservesAllExecution.
bool Debugger::updateObservesAllExecutionOnDebuggees(JSContext* cx,
                                                     IsObserving observing) {
  ExecutionObservableRealms obs(cx);

  for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
       r.popFront()) {
    GlobalObject* global = r.front();
    JS::Realm* realm = global->realm();

    if (realm->debuggerObservesAllExecution() == observing) {
      continue;
    }

    // It's expensive to eagerly invalidate and recompile a realm,
    // so add the realm to the set only if we are observing.
    if (observing && !obs.add(realm)) {
      return false;
    }
  }

  if (!updateExecutionObservability(cx, obs, observing)) {
    return false;
  }

  using RealmRange = ExecutionObservableRealms::RealmRange;
  for (RealmRange r = obs.realms()->all(); !r.empty(); r.popFront()) {
    r.front()->updateDebuggerObservesAllExecution();
  }

  return true;
}

bool Debugger::updateObservesCoverageOnDebuggees(JSContext* cx,
                                                 IsObserving observing) {
  ExecutionObservableRealms obs(cx);

  for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
       r.popFront()) {
    GlobalObject* global = r.front();
    Realm* realm = global->realm();

    if (realm->debuggerObservesCoverage() == observing) {
      continue;
    }

    // Invalidate and recompile a realm to add or remove PCCounts
    // increments. We have to eagerly invalidate, as otherwise we might have
    // dangling pointers to freed PCCounts.
    if (!obs.add(realm)) {
      return false;
    }
  }

  // If any frame on the stack belongs to the debuggee, then we cannot update
  // the ScriptCounts, because this would imply to invalidate a Debugger.Frame
  // to recompile it with/without ScriptCount support.
  for (FrameIter iter(cx); !iter.done(); ++iter) {
    if (obs.shouldMarkAsDebuggee(iter)) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_DEBUG_NOT_IDLE);
      return false;
    }
  }

  if (!updateExecutionObservability(cx, obs, observing)) {
    return false;
  }

  // All realms can safely be toggled, and all scripts will be recompiled.
  // Thus we can update each realm accordingly.
  using RealmRange = ExecutionObservableRealms::RealmRange;
  for (RealmRange r = obs.realms()->all(); !r.empty(); r.popFront()) {
    r.front()->updateDebuggerObservesCoverage();
  }

  return true;
}

void Debugger::updateObservesAsmJSOnDebuggees(IsObserving observing) {
  for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
       r.popFront()) {
    GlobalObject* global = r.front();
    Realm* realm = global->realm();

    if (realm->debuggerObservesAsmJS() == observing) {
      continue;
    }

    realm->updateDebuggerObservesAsmJS();
  }
}

void Debugger::updateObservesWasmOnDebuggees(IsObserving observing) {
  for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
       r.popFront()) {
    GlobalObject* global = r.front();
    Realm* realm = global->realm();

    if (realm->debuggerObservesWasm() == observing) {
      continue;
    }

    realm->updateDebuggerObservesWasm();
  }
}

void Debugger::updateObservesNativeCallOnDebuggees(IsObserving observing) {
  for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
       r.popFront()) {
    GlobalObject* global = r.front();
    Realm* realm = global->realm();

    if (realm->debuggerObservesNativeCall() == observing) {
      continue;
    }

    realm->updateDebuggerObservesNativeCall();
  }
}

/*** Allocations Tracking ***************************************************/

/* static */
bool Debugger::cannotTrackAllocations(const GlobalObject& global) {
  auto existingCallback = global.realm()->getAllocationMetadataBuilder();
  return existingCallback && existingCallback != &SavedStacks::metadataBuilder;
}

/* static */
bool DebugAPI::isObservedByDebuggerTrackingAllocations(
    const GlobalObject& debuggee) {
  JS::AutoAssertNoGC nogc;
  for (Realm::DebuggerVectorEntry& entry : debuggee.getDebuggers(nogc)) {
    // Use unbarrieredGet() to prevent triggering read barrier while
    // collecting, this is safe as long as dbg does not escape.
    Debugger* dbg = entry.dbg.unbarrieredGet();
    if (dbg->trackingAllocationSites) {
      return true;
    }
  }

  return false;
}

/* static */
bool Debugger::addAllocationsTracking(JSContext* cx,
                                      Handle<GlobalObject*> debuggee) {
  // Precondition: the given global object is being observed by at least one
  // Debugger that is tracking allocations.
  MOZ_ASSERT(DebugAPI::isObservedByDebuggerTrackingAllocations(*debuggee));

  if (Debugger::cannotTrackAllocations(*debuggee)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET);
    return false;
  }

  debuggee->realm()->setAllocationMetadataBuilder(
      &SavedStacks::metadataBuilder);
  debuggee->realm()->chooseAllocationSamplingProbability();
  return true;
}

/* static */
void Debugger::removeAllocationsTracking(GlobalObject& global) {
  // If there are still Debuggers that are observing allocations, we cannot
  // remove the metadata callback yet. Recompute the sampling probability
  // based on the remaining debuggers' needs.
  if (DebugAPI::isObservedByDebuggerTrackingAllocations(global)) {
    global.realm()->chooseAllocationSamplingProbability();
    return;
  }

  if (!global.realm()->runtimeFromMainThread()->recordAllocationCallback) {
    // Something like the Gecko Profiler could request from the the JS runtime
    // to record allocations. If it is recording allocations, then do not
    // destroy the allocation metadata builder at this time.
    global.realm()->forgetAllocationMetadataBuilder();
  }
}

bool Debugger::addAllocationsTrackingForAllDebuggees(JSContext* cx) {
  MOZ_ASSERT(trackingAllocationSites);

  // We don't want to end up in a state where we added allocations
  // tracking to some of our debuggees, but failed to do so for
  // others. Before attempting to start tracking allocations in *any* of
  // our debuggees, ensure that we will be able to track allocations for
  // *all* of our debuggees.
  for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
       r.popFront()) {
    if (Debugger::cannotTrackAllocations(*r.front().get())) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET);
      return false;
    }
  }

  Rooted<GlobalObject*> g(cx);
  for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
       r.popFront()) {
    // This should always succeed, since we already checked for the
    // error case above.
    g = r.front().get();
    MOZ_ALWAYS_TRUE(Debugger::addAllocationsTracking(cx, g));
  }

  return true;
}

void Debugger::removeAllocationsTrackingForAllDebuggees() {
  for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
       r.popFront()) {
    Debugger::removeAllocationsTracking(*r.front().get());
  }

  allocationsLog.clear();
}

/*** Debugger JSObjects *****************************************************/

template <typename F>
inline void Debugger::forEachWeakMap(const F& f) {
  f(generatorFrames);
  f(objects);
  f(environments);
  f(scripts);
  f(sources);
  f(wasmInstanceScripts);
  f(wasmInstanceSources);
}

void Debugger::traceCrossCompartmentEdges(JSTracer* trc) {
  forEachWeakMap(
      [trc](auto& weakMap) { weakMap.traceCrossCompartmentEdges(trc); });
}

/*
 * Ordinarily, WeakMap keys and values are marked because at some point it was
 * discovered that the WeakMap was live; that is, some object containing the
 * WeakMap was marked during mark phase.
 *
 * However, during zone GC, we have to do something about cross-compartment
 * edges in non-GC'd compartments. Since the source may be live, we
 * conservatively assume it is and mark the edge.
 *
 * Each Debugger object keeps five cross-compartment WeakMaps: objects, scripts,
 * lazy scripts, script source objects, and environments. They have the property
 * that all their values are in the same compartment as the Debugger object,
 * but we have to mark the keys and the private pointer in the wrapper object.
 *
 * We must scan all Debugger objects regardless of whether they *currently* have
 * any debuggees in a compartment being GC'd, because the WeakMap entries
 * persist even when debuggees are removed.
 *
 * This happens during the initial mark phase, not iterative marking, because
 * all the edges being reported here are strong references.
 *
 * This method is also used during compacting GC to update cross compartment
 * pointers into zones that are being compacted.
 */

/* static */
void DebugAPI::traceCrossCompartmentEdges(JSTracer* trc) {
  MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());

  JSRuntime* rt = trc->runtime();
  gc::State state = rt->gc.state();

  for (Debugger* dbg : rt->debuggerList()) {
    Zone* zone = MaybeForwarded(dbg->object.get())->zone();
    if (!zone->isCollecting() || state == gc::State::Compact) {
      dbg->traceCrossCompartmentEdges(trc);
    }
  }
}

#ifdef DEBUG

static bool RuntimeHasDebugger(JSRuntime* rt, Debugger* dbg) {
  for (Debugger* d : rt->debuggerList()) {
    if (d == dbg) {
      return true;
    }
  }
  return false;
}

/* static */
bool DebugAPI::edgeIsInDebuggerWeakmap(JSRuntime* rt, JSObject* src,
                                       JS::GCCellPtr dst) {
  if (!Debugger::isChildJSObject(src)) {
    return false;
  }

  if (src->is<DebuggerFrame>()) {
    DebuggerFrame* frame = &src->as<DebuggerFrame>();
    Debugger* dbg = frame->owner();
    MOZ_ASSERT(RuntimeHasDebugger(rt, dbg));

    if (dst.is<BaseScript>()) {
      // The generatorFrames map is not keyed on the associated JSScript. Get
      // the key from the source object and check everything matches.
      AbstractGeneratorObject* genObj = &frame->unwrappedGenerator();
      return frame->generatorScript() == &dst.as<BaseScript>() &&
             dbg->generatorFrames.hasEntry(genObj, src);
    }
    return dst.is<JSObject>() &&
           dst.as<JSObject>().is<AbstractGeneratorObject>() &&
           dbg->generatorFrames.hasEntry(
               &dst.as<JSObject>().as<AbstractGeneratorObject>(), src);
  }
  if (src->is<DebuggerObject>()) {
    Debugger* dbg = src->as<DebuggerObject>().owner();
    MOZ_ASSERT(RuntimeHasDebugger(rt, dbg));
    return dst.is<JSObject>() &&
           dbg->objects.hasEntry(&dst.as<JSObject>(), src);
  }
  if (src->is<DebuggerEnvironment>()) {
    Debugger* dbg = src->as<DebuggerEnvironment>().owner();
    MOZ_ASSERT(RuntimeHasDebugger(rt, dbg));
    return dst.is<JSObject>() &&
           dbg->environments.hasEntry(&dst.as<JSObject>(), src);
  }
  if (src->is<DebuggerScript>()) {
    Debugger* dbg = src->as<DebuggerScript>().owner();
    MOZ_ASSERT(RuntimeHasDebugger(rt, dbg));

    return src->as<DebuggerScript>().getReferent().match(
        [=](BaseScript* script) {
          return dst.is<BaseScript>() && script == &dst.as<BaseScript>() &&
                 dbg->scripts.hasEntry(script, src);
        },
        [=](WasmInstanceObject* instance) {
          return dst.is<JSObject>() && instance == &dst.as<JSObject>() &&
                 dbg->wasmInstanceScripts.hasEntry(instance, src);
        });
  }
  if (src->is<DebuggerSource>()) {
    Debugger* dbg = src->as<DebuggerSource>().owner();
    MOZ_ASSERT(RuntimeHasDebugger(rt, dbg));

    return src->as<DebuggerSource>().getReferent().match(
        [=](ScriptSourceObject* sso) {
          return dst.is<JSObject>() && sso == &dst.as<JSObject>() &&
                 dbg->sources.hasEntry(sso, src);
        },
        [=](WasmInstanceObject* instance) {
          return dst.is<JSObject>() && instance == &dst.as<JSObject>() &&
                 dbg->wasmInstanceSources.hasEntry(instance, src);
        });
  }
  MOZ_ASSERT_UNREACHABLE("Unhandled cross-compartment edge");
}

#endif

/* See comments in DebugAPI.h. */
void DebugAPI::traceFramesWithLiveHooks(JSTracer* tracer) {
  JSRuntime* rt = tracer->runtime();

  // Note that we must loop over all Debuggers here, not just those known to be
  // reachable from JavaScript. The existence of hooks set on a Debugger.Frame
  // for a live stack frame makes the Debuger.Frame (and hence its Debugger)
  // reachable.
  for (Debugger* dbg : rt->debuggerList()) {
    // Callback tracers set their own traversal boundaries, but otherwise we're
    // only interested in Debugger.Frames participating in the collection.
    if (!dbg->zone()->isGCMarking() && !tracer->isCallbackTracer()) {
      continue;
    }

    for (Debugger::FrameMap::Range r = dbg->frames.all(); !r.empty();
         r.popFront()) {
      HeapPtr<DebuggerFrame*>& frameobj = r.front().value();
      MOZ_ASSERT(frameobj->isOnStackOrSuspendedWasmStack());
      if (frameobj->hasAnyHooks()) {
        TraceEdge(tracer, &frameobj, "Debugger.Frame with live hooks");
      }
    }
  }
}

void DebugAPI::slowPathTraceGeneratorFrame(JSTracer* tracer,
                                           AbstractGeneratorObject* generator) {
  MOZ_ASSERT(generator->realm()->isDebuggee());

  // Ignore generic tracers.
  //
  // There are two kinds of generic tracers we need to bar: MovingTracers used
  // by compacting GC; and CompartmentCheckTracers.
  //
  // MovingTracers are used by the compacting GC to update pointers to objects
  // that have been moved: the MovingTracer checks each outgoing pointer to see
  // if it refers to a forwarding pointer, and if so, updates the pointer stored
  // in the object.
  //
  // Generator objects are background finalized, so the compacting GC assumes it
  // can update their pointers in the background as well. Since we treat
  // generator objects as having an owning edge to their Debugger.Frame objects,
  // a helper thread trying to update a generator object will end up calling
  // this function. However, it is verboten to do weak map lookups (e.g., in
  // Debugger::generatorFrames) off the main thread, since StableCellHasher
  // must consult the Zone to find the key's unique id.
  //
  // Fortunately, it's not necessary for compacting GC to worry about that edge
  // in the first place: the edge isn't a literal pointer stored on the
  // generator object, it's only inferred from the realm's debuggee status and
  // its Debuggers' generatorFrames weak maps. Those get relocated when the
  // Debugger itself is visited, so compacting GC can just ignore this edge.
  //
  // CompartmentCheckTracers walk the graph and verify that all
  // cross-compartment edges are recorded in the cross-compartment wrapper
  // tables. But edges between Debugger.Foo objects and their referents are not
  // in the CCW tables, so a CrossCompartmentCheckTracers also calls
  // DebugAPI::edgeIsInDebuggerWeakmap to see if a given cross-compartment edge
  // is accounted for there. However, edgeIsInDebuggerWeakmap only handles
  // debugger -> debuggee edges, so it won't recognize the edge we're
  // potentially traversing here, from a generator object to its Debugger.Frame.
  //
  // But since the purpose of this function is to retrieve such edges, if they
  // exist, from the very tables that edgeIsInDebuggerWeakmap would consult,
  // we're at no risk of reporting edges that they do not cover. So we can
  // safely hide the edges from CompartmentCheckTracers.
  //
  // We can't quite recognize MovingTracers and CompartmentCheckTracers
  // precisely, but they're both generic tracers, so we just show them all the
  // door. This means the generator -> Debugger.Frame edge is going to be
  // invisible to some traversals. We'll cope with that when it's a problem.
  if (!tracer->isMarkingTracer()) {
    return;
  }

  mozilla::Maybe<AutoLockGC> lock;
  GCMarker* marker = GCMarker::fromTracer(tracer);
  if (marker->isParallelMarking()) {
    // Synchronise access to generatorFrames.
    lock.emplace(marker->runtime());
  }

  JS::AutoAssertNoGC nogc;
  for (Realm::DebuggerVectorEntry& entry :
       generator->realm()->getDebuggers(nogc)) {
    Debugger* dbg = entry.dbg.unbarrieredGet();

    if (Debugger::GeneratorWeakMap::Ptr entry =
            dbg->generatorFrames.lookupUnbarriered(generator)) {
      HeapPtr<DebuggerFrame*>& frameObj = entry->value();
      if (frameObj->hasAnyHooks()) {
        // See comment above.
        TraceCrossCompartmentEdge(tracer, generator, &frameObj,
                                  "Debugger.Frame with hooks for generator");
      }
    }
  }
}

/* static */
void DebugAPI::traceAllForMovingGC(JSTracer* trc) {
  JSRuntime* rt = trc->runtime();
  for (Debugger* dbg : rt->debuggerList()) {
    dbg->traceForMovingGC(trc);
  }
}

/*
 * Trace all debugger-owned GC things unconditionally. This is used during
 * compacting GC and in minor GC: the minor GC cannot apply the weak constraints
 * of the full GC because it visits only part of the heap.
 */

void Debugger::traceForMovingGC(JSTracer* trc) {
  trace(trc);

  for (WeakGlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
    TraceEdge(trc, &e.mutableFront(), "Global Object");
  }
}

/* static */
void Debugger::traceObject(JSTracer* trc, JSObject* obj) {
  if (Debugger* dbg = Debugger::fromJSObject(obj)) {
    dbg->trace(trc);
  }
}

void Debugger::trace(JSTracer* trc) {
  TraceEdge(trc, &object, "Debugger Object");

  TraceNullableEdge(trc, &uncaughtExceptionHook, "hooks");

  // Mark Debugger.Frame objects. Since the Debugger is reachable, JS could call
  // getNewestFrame and then walk the stack, so these are all reachable from JS.
  //
  // Note that if a Debugger.Frame has hooks set, it must be retained even if
  // its Debugger is unreachable, since JS could observe that its hooks did not
  // fire. That case is handled by DebugAPI::traceFrames.
  //
  // (We have weakly-referenced Debugger.Frame objects as well, for suspended
  // generator frames; these are traced via generatorFrames just below.)
  for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
    HeapPtr<DebuggerFrame*>& frameobj = r.front().value();
    TraceEdge(trc, &frameobj, "live Debugger.Frame");
    MOZ_ASSERT(frameobj->isOnStackOrSuspendedWasmStack());
  }

  allocationsLog.trace(trc);

  forEachWeakMap([trc](auto& weakMap) { weakMap.trace(trc); });
}

/* static */
void DebugAPI::traceFromRealm(JSTracer* trc, Realm* realm) {
  JS::AutoAssertNoGC nogc;
  for (Realm::DebuggerVectorEntry& entry : realm->getDebuggers(nogc)) {
    TraceEdge(trc, &entry.debuggerLink, "realm debugger");
  }
}

/* static */
void DebugAPI::sweepAll(JS::GCContext* gcx) {
  JSRuntime* rt = gcx->runtime();

  Debugger* next;
  for (Debugger* dbg = rt->debuggerList().getFirst(); dbg; dbg = next) {
    next = dbg->getNext();

    // Debugger.Frames for generator calls bump the JSScript's
    // generatorObserverCount, so the JIT will instrument the code to notify
    // Debugger when the generator is resumed. When a Debugger.Frame gets GC'd,
    // generatorObserverCount needs to be decremented. It's much easier to do
    // this when we know that all parties involved - the Debugger.Frame, the
    // generator object, and the JSScript - have not yet been finalized.
    //
    // Since DebugAPI::sweepAll is called after everything is marked, but before
    // anything has been finalized, this is the perfect place to drop the count.
    if (dbg->zone()->isGCSweeping()) {
      for (Debugger::GeneratorWeakMap::Enum e(dbg->generatorFrames); !e.empty();
           e.popFront()) {
        DebuggerFrame* frameObj = e.front().value();
        if (IsAboutToBeFinalizedUnbarriered(frameObj)) {
          // If the DebuggerFrame is being finalized, that means either:
          //  1) It is not present in "frames".
          //  2) The Debugger itself is also being finalized.
          //
          // In the first case, passing the frame is not necessary because there
          // isn't a frame entry to clear, and in the second case,
          // removeDebuggeeGlobal below will iterate and remove the entries
          // anyway, so things will be cleaned up properly.
          Debugger::terminateDebuggerFrame(gcx, dbg, frameObj, NullFramePtr(),
                                           nullptr, &e);
        }
      }
    }

    // Detach dying debuggers and debuggees from each other. Since this
    // requires access to both objects it must be done before either
    // object is finalized.
    bool debuggerDying = IsAboutToBeFinalized(dbg->object);
    for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty();
         e.popFront()) {
      GlobalObject* global = e.front().unbarrieredGet();
      if (debuggerDying || IsAboutToBeFinalizedUnbarriered(global)) {
        dbg->removeDebuggeeGlobal(gcx, e.front().unbarrieredGet(), &e,
                                  Debugger::FromSweep::Yes);
      }
    }

    if (debuggerDying) {
      gcx->delete_(dbg->object, dbg, MemoryUse::Debugger);
    }

    dbg = next;
  }
}

static inline bool SweepZonesInSameGroup(Zone* a, Zone* b) {
  // Ensure two zones are swept in the same sweep group by adding an edge
  // between them in each direction.
  return a->addSweepGroupEdgeTo(b) && b->addSweepGroupEdgeTo(a);
}

/* static */
bool DebugAPI::findSweepGroupEdges(JSRuntime* rt) {
  // Ensure that debuggers and their debuggees are finalized in the same group
  // by adding edges in both directions for debuggee zones. These are weak
  // references that are not in the cross compartment wrapper map.

  for (Debugger* dbg : rt->debuggerList()) {
    Zone* debuggerZone = dbg->object->zone();
    if (!debuggerZone->isGCMarking()) {
      continue;
    }

    for (auto e = dbg->debuggeeZones.all(); !e.empty(); e.popFront()) {
      Zone* debuggeeZone = e.front();
      if (!debuggeeZone->isGCMarking()) {
        continue;
      }

      if (!SweepZonesInSameGroup(debuggerZone, debuggeeZone)) {
        return false;
      }
    }
  }

  return true;
}

template <class UnbarrieredKey, class Wrapper, bool InvisibleKeysOk>
bool DebuggerWeakMap<UnbarrieredKey, Wrapper,
                     InvisibleKeysOk>::findSweepGroupEdges() {
  Zone* debuggerZone = zone();
  MOZ_ASSERT(debuggerZone->isGCMarking());
  for (Enum e(*this); !e.empty(); e.popFront()) {
    MOZ_ASSERT(e.front().value()->zone() == debuggerZone);

    Zone* keyZone = e.front().key()->zone();
    if (keyZone->isGCMarking() &&
        !SweepZonesInSameGroup(debuggerZone, keyZone)) {
      return false;
    }
  }

  // Add in edges for delegates, if relevant for the key type.
  return Base::findSweepGroupEdges();
}

const JSClassOps DebuggerInstanceObject::classOps_ = {
    nullptr,                // addProperty
    nullptr,                // delProperty
    nullptr,                // enumerate
    nullptr,                // newEnumerate
    nullptr,                // resolve
    nullptr,                // mayResolve
    nullptr,                // finalize
    nullptr,                // call
    nullptr,                // construct
    Debugger::traceObject,  // trace
};

const JSClass DebuggerInstanceObject::class_ = {
    "Debugger",
    JSCLASS_HAS_RESERVED_SLOTS(Debugger::JSSLOT_DEBUG_COUNT),
    &classOps_,
};

static_assert(Debugger::JSSLOT_DEBUG_PROTO_START == 0,
              "DebuggerPrototypeObject only needs slots for the proto objects");

const JSClass DebuggerPrototypeObject::class_ = {
    "DebuggerPrototype",
    JSCLASS_HAS_RESERVED_SLOTS(Debugger::JSSLOT_DEBUG_PROTO_STOP),
};

static Debugger* Debugger_fromThisValue(JSContext* cx, const CallArgs& args,
                                        const char* fnname) {
  JSObject* thisobj = RequireObject(cx, args.thisv());
  if (!thisobj) {
    return nullptr;
  }
  if (!thisobj->is<DebuggerInstanceObject>()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_INCOMPATIBLE_PROTO, "Debugger", fnname,
                              thisobj->getClass()->name);
    return nullptr;
  }

  Debugger* dbg = Debugger::fromJSObject(thisobj);
  MOZ_ASSERT(dbg);
  return dbg;
}

struct MOZ_STACK_CLASS Debugger::CallData {
  JSContext* cx;
  const CallArgs& args;

  Debugger* dbg;

  CallData(JSContext* cx, const CallArgs& args, Debugger* dbg)
      : cx(cx), args(args), dbg(dbg) {}

  bool getOnDebuggerStatement();
  bool setOnDebuggerStatement();
  bool getOnExceptionUnwind();
  bool setOnExceptionUnwind();
  bool getOnNewScript();
  bool setOnNewScript();
  bool getOnEnterFrame();
  bool setOnEnterFrame();
  bool getOnNativeCall();
  bool setOnNativeCall();
  bool getShouldAvoidSideEffects();
  bool setShouldAvoidSideEffects();
  bool getOnNewGlobalObject();
  bool setOnNewGlobalObject();
  bool getOnNewPromise();
  bool setOnNewPromise();
  bool getOnPromiseSettled();
  bool setOnPromiseSettled();
  bool getUncaughtExceptionHook();
  bool setUncaughtExceptionHook();
  bool getAllowUnobservedAsmJS();
  bool setAllowUnobservedAsmJS();
  bool getAllowUnobservedWasm();
  bool setAllowUnobservedWasm();
  bool getExclusiveDebuggerOnEval();
  bool setExclusiveDebuggerOnEval();
  bool getInspectNativeCallArguments();
  bool setInspectNativeCallArguments();
  bool getCollectCoverageInfo();
  bool setCollectCoverageInfo();
  bool getMemory();
  bool addDebuggee();
  bool addAllGlobalsAsDebuggees();
  bool removeDebuggee();
  bool removeAllDebuggees();
  bool hasDebuggee();
  bool getDebuggees();
  bool getNewestFrame();
  bool clearAllBreakpoints();
  bool findScripts();
  bool findSources();
  bool findObjects();
  bool findAllGlobals();
  bool findSourceURLs();
  bool makeGlobalObjectReference();
  bool adoptDebuggeeValue();
  bool adoptFrame();
  bool adoptSource();
  bool enableAsyncStack();
  bool disableAsyncStack();
  bool enableUnlimitedStacksCapturing();
  bool disableUnlimitedStacksCapturing();

  using Method = bool (CallData::*)();

  template <Method MyMethod>
  static bool ToNative(JSContext* cx, unsigned argc, Value* vp);
};

template <Debugger::CallData::Method MyMethod>
/* static */
bool Debugger::CallData::ToNative(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  Debugger* dbg = Debugger_fromThisValue(cx, args, "method");
  if (!dbg) {
    return false;
  }

  CallData data(cx, args, dbg);
  return (data.*MyMethod)();
}

/* static */
bool Debugger::getHookImpl(JSContext* cx, const CallArgs& args, Debugger& dbg,
                           Hook which) {
  MOZ_ASSERT(which >= 0 && which < HookCount);
  args.rval().set(dbg.object->getReservedSlot(
      JSSLOT_DEBUG_HOOK_START + std::underlying_type_t<Hook>(which)));
  return true;
}

/* static */
bool Debugger::setHookImpl(JSContext* cx, const CallArgs& args, Debugger& dbg,
                           Hook which) {
  MOZ_ASSERT(which >= 0 && which < HookCount);
  if (!args.requireAtLeast(cx, "Debugger.setHook", 1)) {
    return false;
  }
  if (args[0].isObject()) {
    if (!args[0].toObject().isCallable()) {
      return ReportIsNotFunction(cx, args[0], args.length() - 1);
    }
  } else if (!args[0].isUndefined()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_NOT_CALLABLE_OR_UNDEFINED);
    return false;
  }

  // Disallow simultaneous activation of OnEnterFrame and code coverage support;
  // as they both use the execution observer flag. See Bug 1608891.
  if (dbg.collectCoverageInfo && which == Hook::OnEnterFrame) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_EXCLUSIVE_FRAME_COVERAGE);
    return false;
  }

  uint32_t slot = JSSLOT_DEBUG_HOOK_START + std::underlying_type_t<Hook>(which);
  RootedValue oldHook(cx, dbg.object->getReservedSlot(slot));
  dbg.object->setReservedSlot(slot, args[0]);
  if (hookObservesAllExecution(which)) {
    if (!dbg.updateObservesAllExecutionOnDebuggees(
            cx, dbg.observesAllExecution())) {
      dbg.object->setReservedSlot(slot, oldHook);
      return false;
    }
  }

  Rooted<DebuggerDebuggeeLink*> debuggeeLink(cx, dbg.getDebuggeeLink());
  if (dbg.hasAnyLiveHooks()) {
    debuggeeLink->setLinkSlot(dbg);
  } else {
    debuggeeLink->clearLinkSlot();
  }

  args.rval().setUndefined();
  return true;
}

/* static */
bool Debugger::getGarbageCollectionHook(JSContext* cx, const CallArgs& args,
                                        Debugger& dbg) {
  return getHookImpl(cx, args, dbg, OnGarbageCollection);
}

/* static */
bool Debugger::setGarbageCollectionHook(JSContext* cx, const CallArgs& args,
                                        Debugger& dbg) {
  Rooted<JSObject*> oldHook(cx, dbg.getHook(OnGarbageCollection));

  if (!setHookImpl(cx, args, dbg, OnGarbageCollection)) {
    // We want to maintain the invariant that the hook is always set when the
    // Debugger is in the runtime's list, and vice-versa, so if we return early
    // and don't adjust the watcher list below, we need to be sure that the
    // hook didn't change.
    MOZ_ASSERT(dbg.getHook(OnGarbageCollection) == oldHook);
    return false;
  }

  // Add or remove ourselves from the runtime's list of Debuggers that care
  // about garbage collection.
  JSObject* newHook = dbg.getHook(OnGarbageCollection);
  if (!oldHook && newHook) {
    cx->runtime()->onGarbageCollectionWatchers().pushBack(&dbg);
  } else if (oldHook && !newHook) {
    cx->runtime()->onGarbageCollectionWatchers().remove(&dbg);
  }

  return true;
}

bool Debugger::CallData::getOnDebuggerStatement() {
  return getHookImpl(cx, args, *dbg, OnDebuggerStatement);
}

bool Debugger::CallData::setOnDebuggerStatement() {
  return setHookImpl(cx, args, *dbg, OnDebuggerStatement);
}

bool Debugger::CallData::getOnExceptionUnwind() {
  return getHookImpl(cx, args, *dbg, OnExceptionUnwind);
}

bool Debugger::CallData::setOnExceptionUnwind() {
  return setHookImpl(cx, args, *dbg, OnExceptionUnwind);
}

bool Debugger::CallData::getOnNewScript() {
  return getHookImpl(cx, args, *dbg, OnNewScript);
}

bool Debugger::CallData::setOnNewScript() {
  return setHookImpl(cx, args, *dbg, OnNewScript);
}

bool Debugger::CallData::getOnNewPromise() {
  return getHookImpl(cx, args, *dbg, OnNewPromise);
}

bool Debugger::CallData::setOnNewPromise() {
  return setHookImpl(cx, args, *dbg, OnNewPromise);
}

bool Debugger::CallData::getOnPromiseSettled() {
  return getHookImpl(cx, args, *dbg, OnPromiseSettled);
}

bool Debugger::CallData::setOnPromiseSettled() {
  return setHookImpl(cx, args, *dbg, OnPromiseSettled);
}

bool Debugger::CallData::getOnEnterFrame() {
  return getHookImpl(cx, args, *dbg, OnEnterFrame);
}

bool Debugger::CallData::setOnEnterFrame() {
  return setHookImpl(cx, args, *dbg, OnEnterFrame);
}

bool Debugger::CallData::getOnNativeCall() {
  return getHookImpl(cx, args, *dbg, OnNativeCall);
}

bool Debugger::CallData::setOnNativeCall() {
  RootedObject oldHook(cx, dbg->getHook(OnNativeCall));

  if (!setHookImpl(cx, args, *dbg, OnNativeCall)) {
    return false;
  }

  JSObject* newHook = dbg->getHook(OnNativeCall);
  if (!oldHook && newHook) {
    dbg->updateObservesNativeCallOnDebuggees(Observing);
  } else if (oldHook && !newHook) {
    dbg->updateObservesNativeCallOnDebuggees(NotObserving);
  }

  return true;
}

bool Debugger::CallData::getShouldAvoidSideEffects() {
  args.rval().setBoolean(dbg->shouldAvoidSideEffects);
  return true;
}

bool Debugger::CallData::setShouldAvoidSideEffects() {
  if (!args.requireAtLeast(cx, "Debugger.set shouldAvoidSideEffects", 1)) {
    return false;
  }

  dbg->shouldAvoidSideEffects = ToBoolean(args[0]);

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::getOnNewGlobalObject() {
  return getHookImpl(cx, args, *dbg, OnNewGlobalObject);
}

bool Debugger::CallData::setOnNewGlobalObject() {
  RootedObject oldHook(cx, dbg->getHook(OnNewGlobalObject));

  if (!setHookImpl(cx, args, *dbg, OnNewGlobalObject)) {
    return false;
  }

  // Add or remove ourselves from the runtime's list of Debuggers that care
  // about new globals.
  JSObject* newHook = dbg->getHook(OnNewGlobalObject);
  if (!oldHook && newHook) {
    cx->runtime()->onNewGlobalObjectWatchers().pushBack(dbg);
  } else if (oldHook && !newHook) {
    cx->runtime()->onNewGlobalObjectWatchers().remove(dbg);
  }

  return true;
}

bool Debugger::CallData::getUncaughtExceptionHook() {
  args.rval().setObjectOrNull(dbg->uncaughtExceptionHook);
  return true;
}

bool Debugger::CallData::setUncaughtExceptionHook() {
  if (!args.requireAtLeast(cx, "Debugger.set uncaughtExceptionHook", 1)) {
    return false;
  }
  if (!args[0].isNull() &&
      (!args[0].isObject() || !args[0].toObject().isCallable())) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ASSIGN_FUNCTION_OR_NULL,
                              "uncaughtExceptionHook");
    return false;
  }
  dbg->uncaughtExceptionHook = args[0].toObjectOrNull();
  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::getAllowUnobservedAsmJS() {
  args.rval().setBoolean(dbg->allowUnobservedAsmJS);
  return true;
}

bool Debugger::CallData::setAllowUnobservedAsmJS() {
  if (!args.requireAtLeast(cx, "Debugger.set allowUnobservedAsmJS", 1)) {
    return false;
  }
  dbg->allowUnobservedAsmJS = ToBoolean(args[0]);

  for (WeakGlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty();
       r.popFront()) {
    GlobalObject* global = r.front();
    Realm* realm = global->realm();
    realm->updateDebuggerObservesAsmJS();
  }

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::getAllowUnobservedWasm() {
  args.rval().setBoolean(dbg->allowUnobservedWasm);
  return true;
}

bool Debugger::CallData::setAllowUnobservedWasm() {
  if (!args.requireAtLeast(cx, "Debugger.set allowUnobservedWasm", 1)) {
    return false;
  }
  dbg->allowUnobservedWasm = ToBoolean(args[0]);

  for (WeakGlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty();
       r.popFront()) {
    GlobalObject* global = r.front();
    Realm* realm = global->realm();
    realm->updateDebuggerObservesWasm();
  }

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::getExclusiveDebuggerOnEval() {
  args.rval().setBoolean(dbg->exclusiveDebuggerOnEval);
  return true;
}

bool Debugger::CallData::setExclusiveDebuggerOnEval() {
  if (!args.requireAtLeast(cx, "Debugger.set exclusiveDebuggerOnEval", 1)) {
    return false;
  }
  dbg->exclusiveDebuggerOnEval = ToBoolean(args[0]);

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::getInspectNativeCallArguments() {
  args.rval().setBoolean(dbg->inspectNativeCallArguments);
  return true;
}

bool Debugger::CallData::setInspectNativeCallArguments() {
  if (!args.requireAtLeast(cx, "Debugger.set inspectNativeCallArguments", 1)) {
    return false;
  }
  dbg->inspectNativeCallArguments = ToBoolean(args[0]);

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::getCollectCoverageInfo() {
  args.rval().setBoolean(dbg->collectCoverageInfo);
  return true;
}

bool Debugger::CallData::setCollectCoverageInfo() {
  if (!args.requireAtLeast(cx, "Debugger.set collectCoverageInfo", 1)) {
    return false;
  }

  // Disallow simultaneous activation of OnEnterFrame and code coverage support;
  // as they both use the execution observer flag. See Bug 1608891.
  uint32_t slot = JSSLOT_DEBUG_HOOK_START +
                  std::underlying_type_t<Hook>(Hook::OnEnterFrame);
  if (!dbg->object->getReservedSlot(slot).isUndefined()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_EXCLUSIVE_FRAME_COVERAGE);
    return false;
  }

  if (cx->realm()->isTracingExecution()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_EXCLUSIVE_EXECUTION_TRACE_COVERAGE);
    return false;
  }

  dbg->collectCoverageInfo = ToBoolean(args[0]);

  IsObserving observing = dbg->collectCoverageInfo ? Observing : NotObserving;
  if (!dbg->updateObservesCoverageOnDebuggees(cx, observing)) {
    return false;
  }

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::getMemory() {
  Value memoryValue =
      dbg->object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE);

  if (!memoryValue.isObject()) {
    RootedObject memory(cx, DebuggerMemory::create(cx, dbg));
    if (!memory) {
      return false;
    }
    memoryValue = ObjectValue(*memory);
  }

  args.rval().set(memoryValue);
  return true;
}

/*
 * Given a value used to designate a global (there's quite a variety; see the
 * docs), return the actual designee.
 *
 * Note that this does not check whether the designee is marked "invisible to
 * Debugger" or not; different callers need to handle invisible-to-Debugger
 * globals in different ways.
 */

GlobalObject* Debugger::unwrapDebuggeeArgument(JSContext* cx, const Value& v) {
  if (!v.isObject()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_UNEXPECTED_TYPE, "argument",
                              "not a global object");
    return nullptr;
  }

  RootedObject obj(cx, &v.toObject());

  // If it's a Debugger.Object belonging to this debugger, dereference that.
  if (obj->getClass() == &DebuggerObject::class_) {
    RootedValue rv(cx, v);
    if (!unwrapDebuggeeValue(cx, &rv)) {
      return nullptr;
    }
    obj = &rv.toObject();
  }

  // If we have a cross-compartment wrapper, dereference as far as is secure.
  //
  // Since we're dealing with globals, we may have a WindowProxy here.  So we
  // have to make sure to do a dynamic unwrap, and we want to unwrap the
  // WindowProxy too, if we have one.
  obj = CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false);
  if (!obj) {
    ReportAccessDenied(cx);
    return nullptr;
  }

  if (JS_IsDeadWrapper(obj)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
    return nullptr;
  }

  // If that didn't produce a global object, it's an error.
  if (!obj->is<GlobalObject>()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_UNEXPECTED_TYPE, "argument",
                              "not a global object");
    return nullptr;
  }

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

bool Debugger::CallData::addDebuggee() {
  if (!args.requireAtLeast(cx, "Debugger.addDebuggee", 1)) {
    return false;
  }
  Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
  if (!global) {
    return false;
  }

  if (!dbg->addDebuggeeGlobal(cx, global)) {
    return false;
  }

  RootedValue v(cx, ObjectValue(*global));
  if (!dbg->wrapDebuggeeValue(cx, &v)) {
    return false;
  }
  args.rval().set(v);
  return true;
}

bool Debugger::CallData::addAllGlobalsAsDebuggees() {
  for (CompartmentsIter comp(cx->runtime()); !comp.done(); comp.next()) {
    if (comp == dbg->object->compartment()) {
      continue;
    }
    for (RealmsInCompartmentIter r(comp); !r.done(); r.next()) {
      if (r->creationOptions().invisibleToDebugger()) {
        continue;
      }
      if (!r->hasInitializedGlobal()) {
        continue;
      }
      r->compartment()->gcState.scheduledForDestruction = false;
      Rooted<GlobalObject*> global(cx, r->maybeGlobal());
      MOZ_ASSERT(global);
      if (!dbg->addDebuggeeGlobal(cx, global)) {
        return false;
      }
    }
  }

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::removeDebuggee() {
  if (!args.requireAtLeast(cx, "Debugger.removeDebuggee", 1)) {
    return false;
  }
  Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
  if (!global) {
    return false;
  }

  ExecutionObservableRealms obs(cx);

  if (dbg->debuggees.has(global)) {
    dbg->removeDebuggeeGlobal(cx->gcContext(), global, nullptr, FromSweep::No);

    // Only update the realm if there are no Debuggers left, as it's
    // expensive to check if no other Debugger has a live script or frame
    // hook on any of the current on-stack debuggee frames.
    if (!global->hasDebuggers() && !obs.add(global->realm())) {
      return false;
    }
    if (!updateExecutionObservability(cx, obs, NotObserving)) {
      return false;
    }
  }

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::removeAllDebuggees() {
  ExecutionObservableRealms obs(cx);

  for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
    Rooted<GlobalObject*> global(cx, e.front());
    dbg->removeDebuggeeGlobal(cx->gcContext(), global, &e, FromSweep::No);

    // See note about adding to the observable set in removeDebuggee.
    if (!global->hasDebuggers() && !obs.add(global->realm())) {
      return false;
    }
  }

  if (!updateExecutionObservability(cx, obs, NotObserving)) {
    return false;
  }

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::hasDebuggee() {
  if (!args.requireAtLeast(cx, "Debugger.hasDebuggee", 1)) {
    return false;
  }
  GlobalObject* global = dbg->unwrapDebuggeeArgument(cx, args[0]);
  if (!global) {
    return false;
  }
  args.rval().setBoolean(!!dbg->debuggees.lookup(global));
  return true;
}

bool Debugger::CallData::getDebuggees() {
  // Obtain the list of debuggees before wrapping each debuggee, as a GC could
  // update the debuggees set while we are iterating it.
  unsigned count = dbg->debuggees.count();
  RootedValueVector debuggees(cx);
  if (!debuggees.resize(count)) {
    return false;
  }
  unsigned i = 0;
  {
    JS::AutoCheckCannotGC nogc;
    for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty();
         e.popFront()) {
      debuggees[i++].setObject(*e.front().get());
    }
  }

  Rooted<ArrayObject*> arrobj(cx, NewDenseFullyAllocatedArray(cx, count));
  if (!arrobj) {
    return false;
  }
  arrobj->ensureDenseInitializedLength(0, count);
  for (i = 0; i < count; i++) {
    RootedValue v(cx, debuggees[i]);
    if (!dbg->wrapDebuggeeValue(cx, &v)) {
      return false;
    }
    arrobj->setDenseElement(i, v);
  }

  args.rval().setObject(*arrobj);
  return true;
}

bool Debugger::CallData::getNewestFrame() {
  // Since there may be multiple contexts, use AllFramesIter.
  for (AllFramesIter i(cx); !i.done(); ++i) {
    if (dbg->observesFrame(i)) {
      // Ensure that Ion frames are rematerialized. Only rematerialized
      // Ion frames may be used as AbstractFramePtrs.
      if (i.isIon() && !i.ensureHasRematerializedFrame(cx)) {
        return false;
      }
      AbstractFramePtr frame = i.abstractFramePtr();
      FrameIter iter(i.activation()->cx());
      while (!iter.hasUsableAbstractFramePtr() ||
             iter.abstractFramePtr() != frame) {
        ++iter;
      }
      return dbg->getFrame(cx, iter, args.rval());
    }
  }
  args.rval().setNull();
  return true;
}

bool Debugger::CallData::clearAllBreakpoints() {
  JS::GCContext* gcx = cx->gcContext();
  Breakpoint* nextbp;
  for (Breakpoint* bp = dbg->firstBreakpoint(); bp; bp = nextbp) {
    nextbp = bp->nextInDebugger();

    bp->remove(gcx);
  }
  MOZ_ASSERT(!dbg->firstBreakpoint());

  return true;
}

/* static */
bool Debugger::construct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // Check that the arguments, if any, are cross-compartment wrappers.
  for (unsigned i = 0; i < args.length(); i++) {
    JSObject* argobj = RequireObject(cx, args[i]);
    if (!argobj) {
      return false;
    }
    if (!argobj->is<CrossCompartmentWrapperObject>()) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_DEBUG_CCW_REQUIRED, "Debugger");
      return false;
    }
  }

  // Get Debugger.prototype.
  RootedValue v(cx);
  RootedObject callee(cx, &args.callee());
  if (!GetProperty(cx, callee, callee, cx->names().prototype, &v)) {
    return false;
  }
  Rooted<NativeObject*> proto(cx, &v.toObject().as<NativeObject>());
  MOZ_ASSERT(proto->is<DebuggerPrototypeObject>());

  // Make the new Debugger object. Each one has a reference to
  // Debugger.{Frame,Object,Script,Memory}.prototype in reserved slots. The
  // rest of the reserved slots are for hooks; they default to undefined.
  Rooted<DebuggerInstanceObject*> obj(
      cx, NewTenuredObjectWithGivenProto<DebuggerInstanceObject>(cx, proto));
  if (!obj) {
    return false;
  }
  for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP;
       slot++) {
    obj->setReservedSlot(slot, proto->getReservedSlot(slot));
  }
  obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, NullValue());

  Rooted<NativeObject*> livenessLink(
      cx, NewObjectWithGivenProto<DebuggerDebuggeeLink>(cx, nullptr));
  if (!livenessLink) {
    return false;
  }
  obj->setReservedSlot(JSSLOT_DEBUG_DEBUGGEE_LINK, ObjectValue(*livenessLink));

  Debugger* debugger;
  {
    // Construct the underlying C++ object.
    auto dbg = cx->make_unique<Debugger>(cx, obj.get());
    if (!dbg) {
      return false;
    }

    // The object owns the released pointer.
    debugger = dbg.release();
    InitReservedSlot(obj, JSSLOT_DEBUG_DEBUGGER, debugger, MemoryUse::Debugger);
  }

  // Add the initial debuggees, if any.
  for (unsigned i = 0; i < args.length(); i++) {
    JSObject& wrappedObj =
        args[i].toObject().as<ProxyObject>().private_().toObject();
    Rooted<GlobalObject*> debuggee(cx, &wrappedObj.nonCCWGlobal());
    if (!debugger->addDebuggeeGlobal(cx, debuggee)) {
      return false;
    }
  }

  args.rval().setObject(*obj);
  return true;
}

bool Debugger::addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> global) {
  if (debuggees.has(global)) {
    return true;
  }

  // Callers should generally be unable to get a reference to a debugger-
  // invisible global in order to pass it to addDebuggee. But this is possible
  // with certain testing aides we expose in the shell, so just make addDebuggee
  // throw in that case.
  Realm* debuggeeRealm = global->realm();
  if (debuggeeRealm->creationOptions().invisibleToDebugger()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_CANT_DEBUG_GLOBAL);
    return false;
  }

  // Debugger and debuggee must be in different compartments.
  if (debuggeeRealm->compartment() == object->compartment()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_SAME_COMPARTMENT);
    return false;
  }

  // Check for cycles. If global's realm is reachable from this Debugger
  // object's realm by following debuggee-to-debugger links, then adding
  // global would create a cycle. (Typically nobody is debugging the
  // debugger, in which case we zip through this code without looping.)
  Vector<Realm*> visited(cx);
  if (!visited.append(object->realm())) {
    return false;
  }
  for (size_t i = 0; i < visited.length(); i++) {
    Realm* realm = visited[i];
    if (realm == debuggeeRealm) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_LOOP);
      return false;
    }

    // Find all realms containing debuggers debugging realm's global object.
    // Add those realms to visited.
    if (realm->isDebuggee()) {
      JS::AutoAssertNoGC nogc;
      for (Realm::DebuggerVectorEntry& entry : realm->getDebuggers(nogc)) {
        Realm* next = entry.dbg->object->realm();
        if (std::find(visited.begin(), visited.end(), next) == visited.end()) {
          if (!visited.append(next)) {
            return false;
          }
        }
      }
    }
  }

  // For global to become this js::Debugger's debuggee:
  //
  // 1. this js::Debugger must be in global->getDebuggers(),
  // 2. global must be in this->debuggees,
  // 3. the debuggee's zone must be in this->debuggeeZones,
  // 4. if we are tracking allocations, the SavedStacksMetadataBuilder must be
  //    installed for this realm, and
  // 5. Realm::isDebuggee()'s bit must be set.
  //
  // All five indications must be kept consistent.

  AutoRealm ar(cx, global);
  Zone* zone = global->zone();

  RootedObject debuggeeLink(cx, getDebuggeeLink());
  if (!cx->compartment()->wrap(cx, &debuggeeLink)) {
    return false;
  }

  // (1)
  JS::AutoAssertNoGC nogc;
  auto& globalDebuggers = global->getDebuggers(nogc);
  if (!globalDebuggers.append(Realm::DebuggerVectorEntry(this, debuggeeLink))) {
    ReportOutOfMemory(cx);
    return false;
  }
  auto globalDebuggersGuard = MakeScopeExit([&] { globalDebuggers.popBack(); });

  // (2)
  if (!debuggees.put(global)) {
    ReportOutOfMemory(cx);
    return false;
  }
  auto debuggeesGuard = MakeScopeExit([&] { debuggees.remove(global); });

  bool addingZoneRelation = !debuggeeZones.has(zone);

  // (3)
  if (addingZoneRelation && !debuggeeZones.put(zone)) {
    ReportOutOfMemory(cx);
    return false;
  }
  auto debuggeeZonesGuard = MakeScopeExit([&] {
    if (addingZoneRelation) {
      debuggeeZones.remove(zone);
    }
  });

  // (4)
  if (trackingAllocationSites &&
      !Debugger::addAllocationsTracking(cx, global)) {
    return false;
  }

  auto allocationsTrackingGuard = MakeScopeExit([&] {
    if (trackingAllocationSites) {
      Debugger::removeAllocationsTracking(*global);
    }
  });

  // (5)
  AutoRestoreRealmDebugMode debugModeGuard(debuggeeRealm);
  debuggeeRealm->setIsDebuggee();
  debuggeeRealm->updateDebuggerObservesAsmJS();
  debuggeeRealm->updateDebuggerObservesWasm();
  debuggeeRealm->updateDebuggerObservesCoverage();
  if (observesAllExecution() &&
      !ensureExecutionObservabilityOfRealm(cx, debuggeeRealm)) {
    return false;
  }

  globalDebuggersGuard.release();
  debuggeesGuard.release();
  debuggeeZonesGuard.release();
  allocationsTrackingGuard.release();
  debugModeGuard.release();
  return true;
}

void Debugger::recomputeDebuggeeZoneSet() {
  AutoEnterOOMUnsafeRegion oomUnsafe;
  debuggeeZones.clear();
  for (auto range = debuggees.all(); !range.empty(); range.popFront()) {
    if (!debuggeeZones.put(range.front().unbarrieredGet()->zone())) {
      oomUnsafe.crash("Debugger::removeDebuggeeGlobal");
    }
  }
}

template <typename T, typename AP>
static T* findDebuggerInVector(Debugger* dbg, Vector<T, 0, AP>* vec) {
  T* p;
  for (p = vec->begin(); p != vec->end(); p++) {
    if (p->dbg == dbg) {
      break;
    }
  }
  MOZ_ASSERT(p != vec->end());
  return p;
}

void Debugger::removeDebuggeeGlobal(JS::GCContext* gcx, GlobalObject* global,
                                    WeakGlobalObjectSet::Enum* debugEnum,
                                    FromSweep fromSweep) {
  // The caller might have found global by enumerating this->debuggees; if
  // so, use HashSet::Enum::removeFront rather than HashSet::remove below,
  // to avoid invalidating the live enumerator.
  MOZ_ASSERT(debuggees.has(global));
  MOZ_ASSERT(debuggeeZones.has(global->zone()));
  MOZ_ASSERT_IF(debugEnum, debugEnum->front().unbarrieredGet() == global);

  // Clear this global's generators from generatorFrames as well.
  //
  // This method can be called either from script (dbg.removeDebuggee) or during
  // GC sweeping, because the Debugger, debuggee global, or both are being GC'd.
  //
  // When called from script, it's okay to iterate over generatorFrames and
  // touch its keys and values (even when an incremental GC is in progress).
  // When called from GC, it's not okay; the keys and values may be dying. But
  // in that case, we can actually just skip the loop entirely! If the Debugger
  // is going away, it doesn't care about the state of its generatorFrames
  // table, and the Debugger.Frame finalizer will fix up the generator observer
  // counts.
  if (fromSweep == FromSweep::No) {
    for (GeneratorWeakMap::Enum e(generatorFrames); !e.empty(); e.popFront()) {
      AbstractGeneratorObject& genObj = *e.front().key();
      if (&genObj.global() == global) {
        terminateDebuggerFrame(gcx, this, e.front().value(), NullFramePtr(),
                               nullptr, &e);
      }
    }
  }

  for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
    AbstractFramePtr frame = e.front().key();
    if (frame.hasGlobal(global)) {
      terminateDebuggerFrame(gcx, this, e.front().value(), frame, &e);
    }
  }

  JS::AutoAssertNoGC nogc;
  auto& globalDebuggersVector = global->getDebuggers(nogc);

  // The relation must be removed from up to three places:
  // globalDebuggersVector and debuggees for sure, and possibly the
  // compartment's debuggee set.
  //
  // The debuggee zone set is recomputed on demand. This avoids refcounting
  // and in practice we have relatively few debuggees that tend to all be in
  // the same zone. If after recomputing the debuggee zone set, this global's
  // zone is not in the set, then we must remove ourselves from the zone's
  // vector of observing debuggers.
  globalDebuggersVector.erase(
      findDebuggerInVector(this, &globalDebuggersVector));

  if (debugEnum) {
    debugEnum->removeFront();
  } else {
    debuggees.remove(global);
  }

  recomputeDebuggeeZoneSet();

  // Remove all breakpoints for the debuggee.
  Breakpoint* nextbp;
  for (Breakpoint* bp = firstBreakpoint(); bp; bp = nextbp) {
    nextbp = bp->nextInDebugger();

    if (bp->site->realm() == global->realm()) {
      bp->remove(gcx);
    }
  }
  MOZ_ASSERT_IF(debuggees.empty(), !firstBreakpoint());

  // If we are tracking allocation sites, we need to remove the object
  // metadata callback from this global's realm.
  if (trackingAllocationSites) {
    Debugger::removeAllocationsTracking(*global);
  }

  if (!global->realm()->hasDebuggers() &&
      !global->realm()->isTracingExecution()) {
    global->realm()->unsetIsDebuggee();
  } else {
    global->realm()->updateDebuggerObservesAllExecution();
    global->realm()->updateDebuggerObservesAsmJS();
    global->realm()->updateDebuggerObservesWasm();
    global->realm()->updateDebuggerObservesCoverage();
  }
}

class MOZ_STACK_CLASS Debugger::QueryBase {
 protected:
  QueryBase(JSContext* cx, Debugger* dbg)
      : cx(cx),
        debugger(dbg),
        iterMarker(&cx->runtime()->gc),
        realms(cx->zone()) {}

  // The context in which we should do our work.
  JSContext* cx;

  // The debugger for which we conduct queries.
  Debugger* debugger;

  // Require the set of realms to stay fixed while the query is alive.
  gc::AutoEnterIteration iterMarker;

  using RealmSet = HashSet<Realm*, DefaultHasher<Realm*>, ZoneAllocPolicy>;

  // A script must be in one of these realms to match the query.
  RealmSet realms;

  // Indicates whether OOM has occurred while matching.
  bool oom = false;

  bool addRealm(Realm* realm) { return realms.put(realm); }

  // Arrange for this query to match only scripts that run in |global|.
  bool matchSingleGlobal(GlobalObject* global) {
    MOZ_ASSERT(realms.count() == 0);
    if (!addRealm(global->realm())) {
      ReportOutOfMemory(cx);
      return false;
    }
    return true;
  }

  // Arrange for this ScriptQuery to match all scripts running in debuggee
  // globals.
  bool matchAllDebuggeeGlobals() {
    MOZ_ASSERT(realms.count() == 0);
    // Build our realm set from the debugger's set of debuggee globals.
    for (WeakGlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty();
         r.popFront()) {
      if (!addRealm(r.front()->realm())) {
        ReportOutOfMemory(cx);
        return false;
      }
    }
    return true;
  }
};

/*
 * A class for parsing 'findScripts' query arguments and searching for
 * scripts that match the criteria they represent.
 */

class MOZ_STACK_CLASS Debugger::ScriptQuery : public Debugger::QueryBase {
 public:
  /* Construct a ScriptQuery to use matching scripts for |dbg|. */
  ScriptQuery(JSContext* cx, Debugger* dbg)
      : QueryBase(cx, dbg),
        url(cx),
        displayURLString(cx),
        source(cx, AsVariant(static_cast<ScriptSourceObject*>(nullptr))),
        scriptVector(cx, BaseScriptVector(cx)),
        partialMatchVector(cx, BaseScriptVector(cx)),
        wasmInstanceVector(cx, WasmInstanceObjectVector(cx)) {}

  /*
   * Parse the query object |query|, and prepare to match only the scripts
   * it specifies.
   */

  bool parseQuery(HandleObject query) {
    // Check for a 'global' property, which limits the results to those
    // scripts scoped to a particular global object.
    RootedValue global(cx);
    if (!GetProperty(cx, query, query, cx->names().global, &global)) {
      return false;
    }
    if (global.isUndefined()) {
      if (!matchAllDebuggeeGlobals()) {
        return false;
      }
    } else {
      GlobalObject* globalObject = debugger->unwrapDebuggeeArgument(cx, global);
      if (!globalObject) {
        return false;
      }

      // If the given global isn't a debuggee, just leave the set of
      // acceptable globals empty; we'll return no scripts.
      if (debugger->debuggees.has(globalObject)) {
        if (!matchSingleGlobal(globalObject)) {
          return false;
        }
      }
    }

    // Check for a 'url' property.
    if (!GetProperty(cx, query, query, cx->names().url, &url)) {
      return false;
    }
    if (!url.isUndefined() && !url.isString()) {
      JS_ReportErrorNumberASCII(
          cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
          "query object's 'url' property""neither undefined nor a string");
      return false;
    }

    // Check for a 'source' property
    RootedValue debuggerSource(cx);
    if (!GetProperty(cx, query, query, cx->names().source, &debuggerSource)) {
      return false;
    }
    if (!debuggerSource.isUndefined()) {
      if (!debuggerSource.isObject() ||
          !debuggerSource.toObject().is<DebuggerSource>()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_UNEXPECTED_TYPE,
                                  "query object's 'source' property",
                                  "not undefined nor a Debugger.Source object");
        return false;
      }

      DebuggerSource& debuggerSourceObj =
          debuggerSource.toObject().as<DebuggerSource>();

      // If it does have an owner, it should match the Debugger we're
      // calling findScripts on. It would work fine even if it didn't,
      // but mixing Debugger.Sources is probably a sign of confusion.
      if (debuggerSourceObj.owner() != debugger) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_DEBUG_WRONG_OWNER, "Debugger.Source");
        return false;
      }

      hasSource = true;
      source = debuggerSourceObj.getReferent();
    }

    // Check for a 'displayURL' property.
    RootedValue displayURL(cx);
    if (!GetProperty(cx, query, query, cx->names().displayURL, &displayURL)) {
      return false;
    }
    if (!displayURL.isUndefined() && !displayURL.isString()) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_UNEXPECTED_TYPE,
                                "query object's 'displayURL' property",
                                "neither undefined nor a string");
      return false;
    }

    if (displayURL.isString()) {
      displayURLString = displayURL.toString()->ensureLinear(cx);
      if (!displayURLString) {
        return false;
      }
    }

    // Check for a 'line' property.
    RootedValue lineProperty(cx);
    if (!GetProperty(cx, query, query, cx->names().line, &lineProperty)) {
      return false;
    }
    if (lineProperty.isUndefined()) {
      hasLine = false;
    } else if (lineProperty.isNumber()) {
      if (displayURL.isUndefined() && url.isUndefined() && !hasSource) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_QUERY_LINE_WITHOUT_URL,
                                  "'line' property");
        return false;
      }
      if (!parsePositiveInteger(lineProperty, line, JSMSG_DEBUG_BAD_LINE)) {
        return false;
      }
      hasLine = true;
      lineEnd = line;
    } else {
      JS_ReportErrorNumberASCII(
          cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
          "query object's 'line' property""neither undefined nor an integer");
      return false;
    }

    // Check for a 'start' property.
    RootedValue startProperty(cx);
    if (!GetProperty(cx, query, query, cx->names().start, &startProperty)) {
      return false;
    }
    if (startProperty.isObject()) {
      Rooted<JSObject*> startObject(cx, &startProperty.toObject());
      if (!parseLineColumnObject(startObject, "start", line, columnStart)) {
        return false;
      }
      hasLine = true;
    } else if (!startProperty.isUndefined()) {
      JS_ReportErrorNumberASCII(
          cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
          "query object's 'start' property""neither undefined nor an object");
      return false;
    }

    // Check for a 'end' property.
    RootedValue endProperty(cx);
    if (!GetProperty(cx, query, query, cx->names().end, &endProperty)) {
      return false;
    }
    if (endProperty.isObject()) {
      Rooted<JSObject*> endObject(cx, &endProperty.toObject());
      if (!parseLineColumnObject(endObject, "end", lineEnd, columnEnd)) {
        return false;
      }
    } else if (!endProperty.isUndefined()) {
      JS_ReportErrorNumberASCII(
          cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
          "query object's 'end' property""neither undefined nor an object");
      return false;
    }

    if (startProperty.isUndefined() ^ endProperty.isUndefined()) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_QUERY_USE_START_AND_END_TOGETHER);
      return false;
    }

    if (!startProperty.isUndefined()) {
      // endProperty is also not undefined here
      if (displayURL.isUndefined() && url.isUndefined() && !hasSource) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_QUERY_LINE_WITHOUT_URL,
                                  "'start' and 'end' properties");
        return false;
      }
    }

    if (hasLine && lineEnd < line) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_QUERY_START_LINE_IS_AFTER_END);
      return false;
    }

    // Check for an 'innermost' property.
    PropertyName* innermostName = cx->names().innermost;
    RootedValue innermostProperty(cx);
    if (!GetProperty(cx, query, query, innermostName, &innermostProperty)) {
      return false;
    }
    innermost = ToBoolean(innermostProperty);
    if (innermost) {
      // Technically, we need only check hasLine, but this is clearer.
      if ((displayURL.isUndefined() && url.isUndefined() && !hasSource) ||
          !hasLine) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL);
        return false;
      }
    }

    return true;
  }

  /* Set up this ScriptQuery appropriately for a missing query argument. */
  bool omittedQuery() {
    url.setUndefined();
    hasLine = false;
    innermost = false;
    displayURLString = nullptr;
    return matchAllDebuggeeGlobals();
  }

  /*
   * Search all relevant realms and the stack for scripts matching
   * this query, and append the matching scripts to |scriptVector|.
   */

  bool findScripts() {
    if (!prepareQuery()) {
      return false;
    }

    Realm* singletonRealm = nullptr;
    if (realms.count() == 1) {
      singletonRealm = realms.all().front();
    }

    // Search each realm for debuggee scripts.
    MOZ_ASSERT(scriptVector.empty());
    MOZ_ASSERT(partialMatchVector.empty());
    oom = false;
    IterateScripts(cx, singletonRealm, this, considerScript);
    if (oom) {
      ReportOutOfMemory(cx);
      return false;
    }

    // If we are filtering by line number, the lazy BaseScripts were not checked
    // yet since they do not implement `GetScriptLineExtent`. Instead we revisit
    // each result script and delazify its children and add any matching ones to
    // the results list.
    MOZ_ASSERT(hasLine || partialMatchVector.empty());
    Rooted<BaseScript*> script(cx);
    RootedFunction fun(cx);
    while (!partialMatchVector.empty()) {
      script = partialMatchVector.popCopy();

      // As a performance optimization, we can skip scripts that are definitely
      // out-of-bounds for the target line. This was checked before adding to
      // the partialMatchVector, but the bound may have improved since then.
      if (script->extent().sourceEnd <= sourceOffsetLowerBound) {
        continue;
      }

      MOZ_ASSERT(script->isFunction());
      MOZ_ASSERT(script->isReadyForDelazification());

      fun = script->function();

      // Ignore any delazification placeholder functions. These should not be
      // exposed to debugger in any way.
      if (fun->isGhost()) {
        continue;
      }

      // Delazify script.
      JSScript* compiledScript = GetOrCreateFunctionScript(cx, fun);
      if (!compiledScript) {
        return false;
      }

      // If target line isn't in script, we are done with it.
      if (!scriptIsLineMatch(compiledScript)) {
        continue;
      }

      // Add script to results now that we've completed checks.
      if (!scriptVector.append(compiledScript)) {
        return false;
      }

      // If script was a leaf we are done with it. This is an optional
      // optimization to avoid inspecting the `gcthings` list below.
      if (!script->hasInnerFunctions()) {
        continue;
      }

      // Now add inner scripts to `partialMatchVector` work list to determine if
      // they are matches. Note that out IterateScripts callback ignored them
      // already since they did not have a compiled parent at the time.
      for (JS::GCCellPtr thing : script->gcthings()) {
        if (!thing.is<JSObject>() || !thing.as<JSObject>().is<JSFunction>()) {
          continue;
        }
        JSFunction* fun = &thing.as<JSObject>().as<JSFunction>();
        if (!fun->hasBaseScript()) {
          continue;
        }
        BaseScript* inner = fun->baseScript();
        MOZ_ASSERT(inner);
        if (!inner) {
          // If the function doesn't have script, ignore it.
          continue;
        }

        if (!scriptIsPartialLineMatch(inner)) {
          continue;
        }

        // Add the matching inner script to the back of the results queue
        // where it will be processed recursively.
        if (!partialMatchVector.append(inner)) {
          return false;
        }
      }
    }

    // If this is an 'innermost' query, we want to filter the results again to
    // only return the innermost script for each realm. To do this we build a
    // hashmap to track innermost and then recreate the `scriptVector` with the
    // results that remain in the hashmap.
    if (innermost) {
      using RealmToScriptMap =
          GCHashMap<Realm*, BaseScript*, DefaultHasher<Realm*>>;

      Rooted<RealmToScriptMap> innermostForRealm(cx, cx);

      // Visit each candidate script and find innermost in each realm.
      for (BaseScript* script : scriptVector) {
        Realm* realm = script->realm();
        RealmToScriptMap::AddPtr p = innermostForRealm.lookupForAdd(realm);
        if (p) {
          // Is our newly found script deeper than the last one we found?
          BaseScript* incumbent = p->value();
          if (script->asJSScript()->innermostScope()->chainLength() >
              incumbent->asJSScript()->innermostScope()->chainLength()) {
            p->value() = script;
          }
        } else {
          // This is the first matching script we've encountered for this
          // realm, so it is thus the innermost such script.
          if (!innermostForRealm.add(p, realm, script)) {
            return false;
          }
        }
      }

      // Reset the results vector.
      scriptVector.clear();

      // Re-add only the innermost scripts to the results.
      for (RealmToScriptMap::Range r = innermostForRealm.all(); !r.empty();
           r.popFront()) {
        if (!scriptVector.append(r.front().value())) {
          return false;
        }
      }
    }

    // TODO: Until such time that wasm modules are real ES6 modules,
    // unconditionally consider all wasm toplevel instance scripts.
    for (WeakGlobalObjectSet::Range r = debugger->allDebuggees(); !r.empty();
         r.popFront()) {
      for (wasm::Instance* instance : r.front()->realm()->wasm.instances()) {
        consider(instance->object());
        if (oom) {
          ReportOutOfMemory(cx);
          return false;
        }
      }
    }

    return true;
  }

  Handle<BaseScriptVector> foundScripts() const { return scriptVector; }

  Handle<WasmInstanceObjectVector> foundWasmInstances() const {
    return wasmInstanceVector;
  }

 private:
  static const uint32_t LINE_CONSTRAINT_NOT_PROVIDED = 0;

  /* If this is a string, matching scripts have urls equal to it. */
  RootedValue url;

  /* url as a C string. */
  UniqueChars urlCString;

  /* If this is a string, matching scripts' sources have displayURLs equal to
   * it. */

  Rooted<JSLinearString*> displayURLString;

  /*
   * If this is a source referent, matching scripts will have sources equal
   * to this instance. Ideally we'd use a Maybe here, but Maybe interacts
   * very badly with Rooted's LIFO invariant.
   */

  bool hasSource = false;
  Rooted<DebuggerSourceReferent> source;

  /* True if the query contained a 'line' or 'start' property. */
  bool hasLine = false;

  /* The start line of the target range, inclusive. A script's lines must
   * overlap the target line range or it will be filtered out by the query. */

  uint32_t line = LINE_CONSTRAINT_NOT_PROVIDED;

  /* The end line of the target range, inclusive. A script's lines must overlap
   * the target line range or it will be filtered out by the query. */

  uint32_t lineEnd = LINE_CONSTRAINT_NOT_PROVIDED;

  Maybe<JS::LimitedColumnNumberOneOrigin> columnStart;

  Maybe<JS::LimitedColumnNumberOneOrigin> columnEnd;

  // As a performance optimization (and to avoid delazifying as many scripts),
  // we would like to know the source offset of the target range start line.
  //
  // Since we do not have a simple way to compute this precisely, we instead
  // track a lower-bound of the offset value. As we collect SourceExtent
  // examples with (line,column) <-> sourceStart mappings, we can improve the
  // bound. The target range start line is within the range
  // [sourceOffsetLowerBound, Inf).
  //
  // NOTE: Using a SourceExtent for updating the bound happens independently of
  //       if the script matches the target range start line or not in the end.
  mutable uint32_t sourceOffsetLowerBound = 0;

  /* True if the query has an 'innermost' property whose value is true. */
  bool innermost = false;

  /*
   * Accumulate the scripts in an Rooted<BaseScriptVector> instead of creating
   * the JS array as we go, because we mustn't allocate JS objects or GC while
   * we use the CellIter.
   */

  Rooted<BaseScriptVector> scriptVector;

  /*
   * While in the CellIter we may find BaseScripts that need to be compiled
   * before the query can be fully checked. Since we cannot compile while under
   * CellIter we accumulate them here instead.
   *
   * This occurs when matching line numbers since `GetScriptLineExtent` cannot
   * be computed without bytecode existing.
   */

  Rooted<BaseScriptVector> partialMatchVector;

  /*
   * Like above, but for wasm modules.
   */

  Rooted<WasmInstanceObjectVector> wasmInstanceVector;

  /*
   * Given that parseQuery or omittedQuery has been called, prepare to match
   * scripts. Set urlCString and displayURLChars as appropriate.
   */

  bool prepareQuery() {
    // Compute urlCString and displayURLChars, if a url or displayURL was
    // given respectively.
    if (url.isString()) {
      Rooted<JSString*> str(cx, url.toString());
      urlCString = JS_EncodeStringToUTF8(cx, str);
      if (!urlCString) {
        return false;
      }
    }

    return true;
  }

  template <size_t N>
  bool parseLineColumnObject(
      Handle<JSObject*> obj, const char (&propName)[N], uint32_t& lineOut,
      Maybe<JS::LimitedColumnNumberOneOrigin>& columnOut) {
    RootedValue lineProp(cx);
    if (!GetProperty(cx, obj, obj, cx->names().line, &lineProp)) {
      return false;
    }
    if (!lineProp.isNumber()) {
      static const char propMessageFormat[] =
          "query object's '%s.line' property";
      char propMessage[N - 1 /* propName's terminating null */
                       + sizeof(propMessageFormat) - 2 /* '%s' is replaced */];
      DebugOnly<size_t> checkLen =
          SprintfLiteral(propMessage, propMessageFormat, propName);
      MOZ_ASSERT(checkLen == sizeof(propMessage) - 1 /* terminating null */);
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_UNEXPECTED_TYPE, propMessage,
                                "not a number");
      return false;
    }
    if (!parsePositiveInteger(lineProp, lineOut, JSMSG_DEBUG_BAD_LINE)) {
      return false;
    }

    RootedValue columnProp(cx);
    if (!GetProperty(cx, obj, obj, cx->names().column, &columnProp)) {
      return false;
    }
    if (!columnProp.isUndefined()) {
      if (!columnProp.isNumber()) {
        static const char propMessageFormat[] =
            "query object's '%s.column' property";
        char propMessage[N - 1 /* propName's terminating null */
                         + sizeof(propMessageFormat) -
                         2 /* '%s' is replaced */];
        DebugOnly<size_t> checkLen =
            SprintfLiteral(propMessage, propMessageFormat, propName);
        MOZ_ASSERT(checkLen == sizeof(propMessage) - 1 /* terminating null */);
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_UNEXPECTED_TYPE, propMessage,
                                  "not a number");
        return false;
      }
      uint32_t uintColumn = 0;
      if (!parsePositiveInteger(columnProp, uintColumn,
                                JSMSG_BAD_COLUMN_NUMBER)) {
        return false;
      }
      if (uintColumn > JS::LimitedColumnNumberOneOrigin::Limit) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_BAD_COLUMN_NUMBER);
        return false;
      }
      columnOut.emplace(JS::LimitedColumnNumberOneOrigin(uintColumn));
    }
    return true;
  }

  bool parsePositiveInteger(Handle<Value> numberProp, uint32_t& result,
                            JSErrNum errorNumber) {
    double doubleVal = numberProp.toNumber();
    uint32_t uintVal = (uint32_t)doubleVal;
    if (doubleVal <= 0 || uintVal != doubleVal) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber);
      return false;
    }
    result = uintVal;
    return true;
  }

  void updateSourceOffsetLowerBound(const SourceExtent& extent) {
    // We trying to find the offset of (target-range-start-line, 0), so ignore
    // any scripts within the target range.
    MOZ_ASSERT(line != LINE_CONSTRAINT_NOT_PROVIDED &&
               lineEnd != LINE_CONSTRAINT_NOT_PROVIDED);
    MOZ_ASSERT(extent.lineno <= lineEnd);
    if (extent.lineno >= line) {
      return;
    }

    // The extent.sourceStart position is now definitely *before* the target
    // range start line, so update sourceOffsetLowerBound if extent.sourceStart
    // is a tighter bound.
    if (extent.sourceStart > sourceOffsetLowerBound) {
      sourceOffsetLowerBound = extent.sourceStart;
    }
  }

  // A partial match is a script that starts before the target range ends, but
  // may or may not end before the target range starts. We can also return false
  // if we can prove the script ends before the target range starts.
  bool scriptIsPartialLineMatch(BaseScript* script) {
    const SourceExtent& extent = script->extent();

    // We only know for sure that the script is outside the target line range
    // if the start of script is after the target end line, because we don't
    // know how many lines the script has yet.
    MOZ_ASSERT(line != LINE_CONSTRAINT_NOT_PROVIDED &&
               lineEnd != LINE_CONSTRAINT_NOT_PROVIDED);
    MOZ_ASSERT(line <= lineEnd);
    if (extent.lineno > lineEnd) {
      return false;
    }
    if (columnEnd.isSome() && script->lineno() == lineEnd &&
        script->column() > columnEnd.value()) {
      return false;
    }

    // Use the implicit (line, column) <-> sourceStart mapping from the
    // SourceExtent to update our bounds on possible matches. We call this
    // without knowing if the script is a match or not.
    updateSourceOffsetLowerBound(script->extent());

    // As an optional performance optimization, we rule out any script that ends
    // before the lower-bound on where target range start line exists.
    return extent.sourceEnd > sourceOffsetLowerBound;
  }

  // True if any part of script source overlaps the target range.
  bool scriptIsLineMatch(JSScript* script) {
    MOZ_ASSERT(scriptIsPartialLineMatch(script));

    JS::LimitedColumnNumberOneOrigin scriptEndColumn;
    uint32_t lineCount = GetScriptLineExtent(script, &scriptEndColumn);
    if (columnStart.isSome() && script->lineno() + lineCount - 1 == line) {
      if (scriptEndColumn <= columnStart.value()) {
        return false;
      }
    }
    return (script->lineno() + lineCount > line);
  }

  static void considerScript(JSRuntime* rt, void* data, BaseScript* script,
                             const JS::AutoRequireNoGC& nogc) {
    ScriptQuery* self = static_cast<ScriptQuery*>(data);
    self->consider(script, nogc);
  }

  template <typename T>
  [[nodiscard]] bool commonFilter(T script, const JS::AutoRequireNoGC& nogc) {
    if (urlCString) {
      bool gotFilename = false;
      if (script->filename() &&
          strcmp(script->filename(), urlCString.get()) == 0) {
        gotFilename = true;
      }

      bool gotSourceURL = false;
      if (!gotFilename && script->scriptSource()->introducerFilename() &&
          strcmp(script->scriptSource()->introducerFilename(),
                 urlCString.get()) == 0) {
        gotSourceURL = true;
      }
      if (!gotFilename && !gotSourceURL) {
        return false;
      }
    }
    if (displayURLString) {
      if (!script->scriptSource() || !script->scriptSource()->hasDisplayURL()) {
        return false;
      }

      const char16_t* s = script->scriptSource()->displayURL();
      if (CompareChars(s, js_strlen(s), displayURLString) != 0) {
        return false;
      }
    }
    if (hasSource && !(source.is<ScriptSourceObject*>() &&
                       source.as<ScriptSourceObject*>()->source() ==
                           script->scriptSource())) {
      return false;
    }
    return true;
  }

  /*
   * If |script| matches this query, append it to |scriptVector|. Set |oom| if
   * an out of memory condition occurred.
   */

  void consider(BaseScript* script, const JS::AutoRequireNoGC& nogc) {
    if (oom || script->selfHosted()) {
      return;
    }

    Realm* realm = script->realm();
    if (!realms.has(realm)) {
      return;
    }

    if (!commonFilter(script, nogc)) {
      return;
    }

    bool partial = false;

    if (hasLine) {
      if (!scriptIsPartialLineMatch(script)) {
        return;
      }

      if (script->hasBytecode()) {
        // Check if line is within script (or any of its inner scripts).
        if (!scriptIsLineMatch(script->asJSScript())) {
          return;
        }
      } else {
        // GetScriptLineExtent is not available on lazy scripts so instead to
        // the partial match list for be compiled and reprocessed later. We only
        // add scripts that are ready for delazification and they may in turn
        // process their inner functions.
        if (!script->isReadyForDelazification()) {
          return;
        }
        partial = true;
      }
    }

    // If innermost filter is required, we collect everything that matches the
    // line number and filter at the end of `findScripts`.
    MOZ_ASSERT_IF(innermost, hasLine);

    Rooted<BaseScriptVector>& vec = partial ? partialMatchVector : scriptVector;
    if (!vec.append(script)) {
      oom = true;
    }
  }

  /*
   * If |instanceObject| matches this query, append it to |wasmInstanceVector|.
   * Set |oom| if an out of memory condition occurred.
   */

  void consider(WasmInstanceObject* instanceObject) {
    if (oom) {
      return;
    }

    if (hasSource && source != AsVariant(instanceObject)) {
      return;
    }

    if (!wasmInstanceVector.append(instanceObject)) {
      oom = true;
    }
  }
};

bool Debugger::CallData::findScripts() {
  ScriptQuery query(cx, dbg);

  if (args.length() >= 1) {
    RootedObject queryObject(cx, RequireObject(cx, args[0]));
    if (!queryObject || !query.parseQuery(queryObject)) {
      return false;
    }
  } else {
    if (!query.omittedQuery()) {
      return false;
    }
  }

  if (!query.findScripts()) {
    return false;
  }

  Handle<BaseScriptVector> scripts(query.foundScripts());
  Handle<WasmInstanceObjectVector> wasmInstances(query.foundWasmInstances());

  size_t resultLength = scripts.length() + wasmInstances.length();
  Rooted<ArrayObject*> result(cx,
                              NewDenseFullyAllocatedArray(cx, resultLength));
  if (!result) {
    return false;
  }

  result->ensureDenseInitializedLength(0, resultLength);

  for (size_t i = 0; i < scripts.length(); i++) {
    JSObject* scriptObject = dbg->wrapScript(cx, scripts[i]);
    if (!scriptObject) {
      return false;
    }
    result->setDenseElement(i, ObjectValue(*scriptObject));
  }

  size_t wasmStart = scripts.length();
  for (size_t i = 0; i < wasmInstances.length(); i++) {
    JSObject* scriptObject = dbg->wrapWasmScript(cx, wasmInstances[i]);
    if (!scriptObject) {
      return false;
    }
    result->setDenseElement(wasmStart + i, ObjectValue(*scriptObject));
  }

  args.rval().setObject(*result);
  return true;
}

/*
 * A class for searching sources for 'findSources'.
 */

class MOZ_STACK_CLASS Debugger::SourceQuery : public Debugger::QueryBase {
 public:
  using SourceSet = JS::GCHashSet<JSObject*, js::StableCellHasher<JSObject*>,
                                  ZoneAllocPolicy>;

  SourceQuery(JSContext* cx, Debugger* dbg)
      : QueryBase(cx, dbg), sources(cx, SourceSet(cx->zone())) {}

  bool findSources() {
    if (!matchAllDebuggeeGlobals()) {
      return false;
    }

    Realm* singletonRealm = nullptr;
    if (realms.count() == 1) {
      singletonRealm = realms.all().front();
    }

    // Search each realm for debuggee scripts.
    MOZ_ASSERT(sources.empty());
    oom = false;
    IterateScripts(cx, singletonRealm, this, considerScript);
    if (oom) {
      ReportOutOfMemory(cx);
      return false;
    }

    // TODO: Until such time that wasm modules are real ES6 modules,
    // unconditionally consider all wasm toplevel instance scripts.
    for (WeakGlobalObjectSet::Range r = debugger->allDebuggees(); !r.empty();
         r.popFront()) {
      for (wasm::Instance* instance : r.front()->realm()->wasm.instances()) {
        consider(instance->object());
        if (oom) {
          ReportOutOfMemory(cx);
          return false;
        }
      }
    }

    return true;
  }

  Handle<SourceSet> foundSources() const { return sources; }

 private:
  Rooted<SourceSet> sources;

  static void considerScript(JSRuntime* rt, void* data, BaseScript* script,
                             const JS::AutoRequireNoGC& nogc) {
    SourceQuery* self = static_cast<SourceQuery*>(data);
    self->consider(script, nogc);
  }

  void consider(BaseScript* script, const JS::AutoRequireNoGC& nogc) {
    if (oom || script->selfHosted()) {
      return;
    }

    Realm* realm = script->realm();
    if (!realms.has(realm)) {
      return;
    }

    ScriptSourceObject* source = script->sourceObject();
    if (!sources.put(source)) {
      oom = true;
    }
  }

  void consider(WasmInstanceObject* instanceObject) {
    if (oom) {
      return;
    }

    if (!sources.put(instanceObject)) {
      oom = true;
    }
  }
};

static inline DebuggerSourceReferent AsSourceReferent(JSObject* obj) {
  if (obj->is<ScriptSourceObject>()) {
    return AsVariant(&obj->as<ScriptSourceObject>());
  }
  return AsVariant(&obj->as<WasmInstanceObject>());
}

bool Debugger::CallData::findSources() {
  SourceQuery query(cx, dbg);
  if (!query.findSources()) {
    return false;
  }

  Handle<SourceQuery::SourceSet> sources(query.foundSources());

  size_t resultLength = sources.count();
  Rooted<ArrayObject*> result(cx,
                              NewDenseFullyAllocatedArray(cx, resultLength));
  if (!result) {
    return false;
  }

  result->ensureDenseInitializedLength(0, resultLength);

  size_t i = 0;
  for (auto iter = sources.get().iter(); !iter.done(); iter.next()) {
    Rooted<DebuggerSourceReferent> sourceReferent(cx,
                                                  AsSourceReferent(iter.get()));
    RootedObject sourceObject(cx, dbg->wrapVariantReferent(cx, sourceReferent));
    if (!sourceObject) {
      return false;
    }
    result->setDenseElement(i, ObjectValue(*sourceObject));
    i++;
  }

  args.rval().setObject(*result);
  return true;
}

/*
 * A class for parsing 'findObjects' query arguments and searching for objects
 * that match the criteria they represent.
 */

class MOZ_STACK_CLASS Debugger::ObjectQuery {
 public:
  /* Construct an ObjectQuery to use matching scripts for |dbg|. */
  ObjectQuery(JSContext* cx, Debugger* dbg)
      : objects(cx),
        cx(cx),
        dbg(dbg),
        queryType(QueryType::None),
        jsClassName(cx),
        unwrappedCtorOrProto(cx) {}

  /* The vector that we are accumulating results in. */
  RootedObjectVector objects;

  /* The set of debuggee compartments. */
  JS::CompartmentSet debuggeeCompartments;

  /*
   * Parse the query object |query|, and prepare to match only the objects it
   * specifies.
   */

  bool parseQuery(HandleObject query) {
    // Check for the 'class' property
    RootedValue cls(cx);
    if (!GetProperty(cx, query, query, cx->names().class_, &cls)) {
      return false;
    }

    if (cls.isUndefined()) {
      return true;
    }

    if (cls.isString()) {
      JSLinearString* str = cls.toString()->ensureLinear(cx);
      if (!str) {
        return false;
      }
      if (!StringIsAscii(str)) {
        JS_ReportErrorNumberASCII(
            cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
            "query object's 'class' property string",
            "not a string containing only ASCII characters");
        return false;
      }
      jsClassName = cls;
      queryType = QueryType::JSClassName;
      return true;
    }

    if (cls.isObject()) {
      JS::Rooted<JSObject*> obj(cx, &cls.toObject());
      obj = UncheckedUnwrap(obj);
      if (JS_IsDeadWrapper(obj)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_DEAD_OBJECT);
        return false;
      }
      if (!obj->is<DebuggerObject>()) {
        JS_ReportErrorNumberASCII(
            cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
            "query object's 'class' property object""not Debugger.Object");
        return false;
      }

      unwrappedCtorOrProto = obj->as<DebuggerObject>().referent();
      unwrappedCtorOrProto = UncheckedUnwrap(unwrappedCtorOrProto);
      if (JS_IsDeadWrapper(unwrappedCtorOrProto)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_DEAD_OBJECT);
        return false;
      }
      queryType = QueryType::CtorOrProto;
      return true;
    }

    JS_ReportErrorNumberASCII(
        cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
        "query object's 'class' property",
        "none of JSClass name string, constructor/prototype debuggee object, "
        "or undefined");
    return false;
  }

  /* Set up this ObjectQuery appropriately for a missing query argument. */
  void omittedQuery() {
    jsClassName.setUndefined();
    unwrappedCtorOrProto = nullptr;
    queryType = QueryType::None;
  }

  /*
   * Traverse the heap to find all relevant objects and add them to the
   * provided vector.
   */

  bool findObjects() {
    if (!prepareQuery()) {
      return false;
    }

    for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
         r.popFront()) {
      if (!debuggeeCompartments.put(r.front()->compartment())) {
        ReportOutOfMemory(cx);
        return false;
      }
    }

    {
      // We can't tolerate the GC moving things around while we're
      // searching the heap. Check that nothing we do causes a GC.
      RootedObject dbgObj(cx, dbg->object);
      JS::ubi::RootList rootList(cx);
      auto [ok, nogc] = rootList.init(dbgObj);
      if (!ok) {
        ReportOutOfMemory(cx);
        return false;
      }

      Traversal traversal(cx, *this, nogc);
      traversal.wantNames = false;

      if (!traversal.addStart(JS::ubi::Node(&rootList)) ||
          !traversal.traverse()) {
        ReportOutOfMemory(cx);
        return false;
      }
      return true;
    }
  }

  /*
   * |ubi::Node::BreadthFirst| interface.
   */

  class NodeData {};
  using Traversal = JS::ubi::BreadthFirst<ObjectQuery>;
  bool operator()(Traversal& traversal, JS::ubi::Node origin,
                  const JS::ubi::Edge& edge, NodeData*, bool first) {
    if (!first) {
      return true;
    }

    JS::ubi::Node referent = edge.referent;

    // Only follow edges within our set of debuggee compartments; we don't
    // care about the heap's subgraphs outside of our debuggee compartments,
    // so we abandon the referent. Either (1) there is not a path from this
    // non-debuggee node back to a node in our debuggee compartments, and we
    // don't need to follow edges to or from this node, or (2) there does
    // exist some path from this non-debuggee node back to a node in our
    // debuggee compartments. However, if that were true, then the incoming
    // cross compartment edge back into a debuggee compartment is already
    // listed as an edge in the RootList we started traversal with, and
    // therefore we don't need to follow edges to or from this non-debuggee
    // node.
    JS::Compartment* comp = referent.compartment();
    if (comp && !debuggeeCompartments.has(comp)) {
      traversal.abandonReferent();
      return true;
    }

    // If the referent has an associated realm and it's not a debuggee
    // realm, skip it. Don't abandonReferent() here like above: realms
    // within a compartment can reference each other without going through
    // cross-compartment wrappers.
    Realm* realm = referent.realm();
    if (realm && !dbg->isDebuggeeUnbarriered(realm)) {
      return true;
    }

    // If the referent is an object and matches our query's restrictions,
    // add it to the vector accumulating results. Skip objects that should
    // never be exposed to JS, like EnvironmentObjects and internal
    // functions.

    if (!referent.is<JSObject>() || referent.exposeToJS().isUndefined()) {
      return true;
    }

    JSObject* obj = referent.as<JSObject>();

    switch (queryType) {
      case QueryType::None:
        break;
      case QueryType::JSClassName: {
        const char* objJSClassName = obj->getClass()->name;
        if (strcmp(objJSClassName, jsClassNameCString.get()) != 0) {
          return true;
        }
        break;
      }
      case QueryType::CtorOrProto:
        if (!hasConstructorOrPrototype(obj, unwrappedCtorOrProto, cx)) {
          return true;
        }
        break;
    }

    return objects.append(obj);
  }

  // Returns true if `obj` is confirmed to have `ctorOrProto` as its
  // constructor or prototype in the prototype chain.
  //
  // If it requires side-effect-ful operation for accessing the constructor or
  // prototype, this can return false even if `obj instanceof ctorOrProto` is
  // actually `true`.
  static bool hasConstructorOrPrototype(JSObject* obj, JSObject* ctorOrProto,
                                        JSContext* cx) {
    obj = UncheckedUnwrap(obj);

    while (true) {
      if (!obj->hasStaticPrototype()) {
        // Dynamic prototype cannot be matched without side-effect.
        break;
      }

      JSObject* proto = obj->staticPrototype();
      if (!proto) {
        break;
      }
      proto = UncheckedUnwrap(proto);
      if (proto == ctorOrProto) {
        return true;
      }

      JS::Value ctorVal;
      bool result;
      {
        AutoRealm ar(cx, proto);
        result = GetPropertyPure(cx, proto, NameToId(cx->names().constructor),
                                 &ctorVal);
      }
      if (result && ctorVal.isObject()) {
        JSObject* ctor = &ctorVal.toObject();
        ctor = UncheckedUnwrap(ctor);
        if (ctor == ctorOrProto) {
          return true;
        }
      }

      obj = proto;
    }

    return false;
  }

 private:
  /* The context in which we should do our work. */
  JSContext* cx;

  /* The debugger for which we conduct queries. */
  Debugger* dbg;

  enum class QueryType {
    /* No filtering. */
    None,

    /* Match objects with given JSClass name. */
    JSClassName,

    /* Match objects with given object as constructor or prototype. */
    CtorOrProto,
  };
  QueryType queryType;

  /* Matching objects will have a JSClass whose name is this property. */
  RootedValue jsClassName;

  /* The jsClassName member, as a C string. */
  UniqueChars jsClassNameCString;

  /* Matching objects will have given object as constructor or prototype. */
  JS::Rooted<JSObject*> unwrappedCtorOrProto;

  /*
   * Given that either omittedQuery or parseQuery has been called, prepare the
   * query for matching objects.
   */

  bool prepareQuery() {
    if (jsClassName.isString()) {
      jsClassNameCString = JS_EncodeStringToASCII(cx, jsClassName.toString());
      if (!jsClassNameCString) {
        return false;
      }
    }

    return true;
  }
};

bool Debugger::CallData::findObjects() {
  ObjectQuery query(cx, dbg);

  if (args.length() >= 1) {
    RootedObject queryObject(cx, RequireObject(cx, args[0]));
    if (!queryObject || !query.parseQuery(queryObject)) {
      return false;
    }
  } else {
    query.omittedQuery();
  }

  if (!query.findObjects()) {
    return false;
  }

  // Returning internal objects (such as self-hosting intrinsics) to JS is not
  // fuzzing-safe. We still want to call parseQuery/findObjects when fuzzing so
  // just clear the Vector here.
  if (fuzzingSafe) {
    query.objects.clear();
  }

  size_t length = query.objects.length();
  Rooted<ArrayObject*> result(cx, NewDenseFullyAllocatedArray(cx, length));
  if (!result) {
    return false;
  }

  result->ensureDenseInitializedLength(0, length);

  for (size_t i = 0; i < length; i++) {
    RootedValue debuggeeVal(cx, ObjectValue(*query.objects[i]));
    if (!dbg->wrapDebuggeeValue(cx, &debuggeeVal)) {
      return false;
    }
    result->setDenseElement(i, debuggeeVal);
  }

  args.rval().setObject(*result);
  return true;
}

bool Debugger::CallData::findAllGlobals() {
  RootedObjectVector globals(cx);

  {
    // Accumulate the list of globals before wrapping them, because
    // wrapping can GC and collect realms from under us, while iterating.
    JS::AutoCheckCannotGC nogc;

    for (RealmsIter r(cx->runtime()); !r.done(); r.next()) {
      if (r->creationOptions().invisibleToDebugger()) {
        continue;
      }

      if (!r->hasInitializedGlobal()) {
        continue;
      }

      if (JS::RealmBehaviorsRef(r).isNonLive()) {
        continue;
      }

      r->compartment()->gcState.scheduledForDestruction = false;

      GlobalObject* global = r->maybeGlobal();

      // We pulled |global| out of nowhere, so it's possible that it was
      // marked gray by XPConnect. Since we're now exposing it to JS code,
      // we need to mark it black.
      JS::ExposeObjectToActiveJS(global);
      if (!globals.append(global)) {
        return false;
      }
    }
  }

  RootedObject result(cx, NewDenseEmptyArray(cx));
  if (!result) {
    return false;
  }

  for (size_t i = 0; i < globals.length(); i++) {
    RootedValue globalValue(cx, ObjectValue(*globals[i]));
    if (!dbg->wrapDebuggeeValue(cx, &globalValue)) {
      return false;
    }
    if (!NewbornArrayPush(cx, result, globalValue)) {
      return false;
    }
  }

  args.rval().setObject(*result);
  return true;
}

bool Debugger::CallData::findSourceURLs() {
  RootedObject result(cx, NewDenseEmptyArray(cx));
  if (!result) {
    return false;
  }

  for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
       r.popFront()) {
    RootedObject holder(cx, r.front()->getSourceURLsHolder());
    if (holder) {
      for (size_t i = 0; i < holder->as<ArrayObject>().length(); i++) {
        Value v = holder->as<ArrayObject>().getDenseElement(i);

        // The value is an atom and doesn't need wrapping, but the holder may be
        // in another zone and the atom must be marked when we create a
        // reference in this zone.
        MOZ_ASSERT(v.isString() && v.toString()->isAtom());
        cx->markAtomValue(v);

        if (!NewbornArrayPush(cx, result, v)) {
          return false;
        }
      }
    }
  }

  args.rval().setObject(*result);
  return true;
}

bool Debugger::CallData::makeGlobalObjectReference() {
  if (!args.requireAtLeast(cx, "Debugger.makeGlobalObjectReference", 1)) {
    return false;
  }

  Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
  if (!global) {
    return false;
  }

  // If we create a D.O referring to a global in an invisible realm,
  // then from it we can reach function objects, scripts, environments, etc.,
  // none of which we're ever supposed to see.
  if (global->realm()->creationOptions().invisibleToDebugger()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_INVISIBLE_COMPARTMENT);
    return false;
  }

  args.rval().setObject(*global);
  return dbg->wrapDebuggeeValue(cx, args.rval());
}

bool Debugger::isCompilableUnit(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!args.requireAtLeast(cx, "Debugger.isCompilableUnit", 1)) {
    return false;
  }

  if (!args[0].isString()) {
    JS_ReportErrorNumberASCII(
        cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
        "Debugger.isCompilableUnit""string", InformalValueTypeName(args[0]));
    return false;
  }

  JSString* str = args[0].toString();
  size_t length = str->length();

  AutoStableStringChars chars(cx);
  if (!chars.initTwoByte(cx, str)) {
    return false;
  }

  bool result = true;

  AutoReportFrontendContext fc(cx,
                               AutoReportFrontendContext::Warning::Suppress);
  CompileOptions options(cx);
  Rooted<frontend::CompilationInput> input(cx,
                                           frontend::CompilationInput(options));
  if (!input.get().initForGlobal(&fc)) {
    return false;
  }

  LifoAllocScope allocScope(&cx->tempLifoAlloc());
  frontend::NoScopeBindingCache scopeCache;
  frontend::CompilationState compilationState(&fc, allocScope, input.get());
  if (!compilationState.init(&fc, &scopeCache)) {
    return false;
  }

  frontend::Parser<frontend::FullParseHandler, char16_t> parser(
      &fc, options, chars.twoByteChars(), length, compilationState,
      /* syntaxParser = */ nullptr);
  if (!parser.checkOptions() || parser.parse().isErr()) {
    // We ran into an error. If it was because we ran out of memory we report
    // it in the usual way.
    if (fc.hadOutOfMemory()) {
      return false;
    }

    // If it was because we ran out of source, we return false so our caller
    // knows to try to collect more [source].
    if (parser.isUnexpectedEOF()) {
      result = false;
    }

    fc.clearAutoReport();
  }

  args.rval().setBoolean(result);
  return true;
}

bool Debugger::CallData::adoptDebuggeeValue() {
  if (!args.requireAtLeast(cx, "Debugger.adoptDebuggeeValue", 1)) {
    return false;
  }

  RootedValue v(cx, args[0]);
  if (v.isObject()) {
    RootedObject obj(cx, &v.toObject());
    DebuggerObject* ndobj = ToNativeDebuggerObject(cx, &obj);
    if (!ndobj) {
      return false;
    }

    obj.set(ndobj->referent());
    v = ObjectValue(*obj);

    if (!dbg->wrapDebuggeeValue(cx, &v)) {
      return false;
    }
  }

  args.rval().set(v);
  return true;
}

class DebuggerAdoptSourceMatcher {
  JSContext* cx_;
  Debugger* dbg_;

 public:
  explicit DebuggerAdoptSourceMatcher(JSContext* cx, Debugger* dbg)
      : cx_(cx), dbg_(dbg) {}

  using ReturnType = DebuggerSource*;

  ReturnType match(Handle<ScriptSourceObject*> source) {
    if (source->compartment() == cx_->compartment()) {
      JS_ReportErrorASCII(cx_,
                          "Source is in the same compartment as this debugger");
      return nullptr;
    }
    return dbg_->wrapSource(cx_, source);
  }
  ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
    if (wasmInstance->compartment() == cx_->compartment()) {
      JS_ReportErrorASCII(
          cx_, "WasmInstance is in the same compartment as this debugger");
      return nullptr;
    }
    return dbg_->wrapWasmSource(cx_, wasmInstance);
  }
};

bool Debugger::CallData::adoptFrame() {
  if (!args.requireAtLeast(cx, "Debugger.adoptFrame", 1)) {
    return false;
  }

  RootedObject obj(cx, RequireObject(cx, args[0]));
  if (!obj) {
    return false;
  }

  obj = UncheckedUnwrap(obj);
  if (!obj->is<DebuggerFrame>()) {
    JS_ReportErrorASCII(cx, "Argument is not a Debugger.Frame");
    return false;
  }

  RootedValue objVal(cx, ObjectValue(*obj));
  Rooted<DebuggerFrame*> frameObj(cx, DebuggerFrame::check(cx, objVal));
  if (!frameObj) {
    return false;
  }

  Rooted<DebuggerFrame*> adoptedFrame(cx);
  if (frameObj->isOnStack()) {
    FrameIter iter = frameObj->getFrameIter(cx);
    if (!dbg->observesFrame(iter)) {
      JS_ReportErrorASCII(cx, "Debugger.Frame's global is not a debuggee");
      return false;
    }
    if (!dbg->getFrame(cx, iter, &adoptedFrame)) {
      return false;
    }
  } else if (frameObj->isSuspended()) {
    Rooted<AbstractGeneratorObject*> gen(cx, &frameObj->unwrappedGenerator());
    if (!dbg->observesGlobal(&gen->global())) {
      JS_ReportErrorASCII(cx, "Debugger.Frame's global is not a debuggee");
      return false;
    }

    if (!dbg->getFrame(cx, gen, &adoptedFrame)) {
      return false;
    }
  } else {
    if (!dbg->getFrame(cx, &adoptedFrame)) {
      return false;
    }
  }

  args.rval().setObject(*adoptedFrame);
  return true;
}

bool Debugger::CallData::adoptSource() {
  if (!args.requireAtLeast(cx, "Debugger.adoptSource", 1)) {
    return false;
  }

  RootedObject obj(cx, RequireObject(cx, args[0]));
  if (!obj) {
    return false;
  }

  obj = UncheckedUnwrap(obj);
  if (!obj->is<DebuggerSource>()) {
    JS_ReportErrorASCII(cx, "Argument is not a Debugger.Source");
    return false;
  }

  Rooted<DebuggerSource*> sourceObj(cx, &obj->as<DebuggerSource>());
  if (!sourceObj->getReferentRawObject()) {
    JS_ReportErrorASCII(cx, "Argument is Debugger.Source.prototype");
    return false;
  }

  Rooted<DebuggerSourceReferent> referent(cx, sourceObj->getReferent());

  DebuggerAdoptSourceMatcher matcher(cx, dbg);
  DebuggerSource* res = referent.match(matcher);
  if (!res) {
    return false;
  }

  args.rval().setObject(*res);
  return true;
}

bool Debugger::CallData::enableAsyncStack() {
  if (!args.requireAtLeast(cx, "Debugger.enableAsyncStack", 1)) {
    return false;
  }
  Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
  if (!global) {
    return false;
  }

  global->realm()->isAsyncStackCapturingEnabled = true;

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::disableAsyncStack() {
  if (!args.requireAtLeast(cx, "Debugger.disableAsyncStack", 1)) {
    return false;
  }
  Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
  if (!global) {
    return false;
  }

  global->realm()->isAsyncStackCapturingEnabled = false;

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::enableUnlimitedStacksCapturing() {
  if (!args.requireAtLeast(cx, "Debugger.enableUnlimitedStacksCapturing", 1)) {
    return false;
  }
  Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
  if (!global) {
    return false;
  }

  global->realm()->isUnlimitedStacksCapturingEnabled = true;

  args.rval().setUndefined();
  return true;
}

bool Debugger::CallData::disableUnlimitedStacksCapturing() {
  if (!args.requireAtLeast(cx, "Debugger.disableUnlimitedStacksCapturing", 1)) {
    return false;
  }
  Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
  if (!global) {
    return false;
  }

  global->realm()->isUnlimitedStacksCapturingEnabled = false;

  args.rval().setUndefined();
  return true;
}

const JSPropertySpec Debugger::properties[] = {
    JS_DEBUG_PSGS("onDebuggerStatement", getOnDebuggerStatement,
                  setOnDebuggerStatement),
    JS_DEBUG_PSGS("onExceptionUnwind", getOnExceptionUnwind,
                  setOnExceptionUnwind),
    JS_DEBUG_PSGS("onNewScript", getOnNewScript, setOnNewScript),
    JS_DEBUG_PSGS("onNewPromise", getOnNewPromise, setOnNewPromise),
    JS_DEBUG_PSGS("onPromiseSettled", getOnPromiseSettled, setOnPromiseSettled),
    JS_DEBUG_PSGS("onEnterFrame", getOnEnterFrame, setOnEnterFrame),
    JS_DEBUG_PSGS("onNativeCall", getOnNativeCall, setOnNativeCall),
    JS_DEBUG_PSGS("shouldAvoidSideEffects", getShouldAvoidSideEffects,
                  setShouldAvoidSideEffects),
    JS_DEBUG_PSGS("onNewGlobalObject", getOnNewGlobalObject,
                  setOnNewGlobalObject),
    JS_DEBUG_PSGS("uncaughtExceptionHook", getUncaughtExceptionHook,
                  setUncaughtExceptionHook),
    JS_DEBUG_PSGS("allowUnobservedAsmJS", getAllowUnobservedAsmJS,
                  setAllowUnobservedAsmJS),
    JS_DEBUG_PSGS("allowUnobservedWasm", getAllowUnobservedWasm,
                  setAllowUnobservedWasm),
    JS_DEBUG_PSGS("collectCoverageInfo", getCollectCoverageInfo,
                  setCollectCoverageInfo),
    JS_DEBUG_PSGS("exclusiveDebuggerOnEval", getExclusiveDebuggerOnEval,
                  setExclusiveDebuggerOnEval),
    JS_DEBUG_PSGS("inspectNativeCallArguments", getInspectNativeCallArguments,
                  setInspectNativeCallArguments),
    JS_DEBUG_PSG("memory", getMemory),
    JS_STRING_SYM_PS(toStringTag, "Debugger", JSPROP_READONLY),
    JS_PS_END,
};

const JSFunctionSpec Debugger::methods[] = {
    JS_DEBUG_FN("addDebuggee", addDebuggee, 1),
    JS_DEBUG_FN("addAllGlobalsAsDebuggees", addAllGlobalsAsDebuggees, 0),
    JS_DEBUG_FN("removeDebuggee", removeDebuggee, 1),
    JS_DEBUG_FN("removeAllDebuggees", removeAllDebuggees, 0),
    JS_DEBUG_FN("hasDebuggee", hasDebuggee, 1),
    JS_DEBUG_FN("getDebuggees", getDebuggees, 0),
    JS_DEBUG_FN("getNewestFrame", getNewestFrame, 0),
    JS_DEBUG_FN("clearAllBreakpoints", clearAllBreakpoints, 0),
    JS_DEBUG_FN("findScripts", findScripts, 1),
    JS_DEBUG_FN("findSources", findSources, 1),
    JS_DEBUG_FN("findObjects", findObjects, 1),
    JS_DEBUG_FN("findAllGlobals", findAllGlobals, 0),
    JS_DEBUG_FN("findSourceURLs", findSourceURLs, 0),
    JS_DEBUG_FN("makeGlobalObjectReference", makeGlobalObjectReference, 1),
    JS_DEBUG_FN("adoptDebuggeeValue", adoptDebuggeeValue, 1),
    JS_DEBUG_FN("adoptFrame", adoptFrame, 1),
    JS_DEBUG_FN("adoptSource", adoptSource, 1),
    JS_DEBUG_FN("enableAsyncStack", enableAsyncStack, 1),
    JS_DEBUG_FN("disableAsyncStack", disableAsyncStack, 1),
    JS_DEBUG_FN("enableUnlimitedStacksCapturing",
                enableUnlimitedStacksCapturing, 1),
    JS_DEBUG_FN("disableUnlimitedStacksCapturing",
                disableUnlimitedStacksCapturing, 1),
    JS_FS_END,
};

const JSFunctionSpec Debugger::static_methods[]{
    JS_FN("isCompilableUnit", Debugger::isCompilableUnit, 1, 0),
    JS_FS_END,
};

DebuggerScript* Debugger::newDebuggerScript(
    JSContext* cx, Handle<DebuggerScriptReferent> referent) {
  cx->check(object.get());

  RootedObject proto(
      cx, &object->getReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO).toObject());
  MOZ_ASSERT(proto);
  Rooted<NativeObject*> debugger(cx, object);

  return DebuggerScript::create(cx, proto, referent, debugger);
}

template <typename ReferentType, typename Map>
typename Map::WrapperType* Debugger::wrapVariantReferent(
    JSContext* cx, Map& map,
    Handle<typename Map::WrapperType::ReferentVariant> referent) {
  cx->check(object);

  Handle<ReferentType*> untaggedReferent =
      referent.template as<ReferentType*>();
  MOZ_ASSERT(cx->compartment() != untaggedReferent->compartment());

  DependentAddPtr<Map> p(cx, map, untaggedReferent);
  if (!p) {
    typename Map::WrapperType* wrapper = newVariantWrapper(cx, referent);
    if (!wrapper) {
      return nullptr;
    }

    if (!p.add(cx, map, untaggedReferent, wrapper)) {
      // We need to destroy the edge to the referent, to avoid trying to trace
      // it during untimely collections.
      wrapper->clearReferent();
      return nullptr;
    }
  }

  return &p->value()->template as<typename Map::WrapperType>();
}

DebuggerScript* Debugger::wrapVariantReferent(
    JSContext* cx, Handle<DebuggerScriptReferent> referent) {
  if (referent.is<BaseScript*>()) {
    return wrapVariantReferent<BaseScript>(cx, scripts, referent);
  }

  return wrapVariantReferent<WasmInstanceObject>(cx, wasmInstanceScripts,
                                                 referent);
}

DebuggerScript* Debugger::wrapScript(JSContext* cx,
                                     Handle<BaseScript*> script) {
  Rooted<DebuggerScriptReferent> referent(cx,
                                          DebuggerScriptReferent(script.get()));
  return wrapVariantReferent(cx, referent);
}

DebuggerScript* Debugger::wrapWasmScript(
    JSContext* cx, Handle<WasmInstanceObject*> wasmInstance) {
  Rooted<DebuggerScriptReferent> referent(cx, wasmInstance.get());
  return wrapVariantReferent(cx, referent);
}

DebuggerSource* Debugger::newDebuggerSource(
    JSContext* cx, Handle<DebuggerSourceReferent> referent) {
  cx->check(object.get());

  RootedObject proto(
      cx, &object->getReservedSlot(JSSLOT_DEBUG_SOURCE_PROTO).toObject());
  MOZ_ASSERT(proto);
  Rooted<NativeObject*> debugger(cx, object);
  return DebuggerSource::create(cx, proto, referent, debugger);
}

DebuggerSource* Debugger::wrapVariantReferent(
    JSContext* cx, Handle<DebuggerSourceReferent> referent) {
  DebuggerSource* obj;
  if (referent.is<ScriptSourceObject*>()) {
    obj = wrapVariantReferent<ScriptSourceObject>(cx, sources, referent);
  } else {
    obj = wrapVariantReferent<WasmInstanceObject>(cx, wasmInstanceSources,
                                                  referent);
  }
  MOZ_ASSERT_IF(obj, obj->getReferent() == referent);
  return obj;
}

DebuggerSource* Debugger::wrapSource(JSContext* cx,
                                     Handle<ScriptSourceObject*> source) {
  Rooted<DebuggerSourceReferent> referent(cx, source.get());
  return wrapVariantReferent(cx, referent);
}

DebuggerSource* Debugger::wrapWasmSource(
    JSContext* cx, Handle<WasmInstanceObject*> wasmInstance) {
  Rooted<DebuggerSourceReferent> referent(cx, wasmInstance.get());
  return wrapVariantReferent(cx, referent);
}

bool Debugger::observesFrame(AbstractFramePtr frame) const {
  if (frame.isWasmDebugFrame()) {
    return observesWasm(frame.wasmInstance());
  }

  return observesScript(frame.script());
}

bool Debugger::observesFrame(const FrameIter& iter) const {
  // Skip frames not yet fully initialized during their prologue.
  if (iter.isInterp() && iter.isFunctionFrame()) {
    const Value& thisVal = iter.interpFrame()->thisArgument();
    if (thisVal.isMagic() && thisVal.whyMagic() == JS_IS_CONSTRUCTING) {
      return false;
    }
  }
  if (iter.isWasm()) {
    // Skip frame of wasm instances we cannot observe.
    if (!iter.wasmDebugEnabled()) {
      return false;
    }
    return observesWasm(iter.wasmInstance());
  }
  return observesScript(iter.script());
}

bool Debugger::observesScript(JSScript* script) const {
  // Don't ever observe self-hosted scripts: the Debugger API can break
  // self-hosted invariants.
  return observesGlobal(&script->global()) && !script->selfHosted();
}

bool Debugger::observesWasm(wasm::Instance* instance) const {
  if (!instance->debugEnabled()) {
    return false;
  }
  return observesGlobal(&instance->object()->global());
}

/* static */
bool Debugger::replaceFrameGuts(JSContext* cx, AbstractFramePtr from,
                                AbstractFramePtr to, ScriptFrameIter& iter) {
  MOZ_ASSERT(from != to);

  // Rekey missingScopes to maintain Debugger.Environment identity and
  // forward liveScopes to point to the new frame.
  DebugEnvironments::forwardLiveFrame(cx, from, to);

  // If we hit an OOM anywhere in here, we need to make sure there aren't any
  // Debugger.Frame objects left partially-initialized.
  auto terminateDebuggerFramesOnExit = MakeScopeExit([&] {
    terminateDebuggerFrames(cx, from);
    terminateDebuggerFrames(cx, to);

    MOZ_ASSERT(!DebugAPI::inFrameMaps(from));
    MOZ_ASSERT(!DebugAPI::inFrameMaps(to));
  });

  // Forward live Debugger.Frame objects.
  Rooted<DebuggerFrameVector> frames(cx);
  if (!getDebuggerFrames(from, &frames)) {
    // An OOM here means that all Debuggers' frame maps still contain
    // entries for 'from' and no entries for 'to'. Since the 'from' frame
    // will be gone, they are removed by terminateDebuggerFramesOnExit
    // above.
    ReportOutOfMemory(cx);
    return false;
  }

  for (size_t i = 0; i < frames.length(); i++) {
    Handle<DebuggerFrame*> frameobj = frames[i];
    Debugger* dbg = frameobj->owner();

    // Update frame object's ScriptFrameIter::data pointer.
    if (!frameobj->replaceFrameIterData(cx, iter)) {
      return false;
    }

    // Add the frame object with |to| as key.
    if (!dbg->frames.putNew(to, frameobj)) {
      ReportOutOfMemory(cx);
      return false;
    }

    // Remove the old frame entry after all fallible operations are completed
    // so that an OOM will be able to clean up properly.
    dbg->frames.remove(from);
  }

  // All frames successfuly replaced, cancel the rollback.
  terminateDebuggerFramesOnExit.release();

  MOZ_ASSERT(!DebugAPI::inFrameMaps(from));
  MOZ_ASSERT_IF(!frames.empty(), DebugAPI::inFrameMaps(to));
  return true;
}

/* static */
bool DebugAPI::inFrameMaps(AbstractFramePtr frame) {
  bool foundAny = false;
  JS::AutoAssertNoGC nogc;
  Debugger::forEachOnStackDebuggerFrame(
      frame, nogc,
      [&](Debugger*, DebuggerFrame* frameobj) { foundAny = true; });
  return foundAny;
}

/* static */
void Debugger::suspendGeneratorDebuggerFrames(JSContext* cx,
                                              AbstractFramePtr frame) {
  JS::GCContext* gcx = cx->gcContext();
  JS::AutoAssertNoGC nogc;
  forEachOnStackDebuggerFrame(
      frame, nogc, [&](Debugger* dbg, DebuggerFrame* dbgFrame) {
        dbg->frames.remove(frame);

#if DEBUG
        MOZ_ASSERT(dbgFrame->hasGeneratorInfo());
        AbstractGeneratorObject& genObj = dbgFrame->unwrappedGenerator();
        GeneratorWeakMap::Ptr p = dbg->generatorFrames.lookup(&genObj);
        MOZ_ASSERT(p);
        MOZ_ASSERT(p->value() == dbgFrame);
#endif

        dbgFrame->suspend(gcx);
      });
}

/* static */
void Debugger::terminateDebuggerFrames(JSContext* cx, AbstractFramePtr frame) {
  JS::GCContext* gcx = cx->gcContext();

  JS::AutoAssertNoGC nogc;
  forEachOnStackOrSuspendedDebuggerFrame(
      cx, frame, nogc, [&](Debugger* dbg, DebuggerFrame* dbgFrame) {
        Debugger::terminateDebuggerFrame(gcx, dbg, dbgFrame, frame);
      });

  // If this is an eval frame, then from the debugger's perspective the
  // script is about to be destroyed. Remove any breakpoints in it.
  if (frame.isEvalFrame()) {
    RootedScript script(cx, frame.script());
    DebugScript::clearBreakpointsIn(cx->gcContext(), script, nullptr, nullptr);
  }
}

/* static */
void Debugger::terminateDebuggerFrame(
    JS::GCContext* gcx, Debugger* dbg, DebuggerFrame* dbgFrame,
    AbstractFramePtr frame, FrameMap::Enum* maybeFramesEnum,
    GeneratorWeakMap::Enum* maybeGeneratorFramesEnum) {
  // If we were not passed the frame, either we are destroying a frame early
  // on before it was inserted into the "frames" list, or else we are
  // terminating a frame from "generatorFrames" and the "frames" entries will
  // be cleaned up later on with a second call to this function.
  MOZ_ASSERT_IF(!frame, !maybeFramesEnum);
  MOZ_ASSERT_IF(!frame, dbgFrame->hasGeneratorInfo());
  MOZ_ASSERT_IF(!dbgFrame->hasGeneratorInfo(), !maybeGeneratorFramesEnum);

  if (frame) {
    if (maybeFramesEnum) {
      maybeFramesEnum->removeFront();
    } else {
      dbg->frames.remove(frame);
    }
  }

  if (dbgFrame->hasGeneratorInfo()) {
    if (maybeGeneratorFramesEnum) {
      maybeGeneratorFramesEnum->removeFront();
    } else {
      dbg->generatorFrames.remove(&dbgFrame->unwrappedGenerator());
    }
  }

  dbgFrame->terminate(gcx, frame);
}

DebuggerDebuggeeLink* Debugger::getDebuggeeLink() {
  return &object->getReservedSlot(JSSLOT_DEBUG_DEBUGGEE_LINK)
              .toObject()
              .as<DebuggerDebuggeeLink>();
}

void DebuggerDebuggeeLink::setLinkSlot(Debugger& dbg) {
  setReservedSlot(DEBUGGER_LINK_SLOT, ObjectValue(*dbg.toJSObject()));
}

void DebuggerDebuggeeLink::clearLinkSlot() {
  setReservedSlot(DEBUGGER_LINK_SLOT, UndefinedValue());
}

const JSClass DebuggerDebuggeeLink::class_ = {
    "DebuggerDebuggeeLink",
    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS),
};

/* static */
bool DebugAPI::handleBaselineOsr(JSContext* cx, InterpreterFrame* from,
                                 jit::BaselineFrame* to) {
  ScriptFrameIter iter(cx);
  MOZ_ASSERT(iter.abstractFramePtr() == to);
  return Debugger::replaceFrameGuts(cx, from, to, iter);
}

/* static */
bool DebugAPI::handleIonBailout(JSContext* cx, jit::RematerializedFrame* from,
                                jit::BaselineFrame* to) {
  // When we return to a bailed-out Ion real frame, we must update all
  // Debugger.Frames that refer to its inline frames. However, since we
  // can't pop individual inline frames off the stack (we can only pop the
  // real frame that contains them all, as a unit), we cannot assume that
  // the frame we're dealing with is the top frame. Advance the iterator
  // across any inlined frames younger than |to|, the baseline frame
  // reconstructed during bailout from the Ion frame corresponding to
  // |from|.
  ScriptFrameIter iter(cx);
  while (iter.abstractFramePtr() != to) {
    ++iter;
  }
  return Debugger::replaceFrameGuts(cx, from, to, iter);
}

/* static */
void DebugAPI::handleUnrecoverableIonBailoutError(
    JSContext* cx, jit::RematerializedFrame* frame) {
  // Ion bailout can fail due to overrecursion. In such cases we cannot
  // honor any further Debugger hooks on the frame, and need to ensure that
  // its Debugger.Frame entry is cleaned up.
  Debugger::terminateDebuggerFrames(cx, frame);
}

/*** JS::dbg::Builder *******************************************************/

Builder::Builder(JSContext* cx, js::Debugger* debugger)
    : debuggerObject(cx, debugger->toJSObject().get()), debugger(debugger) {}

#if DEBUG
void Builder::assertBuilt(JSObject* obj) {
  // We can't use assertSameCompartment here, because that is always keyed to
  // some JSContext's current compartment, whereas BuiltThings can be
  // constructed and assigned to without respect to any particular context;
  // the only constraint is that they should be in their debugger's compartment.
  MOZ_ASSERT_IF(obj, debuggerObject->compartment() == obj->compartment());
}
#endif

bool Builder::Object::definePropertyToTrusted(JSContext* cx, const char* name,
                                              JS::MutableHandleValue trusted) {
  // We should have checked for false Objects before calling this.
  MOZ_ASSERT(value);

  JSAtom* atom = Atomize(cx, name, strlen(name));
  if (!atom) {
    return false;
  }
  RootedId id(cx, AtomToId(atom));

  return DefineDataProperty(cx, value, id, trusted);
}

bool Builder::Object::defineProperty(JSContext* cx, const char* name,
                                     JS::HandleValue propval_) {
  AutoRealm ar(cx, debuggerObject());

  RootedValue propval(cx, propval_);
  if (!debugger()->wrapDebuggeeValue(cx, &propval)) {
    return false;
  }

  return definePropertyToTrusted(cx, name, &propval);
}

bool Builder::Object::defineProperty(JSContext* cx, const char* name,
                                     JS::HandleObject propval_) {
  RootedValue propval(cx, ObjectOrNullValue(propval_));
  return defineProperty(cx, name, propval);
}

bool Builder::Object::defineProperty(JSContext* cx, const char* name,
                                     Builder::Object& propval_) {
  AutoRealm ar(cx, debuggerObject());

  RootedValue propval(cx, ObjectOrNullValue(propval_.value));
  return definePropertyToTrusted(cx, name, &propval);
}

Builder::Object Builder::newObject(JSContext* cx) {
  AutoRealm ar(cx, debuggerObject);

  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));

  // If the allocation failed, this will return a false Object, as the spec
  // promises.
  return Object(cx, *this, obj);
}

/*** Glue *******************************************************************/

extern JS_PUBLIC_API bool JS_DefineDebuggerObject(JSContext* cx,
                                                  HandleObject obj) {
  Rooted<NativeObject*> debugCtor(cx), debugProto(cx), frameProto(cx),
      scriptProto(cx), sourceProto(cx), objectProto(cx), envProto(cx),
      memoryProto(cx);
  RootedObject debuggeeWouldRunProto(cx);
  RootedValue debuggeeWouldRunCtor(cx);
  Handle<GlobalObject*> global = obj.as<GlobalObject>();

  debugProto = InitClass(cx, global, &DebuggerPrototypeObject::class_, nullptr,
                         "Debugger", Debugger::construct, 1,
                         Debugger::properties, Debugger::methods, nullptr,
                         Debugger::static_methods, debugCtor.address());
  if (!debugProto) {
    return false;
  }

  frameProto = DebuggerFrame::initClass(cx, global, debugCtor);
  if (!frameProto) {
    return false;
  }

  scriptProto = DebuggerScript::initClass(cx, global, debugCtor);
  if (!scriptProto) {
    return false;
  }

  sourceProto = DebuggerSource::initClass(cx, global, debugCtor);
  if (!sourceProto) {
    return false;
  }

  objectProto = DebuggerObject::initClass(cx, global, debugCtor);
  if (!objectProto) {
    return false;
  }

  envProto = DebuggerEnvironment::initClass(cx, global, debugCtor);
  if (!envProto) {
    return false;
  }

  memoryProto = InitClass(
      cx, debugCtor, nullptr, nullptr, "Memory", DebuggerMemory::construct, 0,
      DebuggerMemory::properties, DebuggerMemory::methods, nullptr, nullptr);
  if (!memoryProto) {
    return false;
  }

  debuggeeWouldRunProto = GlobalObject::getOrCreateCustomErrorPrototype(
      cx, global, JSEXN_DEBUGGEEWOULDRUN);
  if (!debuggeeWouldRunProto) {
    return false;
  }
  debuggeeWouldRunCtor =
      ObjectValue(global->getConstructor(JSProto_DebuggeeWouldRun));
  RootedId debuggeeWouldRunId(
      cx, NameToId(ClassName(JSProto_DebuggeeWouldRun, cx)));
  if (!DefineDataProperty(cx, debugCtor, debuggeeWouldRunId,
                          debuggeeWouldRunCtor, 0)) {
    return false;
  }

  debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_FRAME_PROTO,
                              ObjectValue(*frameProto));
  debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_OBJECT_PROTO,
                              ObjectValue(*objectProto));
  debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SCRIPT_PROTO,
                              ObjectValue(*scriptProto));
  debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SOURCE_PROTO,
                              ObjectValue(*sourceProto));
  debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_ENV_PROTO,
                              ObjectValue(*envProto));
  debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO,
                              ObjectValue(*memoryProto));
  return true;
}

JS_PUBLIC_API bool JS::dbg::IsDebugger(JSObject& obj) {
  /* We only care about debugger objects, so CheckedUnwrapStatic is OK. */
  JSObject* unwrapped = CheckedUnwrapStatic(&obj);
  if (!unwrapped || !unwrapped->is<DebuggerInstanceObject>()) {
    return false;
  }
  MOZ_ASSERT(js::Debugger::fromJSObject(unwrapped));
  return true;
}

JS_PUBLIC_API bool JS::dbg::GetDebuggeeGlobals(
    JSContext* cx, JSObject& dbgObj, MutableHandleObjectVector vector) {
  MOZ_ASSERT(IsDebugger(dbgObj));
  /* Since we know we have a debugger object, CheckedUnwrapStatic is fine. */
  js::Debugger* dbg = js::Debugger::fromJSObject(CheckedUnwrapStatic(&dbgObj));

  if (!vector.reserve(vector.length() + dbg->debuggees.count())) {
    JS_ReportOutOfMemory(cx);
    return false;
  }

  for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
       r.popFront()) {
    vector.infallibleAppend(static_cast<JSObject*>(r.front()));
  }

  return true;
}

#ifdef DEBUG
/* static */
bool Debugger::isDebuggerCrossCompartmentEdge(JSObject* obj,
                                              const gc::Cell* target) {
  MOZ_ASSERT(target);

  const gc::Cell* referent = nullptr;
  if (obj->is<DebuggerScript>()) {
    referent = obj->as<DebuggerScript>().getReferentCell();
  } else if (obj->is<DebuggerSource>()) {
    referent = obj->as<DebuggerSource>().getReferentRawObject();
  } else if (obj->is<DebuggerObject>()) {
    referent = obj->as<DebuggerObject>().referent();
  } else if (obj->is<DebuggerEnvironment>()) {
    referent = obj->as<DebuggerEnvironment>().referent();
  }

  return referent == target;
}

static void CheckDebuggeeThingRealm(Realm* realm, bool invisibleOk) {
  MOZ_ASSERT_IF(!invisibleOk, !realm->creationOptions().invisibleToDebugger());
}

void js::CheckDebuggeeThing(BaseScript* script, bool invisibleOk) {
  CheckDebuggeeThingRealm(script->realm(), invisibleOk);
}

void js::CheckDebuggeeThing(JSObject* obj, bool invisibleOk) {
  if (Realm* realm = JS::GetObjectRealmOrNull(obj)) {
    CheckDebuggeeThingRealm(realm, invisibleOk);
  }
}
#endif  // DEBUG

/*** JS::dbg::GarbageCollectionEvent ****************************************/

namespace JS {
namespace dbg {

/* static */ GarbageCollectionEvent::Ptr GarbageCollectionEvent::Create(
    JSRuntime* rt, ::js::gcstats::Statistics& stats, uint64_t gcNumber) {
  auto data = MakeUnique<GarbageCollectionEvent>(gcNumber);
  if (!data) {
    return nullptr;
  }

  data->nonincrementalReason = stats.nonincrementalReason();

  for (auto& slice : stats.slices()) {
    if (!data->reason) {
      // There is only one GC reason for the whole cycle, but for legacy
      // reasons this data is stored and replicated on each slice. Each
      // slice used to have its own GCReason, but now they are all the
      // same.
      data->reason = ExplainGCReason(slice.reason);
      MOZ_ASSERT(data->reason);
    }

    if (!data->collections.growBy(1)) {
      return nullptr;
    }

    data->collections.back().startTimestamp = slice.start;
    data->collections.back().endTimestamp = slice.end;
  }

  return data;
}

static bool DefineStringProperty(JSContext* cx, HandleObject obj,
                                 PropertyName* propName, const char* strVal) {
  RootedValue val(cx, UndefinedValue());
  if (strVal) {
    JSAtom* atomized = Atomize(cx, strVal, strlen(strVal));
    if (!atomized) {
      return false;
    }
    val = StringValue(atomized);
  }
  return DefineDataProperty(cx, obj, propName, val);
}

JSObject* GarbageCollectionEvent::toJSObject(JSContext* cx) const {
  RootedObject obj(cx, NewPlainObject(cx));
  RootedValue gcCycleNumberVal(cx, NumberValue(majorGCNumber_));
  if (!obj ||
      !DefineStringProperty(cx, obj, cx->names().nonincrementalReason,
                            nonincrementalReason) ||
      !DefineStringProperty(cx, obj, cx->names().reason, reason) ||
      !DefineDataProperty(cx, obj, cx->names().gcCycleNumber,
                          gcCycleNumberVal)) {
    return nullptr;
  }

  Rooted<ArrayObject*> slicesArray(cx, NewDenseEmptyArray(cx));
  if (!slicesArray) {
    return nullptr;
  }

  TimeStamp originTime = TimeStamp::ProcessCreation();

  size_t idx = 0;
  for (auto range = collections.all(); !range.empty(); range.popFront()) {
    Rooted<PlainObject*> collectionObj(cx, NewPlainObject(cx));
    if (!collectionObj) {
      return nullptr;
    }

    RootedValue start(cx), end(cx);
    start = NumberValue(
        (range.front().startTimestamp - originTime).ToMilliseconds());
    end =
        NumberValue((range.front().endTimestamp - originTime).ToMilliseconds());
    if (!DefineDataProperty(cx, collectionObj, cx->names().startTimestamp,
                            start) ||
        !DefineDataProperty(cx, collectionObj, cx->names().endTimestamp, end)) {
      return nullptr;
    }

    RootedValue collectionVal(cx, ObjectValue(*collectionObj));
    if (!DefineDataElement(cx, slicesArray, idx++, collectionVal)) {
      return nullptr;
    }
  }

  RootedValue slicesValue(cx, ObjectValue(*slicesArray));
  if (!DefineDataProperty(cx, obj, cx->names().collections, slicesValue)) {
    return nullptr;
  }

  return obj;
}

JS_PUBLIC_API bool FireOnGarbageCollectionHookRequired(JSContext* cx) {
  AutoCheckCannotGC noGC;

  for (auto& dbg : cx->runtime()->onGarbageCollectionWatchers()) {
    MOZ_ASSERT(dbg.getHook(Debugger::OnGarbageCollection));
    if (dbg.observedGC(cx->runtime()->gc.majorGCCount())) {
      return true;
    }
  }

  return false;
}

JS_PUBLIC_API bool FireOnGarbageCollectionHook(
    JSContext* cx, JS::dbg::GarbageCollectionEvent::Ptr&& data) {
  RootedObjectVector triggered(cx);

  {
    // We had better not GC (and potentially get a dangling Debugger
    // pointer) while finding all Debuggers observing a debuggee that
    // participated in this GC.
    AutoCheckCannotGC noGC;

    for (auto& dbg : cx->runtime()->onGarbageCollectionWatchers()) {
      MOZ_ASSERT(dbg.getHook(Debugger::OnGarbageCollection));
      if (dbg.observedGC(data->majorGCNumber())) {
        if (!triggered.append(dbg.object)) {
          JS_ReportOutOfMemory(cx);
          return false;
        }
      }
    }
  }

  for (; !triggered.empty(); triggered.popBack()) {
    Debugger* dbg = Debugger::fromJSObject(triggered.back());

    if (dbg->getHook(Debugger::OnGarbageCollection)) {
      (void)dbg->enterDebuggerHook(cx, [&]() -> bool {
        return dbg->fireOnGarbageCollectionHook(cx, data);
      });
      MOZ_ASSERT(!cx->isExceptionPending());
    }
  }

  return true;
}

bool ShouldAvoidSideEffects(JSContext* cx) {
  return DebugAPI::shouldAvoidSideEffects(cx);
}

}  // namespace dbg
}  // namespace JS

Messung V0.5 in Prozent
C=86 H=93 G=89

¤ 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.0.212Bemerkung:  (vorverarbeitet am  2026-04-26) ¤

*Bot Zugriff






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