js/src/vm/Debugger.cpp
author Andreea Pavel <apavel@mozilla.com>
Wed, 13 Feb 2019 04:27:22 +0200
changeset 458838 e32e036906b9573147bc59a533b4af0f4e3e6988
parent 458823 64ebb085a6b3a5924ddc40c680bf40bb2459f1bd
child 458847 b7f9a1180f2356de24e63fe6f40949f914cc1b88
permissions -rw-r--r--
Backed out 3 changesets (bug 1518661) for failing test_promise_rejections_from_jsimplemented.html Backed out changeset d381785b0c4c (bug 1518661) Backed out changeset 4a8b38e5373b (bug 1518661) Backed out changeset 64ebb085a6b3 (bug 1518661)

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

#include "mozilla/DebugOnly.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/TypeTraits.h"

#include <utility>

#include "jsfriendapi.h"
#include "jsnum.h"

#include "frontend/BytecodeCompilation.h"
#include "frontend/Parser.h"
#include "gc/FreeOp.h"
#include "gc/HashUtil.h"
#include "gc/Marking.h"
#include "gc/Policy.h"
#include "gc/PublicIterators.h"
#include "jit/BaselineDebugModeOSR.h"
#include "jit/BaselineJIT.h"
#include "js/CharacterEncoding.h"
#include "js/Date.h"
#include "js/PropertyDescriptor.h"
#include "js/PropertySpec.h"
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/UbiNodeBreadthFirst.h"
#include "js/Vector.h"
#include "js/Wrapper.h"
#include "proxy/ScriptedProxyHandler.h"
#include "util/Text.h"
#include "vm/ArgumentsObject.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/DebuggerMemory.h"
#include "vm/GeckoProfiler.h"
#include "vm/GeneratorObject.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/Realm.h"
#include "vm/TraceLogging.h"
#include "vm/WrapperObject.h"
#include "wasm/WasmInstance.h"

#include "gc/GC-inl.h"
#include "vm/BytecodeUtil-inl.h"
#include "vm/Compartment-inl.h"
#include "vm/GeckoProfiler-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/Stack-inl.h"

using namespace js;

using JS::AutoStableStringChars;
using JS::CompileOptions;
using JS::SourceOwnership;
using JS::SourceText;
using JS::dbg::AutoEntryMonitor;
using JS::dbg::Builder;
using js::frontend::IsIdentifier;
using mozilla::AsVariant;
using mozilla::DebugOnly;
using mozilla::MakeScopeExit;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::Some;
using mozilla::TimeDuration;
using mozilla::TimeStamp;

/*** Forward declarations, ClassOps and Classes *****************************/

static void DebuggerFrame_finalize(FreeOp* fop, JSObject* obj);
static void DebuggerFrame_trace(JSTracer* trc, JSObject* obj);
static void DebuggerEnv_trace(JSTracer* trc, JSObject* obj);
static void DebuggerObject_trace(JSTracer* trc, JSObject* obj);
static void DebuggerScript_trace(JSTracer* trc, JSObject* obj);
static void DebuggerSource_trace(JSTracer* trc, JSObject* obj);

enum {
  JSSLOT_DEBUGFRAME_OWNER,
  JSSLOT_DEBUGFRAME_ARGUMENTS,
  JSSLOT_DEBUGFRAME_ONSTEP_HANDLER,
  JSSLOT_DEBUGFRAME_ONPOP_HANDLER,
  JSSLOT_DEBUGFRAME_COUNT
};

inline js::Debugger* js::DebuggerFrame::owner() const {
  JSObject* dbgobj = &getReservedSlot(JSSLOT_DEBUGFRAME_OWNER).toObject();
  return Debugger::fromJSObject(dbgobj);
}

const ClassOps DebuggerFrame::classOps_ = {nullptr, /* addProperty */
                                           nullptr, /* delProperty */
                                           nullptr, /* enumerate   */
                                           nullptr, /* newEnumerate */
                                           nullptr, /* resolve     */
                                           nullptr, /* mayResolve  */
                                           DebuggerFrame_finalize,
                                           nullptr, /* call        */
                                           nullptr, /* hasInstance */
                                           nullptr, /* construct   */
                                           DebuggerFrame_trace};

const Class DebuggerFrame::class_ = {
    "Frame",
    JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT) |
        JSCLASS_BACKGROUND_FINALIZE,
    &DebuggerFrame::classOps_};

enum { JSSLOT_DEBUGARGUMENTS_FRAME, JSSLOT_DEBUGARGUMENTS_COUNT };

const Class DebuggerArguments::class_ = {
    "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT)};

const ClassOps DebuggerEnvironment::classOps_ = {nullptr, /* addProperty */
                                                 nullptr, /* delProperty */
                                                 nullptr, /* enumerate   */
                                                 nullptr, /* newEnumerate */
                                                 nullptr, /* resolve     */
                                                 nullptr, /* mayResolve  */
                                                 nullptr, /* finalize    */
                                                 nullptr, /* call        */
                                                 nullptr, /* hasInstance */
                                                 nullptr, /* construct   */
                                                 DebuggerEnv_trace};

const Class DebuggerEnvironment::class_ = {
    "Environment",
    JSCLASS_HAS_PRIVATE |
        JSCLASS_HAS_RESERVED_SLOTS(DebuggerEnvironment::RESERVED_SLOTS),
    &classOps_};

enum { JSSLOT_DEBUGOBJECT_OWNER, JSSLOT_DEBUGOBJECT_COUNT };

const ClassOps DebuggerObject::classOps_ = {nullptr, /* addProperty */
                                            nullptr, /* delProperty */
                                            nullptr, /* enumerate   */
                                            nullptr, /* newEnumerate */
                                            nullptr, /* resolve     */
                                            nullptr, /* mayResolve  */
                                            nullptr, /* finalize    */
                                            nullptr, /* call        */
                                            nullptr, /* hasInstance */
                                            nullptr, /* construct   */
                                            DebuggerObject_trace};

const Class DebuggerObject::class_ = {
    "Object", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS),
    &classOps_};

enum { JSSLOT_DEBUGSCRIPT_OWNER, JSSLOT_DEBUGSCRIPT_COUNT };

static const ClassOps DebuggerScript_classOps = {nullptr, /* addProperty */
                                                 nullptr, /* delProperty */
                                                 nullptr, /* enumerate   */
                                                 nullptr, /* newEnumerate */
                                                 nullptr, /* resolve     */
                                                 nullptr, /* mayResolve  */
                                                 nullptr, /* finalize    */
                                                 nullptr, /* call        */
                                                 nullptr, /* hasInstance */
                                                 nullptr, /* construct   */
                                                 DebuggerScript_trace};

static const Class DebuggerScript_class = {
    "Script",
    JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT),
    &DebuggerScript_classOps};

enum {
  JSSLOT_DEBUGSOURCE_OWNER,
  JSSLOT_DEBUGSOURCE_TEXT,
  JSSLOT_DEBUGSOURCE_COUNT
};

static const ClassOps DebuggerSource_classOps = {nullptr, /* addProperty */
                                                 nullptr, /* delProperty */
                                                 nullptr, /* enumerate   */
                                                 nullptr, /* newEnumerate */
                                                 nullptr, /* resolve     */
                                                 nullptr, /* mayResolve  */
                                                 nullptr, /* finalize    */
                                                 nullptr, /* call        */
                                                 nullptr, /* hasInstance */
                                                 nullptr, /* construct   */
                                                 DebuggerSource_trace};

static const Class DebuggerSource_class = {
    "Source",
    JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSOURCE_COUNT),
    &DebuggerSource_classOps};

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

/*
 * If fun is an interpreted function, remove any async function/generator
 * wrapper and return the underlying scripted function. Otherwise, return fun
 * unchanged.
 *
 * Async functions are implemented as native functions wrapped around a scripted
 * function. JSScripts hold ordinary inner JSFunctions in their object arrays,
 * and when we need to actually create a JS-visible function object, we build an
 * ordinary JS closure and apply the async wrapper to it. Async generators are
 * similar.
 *
 * This means that JSFunction::isInterpreted returns false for such functions,
 * even though their actual code is indeed JavaScript. Debugger should treat
 * async functions and generators like any other scripted function, so we must
 * carefully check for them whenever we want inspect a function.
 */

static JSFunction* RemoveAsyncWrapper(JSFunction* fun) {
  if (js::IsWrappedAsyncFunction(fun)) {
    fun = js::GetUnwrappedAsyncFunction(fun);
  } else if (js::IsWrappedAsyncGenerator(fun)) {
    fun = js::GetUnwrappedAsyncGenerator(fun);
  }

  return fun;
}

static inline bool EnsureFunctionHasScript(JSContext* cx, HandleFunction fun) {
  if (fun->isInterpretedLazy()) {
    AutoRealm ar(cx, fun);
    return !!JSFunction::getOrCreateScript(cx, fun);
  }
  return true;
}

static inline JSScript* GetOrCreateFunctionScript(JSContext* cx,
                                                  HandleFunction fun) {
  MOZ_ASSERT(fun->isInterpreted());
  if (!EnsureFunctionHasScript(cx, fun)) {
    return nullptr;
  }
  return fun->nonLazyScript();
}

static bool ValueToIdentifier(JSContext* cx, HandleValue v,
                              MutableHandleId id) {
  if (!ValueToId<CanGC>(cx, v, id)) {
    return false;
  }
  if (!JSID_IS_ATOM(id) || !IsIdentifier(JSID_TO_ATOM(id))) {
    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; }
};

// Given a Debugger instance dbg, if it is enabled, prevents all its debuggee
// compartments from executing scripts. Attempts to run script will throw an
// instance of Debugger.DebuggeeWouldRun from the topmost locked Debugger's
// compartment.
class MOZ_RAII js::EnterDebuggeeNoExecute {
  friend class js::LeaveDebuggeeNoExecute;

  Debugger& dbg_;
  EnterDebuggeeNoExecute** stack_;
  EnterDebuggeeNoExecute* prev_;

  // Non-nullptr when unlocked temporarily by a LeaveDebuggeeNoExecute.
  LeaveDebuggeeNoExecute* unlocked_;

  // When DebuggeeWouldRun is a warning instead of an error, whether we've
  // reported a warning already.
  bool reported_;

 public:
  explicit EnterDebuggeeNoExecute(JSContext* cx, Debugger& dbg)
      : dbg_(dbg), unlocked_(nullptr), reported_(false) {
    stack_ = &cx->noExecuteDebuggerTop.ref();
    prev_ = *stack_;
    *stack_ = this;
  }

  ~EnterDebuggeeNoExecute() {
    MOZ_ASSERT(*stack_ == this);
    *stack_ = prev_;
  }

  Debugger& debugger() const { return dbg_; }

#ifdef DEBUG
  static bool isLockedInStack(JSContext* cx, Debugger& dbg) {
    for (EnterDebuggeeNoExecute* it = cx->noExecuteDebuggerTop; it;
         it = it->prev_) {
      if (&it->debugger() == &dbg) {
        return !it->unlocked_;
      }
    }
    return false;
  }
#endif

  // Given a JSContext entered into a debuggee realm, find the lock
  // that locks it. Returns nullptr if not found.
  static EnterDebuggeeNoExecute* findInStack(JSContext* cx) {
    Realm* debuggee = cx->realm();
    for (EnterDebuggeeNoExecute* it = cx->noExecuteDebuggerTop; it;
         it = it->prev_) {
      Debugger& dbg = it->debugger();
      if (!it->unlocked_ && dbg.isEnabled() &&
          dbg.observesGlobal(debuggee->maybeGlobal())) {
        return it;
      }
    }
    return nullptr;
  }

  // Given a JSContext entered into a debuggee compartment, report a
  // warning or an error if there is a lock that locks it.
  static bool reportIfFoundInStack(JSContext* cx, HandleScript script) {
    if (EnterDebuggeeNoExecute* nx = findInStack(cx)) {
      bool warning = !cx->options().throwOnDebuggeeWouldRun();
      if (!warning || !nx->reported_) {
        AutoRealm ar(cx, nx->debugger().toJSObject());
        nx->reported_ = true;
        if (cx->options().dumpStackOnDebuggeeWouldRun()) {
          fprintf(stdout, "Dumping stack for DebuggeeWouldRun:\n");
          DumpBacktrace(cx);
        }
        const char* filename =
            script->filename() ? script->filename() : "(none)";
        char linenoStr[15];
        SprintfLiteral(linenoStr, "%u", script->lineno());
        unsigned flags = warning ? JSREPORT_WARNING : JSREPORT_ERROR;
        // FIXME: filename should be UTF-8 (bug 987069).
        return JS_ReportErrorFlagsAndNumberLatin1(
            cx, flags, GetErrorMessage, nullptr, JSMSG_DEBUGGEE_WOULD_RUN,
            filename, linenoStr);
      }
    }
    return true;
  }
};

// Given a JSContext entered into a debuggee compartment, if it is in
// an NX section, unlock the topmost EnterDebuggeeNoExecute instance.
//
// Does nothing if debuggee is not in an NX section. For example, this
// situation arises when invocation functions are called without entering
// Debugger code, e.g., calling D.O.p.executeInGlobal or D.O.p.apply.
class MOZ_RAII js::LeaveDebuggeeNoExecute {
  EnterDebuggeeNoExecute* prevLocked_;

 public:
  explicit LeaveDebuggeeNoExecute(JSContext* cx)
      : prevLocked_(EnterDebuggeeNoExecute::findInStack(cx)) {
    if (prevLocked_) {
      MOZ_ASSERT(!prevLocked_->unlocked_);
      prevLocked_->unlocked_ = this;
    }
  }

  ~LeaveDebuggeeNoExecute() {
    if (prevLocked_) {
      MOZ_ASSERT(prevLocked_->unlocked_ == this);
      prevLocked_->unlocked_ = nullptr;
    }
  }
};

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

static inline void NukeDebuggerWrapper(NativeObject* wrapper) {
  // In some OOM failure cases, we need to destroy the edge to the referent,
  // to avoid trying to trace it during untimely collections.
  wrapper->setPrivate(nullptr);
}

static bool 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;
  }
  RootedLinearString 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;
}

static bool 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_EncodeStringToLatin1(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);
  }

  return true;
}

static bool RequireGlobalObject(JSContext* cx, HandleValue dbgobj,
                                HandleObject referent) {
  RootedObject obj(cx, referent);

  if (!obj->is<GlobalObject>()) {
    const char* isWrapper = "";
    const char* isWindowProxy = "";

    // Help the poor programmer by pointing out wrappers around globals...
    if (obj->is<WrapperObject>()) {
      obj = js::UncheckedUnwrap(obj);
      isWrapper = "a wrapper around ";
    }

    // ... and WindowProxies around Windows.
    if (IsWindowProxy(obj)) {
      obj = ToWindowIfWindowProxy(obj);
      isWindowProxy = "a WindowProxy referring to ";
    }

    if (obj->is<GlobalObject>()) {
      ReportValueError(cx, JSMSG_DEBUG_WRAPPER_IN_WAY, JSDVG_SEARCH_STACK,
                       dbgobj, nullptr, isWrapper, isWindowProxy);
    } else {
      ReportValueError(cx, JSMSG_DEBUG_BAD_REFERENT, JSDVG_SEARCH_STACK, dbgobj,
                       nullptr, "a global object");
    }
    return false;
  }

  return true;
}

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

BreakpointSite::BreakpointSite(Type type) : type_(type), enabledCount(0) {}

void BreakpointSite::inc(FreeOp* fop) {
  enabledCount++;
  if (enabledCount == 1) {
    recompile(fop);
  }
}

void BreakpointSite::dec(FreeOp* fop) {
  MOZ_ASSERT(enabledCount > 0);
  enabledCount--;
  if (enabledCount == 0) {
    recompile(fop);
  }
}

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

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, BreakpointSite* site,
                       JSObject* handler)
    : debugger(debugger), site(site), handler(handler) {
  MOZ_ASSERT(handler->compartment() == debugger->object->compartment());
  debugger->breakpoints.pushBack(this);
  site->breakpoints.pushBack(this);
}

void Breakpoint::destroy(FreeOp* fop,
                         MayDestroySite mayDestroySite /* true */) {
  if (debugger->enabled) {
    site->dec(fop);
  }
  debugger->breakpoints.remove(this);
  site->breakpoints.remove(this);
  if (mayDestroySite == MayDestroySite::True) {
    site->destroyIfEmpty(fop);
  }
  fop->delete_(this);
}

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

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

JSBreakpointSite::JSBreakpointSite(JSScript* script, jsbytecode* pc)
    : BreakpointSite(Type::JS), script(script), pc(pc) {
  MOZ_ASSERT(!script->hasBreakpointsAt(pc));
}

void JSBreakpointSite::recompile(FreeOp* fop) {
  if (script->hasBaselineScript()) {
    script->baselineScript()->toggleDebugTraps(script, pc);
  }
}

void JSBreakpointSite::destroyIfEmpty(FreeOp* fop) {
  if (isEmpty()) {
    script->destroyBreakpointSite(fop, pc);
  }
}

WasmBreakpointSite::WasmBreakpointSite(wasm::DebugState* debug_,
                                       uint32_t offset_)
    : BreakpointSite(Type::Wasm), debug(debug_), offset(offset_) {
  MOZ_ASSERT(debug_);
}

void WasmBreakpointSite::recompile(FreeOp* fop) {
  debug->toggleBreakpointTrap(fop->runtime(), offset, isEnabled());
}

void WasmBreakpointSite::destroyIfEmpty(FreeOp* fop) {
  if (isEmpty()) {
    debug->destroyBreakpointSite(fop, offset);
  }
}

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

Debugger::Debugger(JSContext* cx, NativeObject* dbg)
    : object(dbg),
      debuggees(cx->zone()),
      uncaughtExceptionHook(nullptr),
      enabled(true),
      allowUnobservedAsmJS(false),
      collectCoverageInfo(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),
      lazyScripts(cx),
      sources(cx),
      objects(cx),
      environments(cx),
      wasmInstanceScripts(cx),
      wasmInstanceSources(cx),
#ifdef NIGHTLY_BUILD
      traceLoggerLastDrainedSize(0),
      traceLoggerLastDrainedIteration(0),
#endif
      traceLoggerScriptedCallsLastDrainedSize(0),
      traceLoggerScriptedCallsLastDrainedIteration(0) {
  cx->check(dbg);

#ifdef JS_TRACE_LOGGING
  TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
  if (logger) {
#  ifdef NIGHTLY_BUILD
    logger->getIterationAndSize(&traceLoggerLastDrainedIteration,
                                &traceLoggerLastDrainedSize);
#  endif
    logger->getIterationAndSize(&traceLoggerScriptedCallsLastDrainedIteration,
                                &traceLoggerScriptedCallsLastDrainedSize);
  }
#endif

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

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

  // We don't have to worry about locking here since Debugger is not
  // background finalized.
  JSContext* cx = TlsContext.get();
  if (onNewGlobalObjectWatchersLink.mPrev ||
      onNewGlobalObjectWatchersLink.mNext ||
      cx->runtime()->onNewGlobalObjectWatchers().begin() ==
          JSRuntime::WatchersList::Iterator(this)) {
    cx->runtime()->onNewGlobalObjectWatchers().remove(this);
  }
}

JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) ==
                 unsigned(JSSLOT_DEBUGSCRIPT_OWNER));
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) ==
                 unsigned(JSSLOT_DEBUGSOURCE_OWNER));
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) ==
                 unsigned(JSSLOT_DEBUGOBJECT_OWNER));
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) ==
                 unsigned(DebuggerEnvironment::OWNER_SLOT));

/* static */ Debugger* Debugger::fromChildJSObject(JSObject* obj) {
  MOZ_ASSERT(obj->getClass() == &DebuggerFrame::class_ ||
             obj->getClass() == &DebuggerScript_class ||
             obj->getClass() == &DebuggerSource_class ||
             obj->getClass() == &DebuggerObject::class_ ||
             obj->getClass() == &DebuggerEnvironment::class_);
  JSObject* dbgobj = &obj->as<NativeObject>()
                          .getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER)
                          .toObject();
  return fromJSObject(dbgobj);
}

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>();
}

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

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

  if (referent.hasScript() &&
      !referent.script()->ensureHasAnalyzedArgsUsage(cx)) {
    return false;
  }

  FrameMap::AddPtr p = frames.lookupForAdd(referent);
  if (!p) {
    RootedDebuggerFrame frame(cx);

    // If this is a generator frame, there may be an existing
    // Debugger.Frame object that isn't in `frames` because the generator
    // was suspended, popping the stack frame, and later resumed (and we
    // were not stepping, so did not pass through slowPathOnResumeFrame).
    Rooted<GeneratorObject*> genObj(cx);
    GeneratorWeakMap::AddPtr gp;
    if (referent.isGeneratorFrame()) {
      {
        AutoRealm ar(cx, referent.callee());
        genObj = GetGeneratorObjectForFrame(cx, referent);
      }
      if (genObj) {
        gp = generatorFrames.lookupForAdd(genObj);
        if (gp) {
          frame = &gp->value()->as<DebuggerFrame>();

          // We have found an existing Debugger.Frame object. But
          // since it was previously popped (see comment above), it
          // is not currently "live". We must revive it.
          if (!frame->resume(iter)) {
            return false;
          }
          if (!ensureExecutionObservabilityOfFrame(cx, referent)) {
            return false;
          }
        }
      }

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

    if (!frame) {
      // Create and populate the Debugger.Frame object.
      RootedObject proto(
          cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
      RootedNativeObject debugger(cx, object);

      frame = DebuggerFrame::create(cx, proto, iter, debugger);
      if (!frame) {
        return false;
      }

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

      if (genObj) {
        DebuggerFrame* frameObj = frame;
        if (!generatorFrames.relookupOrAdd(gp, genObj, frameObj)) {
          ReportOutOfMemory(cx);
          return false;
        }
      }
    }

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

  result.set(&p->value()->as<DebuggerFrame>());
  return true;
}

bool Debugger::addGeneratorFrame(JSContext* cx, Handle<GeneratorObject*> genObj,
                                 HandleDebuggerFrame frameObj) {
  GeneratorWeakMap::AddPtr p = generatorFrames.lookupForAdd(genObj);
  if (p) {
    MOZ_ASSERT(p->value() == frameObj);
  } else {
    if (!generatorFrames.relookupOrAdd(p, genObj, frameObj)) {
      ReportOutOfMemory(cx);
      return false;
    }
  }
  return true;
}

/* static */ bool Debugger::hasLiveHook(GlobalObject* global, Hook which) {
  if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
    for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
      Debugger* dbg = *p;
      if (dbg->enabled && dbg->getHook(which)) {
        return true;
      }
    }
  }
  return false;
}

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

bool Debugger::hasAnyLiveHooks(JSRuntime* rt) const {
  if (!enabled) {
    return false;
  }

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

  // If any breakpoints are in live scripts, return true.
  for (Breakpoint* bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
    switch (bp->site->type()) {
      case BreakpointSite::Type::JS:
        if (IsMarkedUnbarriered(rt, &bp->site->asJS()->script)) {
          return true;
        }
        break;
      case BreakpointSite::Type::Wasm:
        if (IsMarkedUnbarriered(rt, &bp->asWasm()->wasmInstance)) {
          return true;
        }
        break;
    }
  }

  // Check for hooks in live stack frames.
  for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
    DebuggerFrame& frameObj = r.front().value()->as<DebuggerFrame>();
    if (frameObj.hasAnyLiveHooks()) {
      return true;
    }
  }

  // Check for hooks set on suspended generator frames.
  for (GeneratorWeakMap::Range r = generatorFrames.all(); !r.empty();
       r.popFront()) {
    JSObject* key = r.front().key();
    DebuggerFrame& frameObj = r.front().value()->as<DebuggerFrame>();
    if (IsMarkedUnbarriered(rt, &key) && frameObj.hasAnyLiveHooks()) {
      return true;
    }
  }

  return false;
}

/* static */ ResumeMode Debugger::slowPathOnEnterFrame(JSContext* cx,
                                                       AbstractFramePtr frame) {
  RootedValue rval(cx);
  ResumeMode resumeMode = dispatchHook(cx,
                                       [frame](Debugger* dbg) -> bool {
                                         return dbg->observesFrame(frame) &&
                                                dbg->observesEnterFrame();
                                       },
                                       [&](Debugger* dbg) -> ResumeMode {
                                         return dbg->fireEnterFrame(cx, &rval);
                                       });

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

    case ResumeMode::Throw:
      cx->setPendingException(rval);
      break;

    case ResumeMode::Terminate:
      cx->clearPendingException();
      break;

    case ResumeMode::Return:
      frame.setReturnValue(rval);
      break;

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

  return resumeMode;
}

/* static */ ResumeMode Debugger::slowPathOnResumeFrame(
    JSContext* cx, AbstractFramePtr frame) {
  // 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<GeneratorObject*> genObj(cx, GetGeneratorObjectForFrame(cx, frame));
  MOZ_ASSERT(genObj);

  // 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.
  if (GlobalObject::DebuggerVector* debuggers =
          frame.global()->getDebuggers()) {
    for (Debugger* dbg : *debuggers) {
      if (GeneratorWeakMap::Ptr entry = dbg->generatorFrames.lookup(genObj)) {
        DebuggerFrame* frameObj = &entry->value()->as<DebuggerFrame>();
        if (!dbg->frames.putNew(frame, frameObj)) {
          ReportOutOfMemory(cx);
          return ResumeMode::Throw;
        }

        FrameIter iter(cx);
        MOZ_ASSERT(iter.abstractFramePtr() == frame);
        if (!frameObj->resume(iter)) {
          return ResumeMode::Throw;
        }
        if (!ensureExecutionObservabilityOfFrame(cx, frame)) {
          return ResumeMode::Throw;
        }
      }
    }
  }

  return slowPathOnEnterFrame(cx, frame);
}

static void DebuggerFrame_maybeDecrementFrameScriptStepModeCount(
    FreeOp* fop, AbstractFramePtr frame, NativeObject* frameobj);

/*
 * 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_;
  Rooted<GeneratorObject*> genObj_;

 public:
  AutoSetGeneratorRunning(JSContext* cx, Handle<GeneratorObject*> genObj)
      : resumeIndex_(0), genObj_(cx, genObj) {
    if (genObj) {
      if (!genObj->isClosed() && genObj->isSuspended()) {
        // Yielding or awaiting.
        resumeIndex_ =
            genObj->getFixedSlot(GeneratorObject::RESUME_INDEX_SLOT).toInt32();
        genObj->setRunning();
      } 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_->setFixedSlot(GeneratorObject::RESUME_INDEX_SLOT,
                            Int32Value(resumeIndex_));
    }
  }
};

/*
 * 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 Debugger::slowPathOnLeaveFrame(JSContext* cx,
                                                 AbstractFramePtr frame,
                                                 jsbytecode* pc, bool frameOk) {
  mozilla::DebugOnly<Handle<GlobalObject*>> debuggeeGlobal = cx->global();

  // Determine if we are suspending this frame or popping it forever.
  bool suspending = false;
  Rooted<GeneratorObject*> genObj(cx);
  if (frame.isGeneratorFrame()) {
    // 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.
    genObj = GetGeneratorObjectForFrame(cx, frame);
    suspending =
        frameOk && pc &&
        (*pc == JSOP_INITIALYIELD || *pc == JSOP_YIELD || *pc == JSOP_AWAIT) &&
        !genObj->isClosed();
  }

  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
    // suspending && !success, the generator is closed, not suspended.
    removeFromFrameMapsAndClearBreakpointsIn(cx, frame, suspending && success);
  });

  // 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<DebuggerFrameVector> frames(cx, DebuggerFrameVector(cx));
  if (!getDebuggerFrames(frame, &frames)) {
    return false;
  }
  if (frames.empty()) {
    return frameOk;
  }

  // Save the frame's completion value.
  ResumeMode resumeMode;
  RootedValue value(cx);
  Debugger::resultToCompletion(cx, frameOk, frame.returnValue(), &resumeMode,
                               &value);

  // 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()) {
    // For each Debugger.Frame, fire its onPop handler, if any.
    for (size_t i = 0; i < frames.length(); i++) {
      HandleDebuggerFrame frameobj = frames[i];
      Debugger* dbg = Debugger::fromChildJSObject(frameobj);
      EnterDebuggeeNoExecute nx(cx, *dbg);

      if (dbg->enabled && frameobj->onPopHandler()) {
        OnPopHandler* handler = frameobj->onPopHandler();

        Maybe<AutoRealm> ar;
        ar.emplace(cx, dbg->object);

        RootedValue wrappedValue(cx, value);
        RootedValue completion(cx);
        if (!dbg->wrapDebuggeeValue(cx, &wrappedValue)) {
          resumeMode = dbg->reportUncaughtException(ar);
          break;
        }

        // Call the onPop handler.
        ResumeMode nextResumeMode = resumeMode;
        RootedValue nextValue(cx, wrappedValue);
        bool success;
        {
          AutoSetGeneratorRunning asgr(cx, genObj);
          success = handler->onPop(cx, frameobj, nextResumeMode, &nextValue);
        }
        nextResumeMode = dbg->processParsedHandlerResult(
            ar, frame, pc, success, nextResumeMode, &nextValue);

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

        // ResumeMode::Continue means "make no change".
        if (nextResumeMode != ResumeMode::Continue) {
          resumeMode = nextResumeMode;
          value = nextValue;
        }
      }
    }
  }

  // Establish (resumeMode, value) as our resumption value.
  switch (resumeMode) {
    case ResumeMode::Return:
      frame.setReturnValue(value);
      success = true;
      return true;

    case ResumeMode::Throw:
      cx->setPendingException(value);
      return false;

    case ResumeMode::Terminate:
      MOZ_ASSERT(!cx->isExceptionPending());
      return false;

    default:
      MOZ_CRASH("bad final onLeaveFrame resume mode");
  }
}

/* static */ bool Debugger::slowPathOnNewGenerator(
    JSContext* cx, AbstractFramePtr frame, Handle<GeneratorObject*> 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
  // GeneratorObject for this generator call, though, has just been
  // created. It must be associated with any existing Debugger.Frames.
  bool ok = true;
  forEachDebuggerFrame(frame, [&](DebuggerFrame* frameObjPtr) {
    if (!ok) {
      return;
    }

    RootedDebuggerFrame frameObj(cx, frameObjPtr);
    Debugger* dbg = Debugger::fromChildJSObject(frameObj);
    if (!dbg->addGeneratorFrame(cx, genObj, frameObj)) {
      ReportOutOfMemory(cx);

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

/* static */ ResumeMode Debugger::slowPathOnDebuggerStatement(
    JSContext* cx, AbstractFramePtr frame) {
  RootedValue rval(cx);
  ResumeMode resumeMode = dispatchHook(
      cx,
      [](Debugger* dbg) -> bool { return dbg->getHook(OnDebuggerStatement); },
      [&](Debugger* dbg) -> ResumeMode {
        return dbg->fireDebuggerStatement(cx, &rval);
      });

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

    case ResumeMode::Return:
      frame.setReturnValue(rval);
      break;

    case ResumeMode::Throw:
      cx->setPendingException(rval);
      break;

    default:
      MOZ_CRASH("Invalid onDebuggerStatement resume mode");
  }

  return resumeMode;
}

/* static */ ResumeMode Debugger::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 ResumeMode::Continue;
  }

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

  RootedValue rval(cx);
  ResumeMode resumeMode = dispatchHook(
      cx, [](Debugger* dbg) -> bool { return dbg->getHook(OnExceptionUnwind); },
      [&](Debugger* dbg) -> ResumeMode {
        return dbg->fireExceptionUnwind(cx, &rval);
      });

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

    case ResumeMode::Throw:
      cx->setPendingException(rval);
      break;

    case ResumeMode::Terminate:
      cx->clearPendingException();
      break;

    case ResumeMode::Return:
      cx->clearPendingException();
      frame.setReturnValue(rval);
      break;

    default:
      MOZ_CRASH("Invalid onExceptionUnwind resume mode");
  }

  return resumeMode;
}

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

  RootedDebuggerEnvironment envobj(cx);

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

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

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

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

  DependentAddPtr<ObjectWeakMap> 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());
    RootedNativeObject debugger(cx, object);

    RootedDebuggerEnvironment envobj(
        cx, DebuggerEnvironment::create(cx, proto, env, debugger));
    if (!envobj) {
      return false;
    }

    if (!p.add(cx, environments, env, envobj)) {
      NukeDebuggerWrapper(envobj);
      return false;
    }

    CrossCompartmentKey key(object, env,
                            CrossCompartmentKey::DebuggerEnvironment);
    if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) {
      NukeDebuggerWrapper(envobj);
      environments.remove(env);
      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());
    RootedDebuggerObject dobj(cx);

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

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

    // We handle three sentinel values: missing arguments (overloading
    // JS_OPTIMIZED_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_OPTIMIZED_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::wrapDebuggeeObject(JSContext* cx, HandleObject obj,
                                  MutableHandleDebuggerObject result) {
  MOZ_ASSERT(obj);

  if (obj->is<JSFunction>()) {
    MOZ_ASSERT(!IsInternalFunctionObject(*obj));
    RootedFunction fun(cx, &obj->as<JSFunction>());
    if (!EnsureFunctionHasScript(cx, fun)) {
      return false;
    }
  }

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

    if (!p.add(cx, objects, obj, dobj)) {
      NukeDebuggerWrapper(dobj);
      return false;
    }

    if (obj->compartment() != object->compartment()) {
      CrossCompartmentKey key(object, obj, CrossCompartmentKey::DebuggerObject);
      if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) {
        NukeDebuggerWrapper(dobj);
        objects.remove(obj);
        ReportOutOfMemory(cx);
        return false;
      }
    }

    result.set(dobj);
  }

  return true;
}

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

  NativeObject* ndobj = &obj->as<NativeObject>();

  Value owner = ndobj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER);
  if (owner.isUndefined()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_PROTO,
                              "Debugger.Object", "Debugger.Object");
    return nullptr;
  }

  return ndobj;
}

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

  Value owner = ndobj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER);
  if (&owner.toObject() != object) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_WRONG_OWNER, "Debugger.Object");
    return false;
  }

  obj.set(static_cast<JSObject*>(ndobj->getPrivate()));
  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.hasGetterObject()) {
    RootedObject get(cx, desc.getterObject());
    if (get) {
      if (!unwrapDebuggeeObject(cx, &get)) {
        return false;
      }
      if (!CheckArgCompartment(cx, obj, get, "defineProperty", "get")) {
        return false;
      }
    }
    desc.setGetterObject(get);
  }

  if (desc.hasSetterObject()) {
    RootedObject set(cx, desc.setterObject());
    if (set) {
      if (!unwrapDebuggeeObject(cx, &set)) {
        return false;
      }
      if (!CheckArgCompartment(cx, obj, set, "defineProperty", "set")) {
        return false;
      }
    }
    desc.setSetterObject(set);
  }

  return true;
}

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

static bool GetResumptionProperty(JSContext* cx, HandleObject obj,
                                  HandlePropertyName 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;
}

static bool 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 GetThisValueForCheck(JSContext* cx, AbstractFramePtr frame,
                                 jsbytecode* pc, MutableHandleValue thisv,
                                 Maybe<HandleValue>& maybeThisv) {
  if (frame.debuggerNeedsCheckPrimitiveReturn()) {
    {
      AutoRealm ar(cx, frame.environmentChain());
      if (!GetThisValueForDebuggerMaybeOptimizedOut(cx, frame, pc, thisv)) {
        return false;
      }
    }

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

    MOZ_ASSERT_IF(thisv.isMagic(), thisv.isMagic(JS_UNINITIALIZED_LEXICAL));
    maybeThisv.emplace(HandleValue(thisv));
  }

  return true;
}

static bool CheckResumptionValue(JSContext* cx, AbstractFramePtr frame,
                                 const Maybe<HandleValue>& maybeThisv,
                                 ResumeMode resumeMode, MutableHandleValue vp) {
  if (maybeThisv.isSome()) {
    const HandleValue& thisv = maybeThisv.ref();
    if (resumeMode == ResumeMode::Return && vp.isPrimitive()) {
      if (vp.isUndefined()) {
        if (thisv.isMagic(JS_UNINITIALIZED_LEXICAL)) {
          return ThrowUninitializedThis(cx, frame);
        }

        vp.set(thisv);
      } else {
        ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, vp,
                         nullptr);
        return false;
      }
    }
  }
  return true;
}

static void AdjustGeneratorResumptionValue(JSContext* cx,
                                           AbstractFramePtr frame,
                                           ResumeMode& resumeMode,
                                           MutableHandleValue vp) {
  if (resumeMode == ResumeMode::Return && frame && frame.isFunctionFrame() &&
      frame.callee()->isGenerator()) {
    // Treat `{return: <value>}` like a `return` statement. For generators,
    // that means doing the work below. It's only what the debuggee would
    // do for an ordinary `return` statement--using a few bytecode
    // instructions--and 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.
    Rooted<GeneratorObject*> genObj(cx, GetGeneratorObjectForFrame(cx, frame));
    if (genObj) {
      // 1.  `return <value>` creates and returns a new object,
      //     `{value: <value>, done: true}`.
      if (!genObj->isBeforeInitialYield()) {
        JSObject* pair = CreateIterResultObject(cx, vp, true);
        if (!pair) {
          // Out of memory in debuggee code. Arrange for this to propagate.
          MOZ_ALWAYS_TRUE(cx->getPendingException(vp));
          cx->clearPendingException();
          resumeMode = ResumeMode::Throw;
          return;
        }
        vp.setObject(*pair);
      }

      // 2.  The generator must be closed.
      genObj->setClosed();
    } else {
      // We're before the initial yield. Carry on with the forced return.
      // The debuggee will see a call to a generator returning the
      // non-generator value *vp.
    }
  }
}

ResumeMode Debugger::reportUncaughtException(Maybe<AutoRealm>& ar) {
  JSContext* cx = ar->context();

  // 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();
  }

  ar.reset();
  return ResumeMode::Terminate;
}

ResumeMode Debugger::handleUncaughtExceptionHelper(
    Maybe<AutoRealm>& ar, MutableHandleValue* vp,
    const Maybe<HandleValue>& thisVForCheck, AbstractFramePtr frame) {
  JSContext* cx = ar->context();

  // 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()) {
    if (uncaughtExceptionHook) {
      RootedValue exc(cx);
      if (!cx->getPendingException(&exc)) {
        return ResumeMode::Terminate;
      }
      cx->clearPendingException();

      RootedValue fval(cx, ObjectValue(*uncaughtExceptionHook));
      RootedValue rv(cx);
      if (js::Call(cx, fval, object, exc, &rv)) {
        if (vp) {
          ResumeMode resumeMode = ResumeMode::Continue;
          if (!ParseResumptionValue(cx, rv, resumeMode, *vp)) {
            return reportUncaughtException(ar);
          }
          return leaveDebugger(ar, frame, thisVForCheck,
                               CallUncaughtExceptionHook::No, resumeMode, *vp);
        } else {
          // Caller is something like onGarbageCollectionHook that
          // doesn't allow Debugger to control debuggee resumption.
          // The return value from uncaughtExceptionHook is ignored.
          return ResumeMode::Continue;
        }
      }
    }

    return reportUncaughtException(ar);
  }

  ar.reset();
  return ResumeMode::Terminate;
}

ResumeMode Debugger::handleUncaughtException(
    Maybe<AutoRealm>& ar, MutableHandleValue vp,
    const Maybe<HandleValue>& thisVForCheck, AbstractFramePtr frame) {
  return handleUncaughtExceptionHelper(ar, &vp, thisVForCheck, frame);
}

ResumeMode Debugger::handleUncaughtException(Maybe<AutoRealm>& ar) {
  return handleUncaughtExceptionHelper(ar, nullptr, mozilla::Nothing(),
                                       NullFramePtr());
}

ResumeMode Debugger::leaveDebugger(Maybe<AutoRealm>& ar, AbstractFramePtr frame,
                                   const Maybe<HandleValue>& maybeThisv,
                                   CallUncaughtExceptionHook callHook,
                                   ResumeMode resumeMode,
                                   MutableHandleValue vp) {
  JSContext* cx = ar->context();
  if (!unwrapDebuggeeValue(cx, vp) ||
      !CheckResumptionValue(cx, frame, maybeThisv, resumeMode, vp)) {
    if (callHook == CallUncaughtExceptionHook::Yes) {
      return handleUncaughtException(ar, vp, maybeThisv, frame);
    }
    return reportUncaughtException(ar);
  }

  ar.reset();
  if (!cx->compartment()->wrap(cx, vp)) {
    resumeMode = ResumeMode::Terminate;
    vp.setUndefined();
  }
  AdjustGeneratorResumptionValue(cx, frame, resumeMode, vp);

  return resumeMode;
}

ResumeMode Debugger::processParsedHandlerResult(Maybe<AutoRealm>& ar,
                                                AbstractFramePtr frame,
                                                jsbytecode* pc, bool success,
                                                ResumeMode resumeMode,
                                                MutableHandleValue vp) {
  JSContext* cx = ar->context();

  RootedValue thisv(cx);
  Maybe<HandleValue> maybeThisv;
  if (!GetThisValueForCheck(cx, frame, pc, &thisv, maybeThisv)) {
    ar.reset();
    return ResumeMode::Terminate;
  }

  if (!success) {
    return handleUncaughtException(ar, vp, maybeThisv, frame);
  }

  return leaveDebugger(ar, frame, maybeThisv, CallUncaughtExceptionHook::Yes,
                       resumeMode, vp);
}

ResumeMode Debugger::processHandlerResult(Maybe<AutoRealm>& ar, bool success,
                                          const Value& rv,
                                          AbstractFramePtr frame,
                                          jsbytecode* pc,
                                          MutableHandleValue vp) {
  JSContext* cx = ar->context();

  RootedValue thisv(cx);
  Maybe<HandleValue> maybeThisv;
  if (!GetThisValueForCheck(cx, frame, pc, &thisv, maybeThisv)) {
    ar.reset();
    return ResumeMode::Terminate;
  }

  if (!success) {
    return handleUncaughtException(ar, vp, maybeThisv, frame);
  }

  RootedValue rootRv(cx, rv);
  ResumeMode resumeMode = ResumeMode::Continue;
  if (!ParseResumptionValue(cx, rootRv, resumeMode, vp)) {
    return handleUncaughtException(ar, vp, maybeThisv, frame);
  }
  return leaveDebugger(ar, frame, maybeThisv, CallUncaughtExceptionHook::Yes,
                       resumeMode, vp);
}

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

/* static */ void Debugger::resultToCompletion(JSContext* cx, bool ok,
                                               const Value& rv,
                                               ResumeMode* resumeMode,
                                               MutableHandleValue value) {
  MOZ_ASSERT_IF(ok, !cx->isExceptionPending());

  if (ok) {
    *resumeMode = ResumeMode::Return;
    value.set(rv);
  } else if (cx->isExceptionPending()) {
    *resumeMode = ResumeMode::Throw;
    if (!cx->getPendingException(value)) {
      *resumeMode = ResumeMode::Terminate;
    }
    cx->clearPendingException();
  } else {
    *resumeMode = ResumeMode::Terminate;
    value.setUndefined();
  }
}

bool Debugger::newCompletionValue(JSContext* cx, ResumeMode resumeMode,
                                  const Value& value_,
                                  MutableHandleValue result) {
  // We must be in the debugger's compartment, since that's where we want
  // to construct the completion value.
  cx->check(object.get());
  cx->check(value_);

  RootedId key(cx);
  RootedValue value(cx, value_);

  switch (resumeMode) {
    case ResumeMode::Return:
      key = NameToId(cx->names().return_);
      break;

    case ResumeMode::Throw:
      key = NameToId(cx->names().throw_);
      break;

    case ResumeMode::Terminate:
      result.setNull();
      return true;

    default:
      MOZ_CRASH("bad resume mode passed to Debugger::newCompletionValue");
  }

  // Common tail for ResumeMode::Return and ResumeMode::Throw.
  RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
  if (!obj ||
      !NativeDefineDataProperty(cx, obj, key, value, JSPROP_ENUMERATE)) {
    return false;
  }

  result.setObject(*obj);
  return true;
}

bool Debugger::receiveCompletionValue(Maybe<AutoRealm>& ar, bool ok,
                                      HandleValue val, MutableHandleValue vp) {
  JSContext* cx = ar->context();

  ResumeMode resumeMode;
  RootedValue value(cx);
  resultToCompletion(cx, ok, val, &resumeMode, &value);
  ar.reset();
  return wrapDebuggeeValue(cx, &value) &&
         newCompletionValue(cx, resumeMode, value, vp);
}

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

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

  Maybe<AutoRealm> ar;
  ar.emplace(cx, object);

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

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

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

  RootedValue exc(cx);
  if (!cx->getPendingException(&exc)) {
    return ResumeMode::Terminate;
  }
  cx->clearPendingException();

  Maybe<AutoRealm> ar;
  ar.emplace(cx, object);

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

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

  RootedValue fval(cx, ObjectValue(*hook));
  RootedValue rv(cx);
  bool ok = js::Call(cx, fval, object, scriptFrame, wrappedExc, &rv);
  ResumeMode resumeMode =
      processHandlerResult(ar, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
  if (resumeMode == ResumeMode::Continue) {
    cx->setPendingException(exc);
  }
  return resumeMode;
}

ResumeMode Debugger::fireEnterFrame(JSContext* cx, 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() && *iter.pc() == JSOP_DEBUGAFTERYIELD) {
    GeneratorObject* genObj =
        GetGeneratorObjectForFrame(cx, iter.abstractFramePtr());
    MOZ_ASSERT(genObj->isRunning() || genObj->isClosing());
  }
#endif

  Maybe<AutoRealm> ar;
  ar.emplace(cx, object);

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

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

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

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

  Maybe<AutoRealm> ar;
  ar.emplace(cx, object);

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

  RootedValue fval(cx, ObjectValue(*hook));
  RootedValue dsval(cx, ObjectValue(*dsobj));
  RootedValue rv(cx);
  if (!js::Call(cx, fval, object, dsval, &rv)) {
    handleUncaughtException(ar);
  }
}

void 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());

  Maybe<AutoRealm> ar;
  ar.emplace(cx, object);

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

  RootedValue fval(cx, ObjectValue(*hook));
  RootedValue dataVal(cx, ObjectValue(*dataObj));
  RootedValue rv(cx);
  if (!js::Call(cx, fval, object, dataVal, &rv)) {
    handleUncaughtException(ar);
  }
}

template <typename HookIsEnabledFun /* bool (Debugger*) */,
          typename FireHookFun /* ResumeMode (Debugger*) */>
/* static */ ResumeMode Debugger::dispatchHook(JSContext* cx,
                                               HookIsEnabledFun hookIsEnabled,
                                               FireHookFun fireHook) {
  // 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.
  //
  // Note: In the general case, 'triggered' contains references to objects in
  // different compartments--every compartment *except* this one.
  AutoValueVector triggered(cx);
  Handle<GlobalObject*> global = cx->global();
  if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
    for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
      Debugger* dbg = *p;
      if (dbg->enabled && hookIsEnabled(dbg)) {
        if (!triggered.append(ObjectValue(*dbg->toJSObject()))) {
          return ResumeMode::Terminate;
        }
      }
    }
  }

  // Deliver the event to each debugger, checking again to make sure it
  // should still be delivered.
  for (Value* p = triggered.begin(); p != triggered.end(); p++) {
    Debugger* dbg = Debugger::fromJSObject(&p->toObject());
    EnterDebuggeeNoExecute nx(cx, *dbg);
    if (dbg->debuggees.has(global) && dbg->enabled && hookIsEnabled(dbg)) {
      ResumeMode resumeMode = fireHook(dbg);
      if (resumeMode != ResumeMode::Continue) {
        return resumeMode;
      }
    }
  }
  return ResumeMode::Continue;
}

void Debugger::slowPathOnNewScript(JSContext* cx, HandleScript script) {
  ResumeMode resumeMode = dispatchHook(
      cx,
      [script](Debugger* dbg) -> bool {
        return dbg->observesNewScript() && dbg->observesScript(script);
      },
      [&](Debugger* dbg) -> ResumeMode {
        Rooted<DebuggerScriptReferent> scriptReferent(cx, script.get());
        dbg->fireNewScript(cx, scriptReferent);
        return ResumeMode::Continue;
      });

  // dispatchHook may fail due to OOM. This OOM is not handlable at the
  // callsites of onNewScript in the engine.
  if (resumeMode == ResumeMode::Terminate) {
    cx->clearPendingException();
    return;
  }

  MOZ_ASSERT(resumeMode == ResumeMode::Continue);
}

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

  // dispatchHook may fail due to OOM. This OOM is not handlable at the
  // callsites of onNewWasmInstance in the engine.
  if (resumeMode == ResumeMode::Terminate) {
    cx->clearPendingException();
    return;
  }

  MOZ_ASSERT(resumeMode == ResumeMode::Continue);
}

/* static */ ResumeMode Debugger::onTrap(JSContext* cx, MutableHandleValue vp) {
  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 = script->getBreakpointSite(pc);
  } else {
    MOZ_ASSERT(iter.isWasm());
    global.set(&iter.wasmInstance()->object()->global());
    isJS = false;
    pc = nullptr;
    bytecodeOffset = iter.wasmBytecodeOffset();
    site = iter.wasmInstance()->debug().getOrCreateBreakpointSite(
        cx, bytecodeOffset);
  }

  // Build list of breakpoint handlers.
  Vector<Breakpoint*> triggered(cx);
  for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
    // Skip a breakpoint that is not set for the current wasm::Instance --
    // single wasm::Code can handle breakpoints for mutiple instances.
    if (!isJS &&
        &bp->asWasm()->wasmInstance->instance() != iter.wasmInstance()) {
      continue;
    }
    if (!triggered.append(bp)) {
      return ResumeMode::Terminate;
    }
  }

  for (Breakpoint** p = triggered.begin(); p != triggered.end(); p++) {
    Breakpoint* bp = *p;

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

    // There are two reasons we have to check whether dbg is enabled and
    // debugging global.
    //
    // One is just that one breakpoint handler can disable other Debuggers
    // or remove debuggees.
    //
    // The other has to do with non-compile-and-go scripts, which have no
    // specific global--until they are executed. Only now do we know which
    // global the script is running against.
    Debugger* dbg = bp->debugger;
    bool hasDebuggee = dbg->enabled && dbg->debuggees.has(global);
    if (hasDebuggee) {
      Maybe<AutoRealm> ar;
      ar.emplace(cx, dbg->object);
      EnterDebuggeeNoExecute nx(cx, *dbg);

      RootedValue scriptFrame(cx);
      if (!dbg->getFrame(cx, iter, &scriptFrame)) {
        return dbg->reportUncaughtException(ar);
      }
      RootedValue rv(cx);
      Rooted<JSObject*> handler(cx, bp->handler);
      bool ok = CallMethodIfPresent(cx, handler, "hit", 1,
                                    scriptFrame.address(), &rv);
      ResumeMode resumeMode = dbg->processHandlerResult(
          ar, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
      if (resumeMode != ResumeMode::Continue) {
        savedExc.drop();
        return resumeMode;
      }

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

  // By convention, return the true op to the interpreter in vp, and return
  // undefined in vp to the wasm debug trap.
  if (isJS) {
    vp.setInt32(JSOp(*pc));
  } else {
    vp.set(UndefinedValue());
  }
  return ResumeMode::Continue;
}

/* static */ ResumeMode Debugger::onSingleStep(JSContext* cx,
                                               MutableHandleValue vp) {
  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<DebuggerFrameVector> frames(cx, DebuggerFrameVector(cx));
  if (!getDebuggerFrames(iter.abstractFramePtr(), &frames)) {
    return ResumeMode::Terminate;
  }

#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 stepperCount = 0;
    JSScript* trappingScript = iter.script();
    GlobalObject* global = cx->global();
    if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
      for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
        Debugger* dbg = *p;
        for (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(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER)
                   .isUndefined()) {
            stepperCount++;
          }
        }
      }
    }
    MOZ_ASSERT(stepperCount == trappingScript->stepModeCount());
  }
#endif

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

    Debugger* dbg = Debugger::fromChildJSObject(frame);
    EnterDebuggeeNoExecute nx(cx, *dbg);

    Maybe<AutoRealm> ar;
    ar.emplace(cx, dbg->object);

    ResumeMode resumeMode = ResumeMode::Continue;
    bool success = handler->onStep(cx, frame, resumeMode, vp);
    resumeMode = dbg->processParsedHandlerResult(
        ar, iter.abstractFramePtr(), iter.pc(), success, resumeMode, vp);
    if (resumeMode != ResumeMode::Continue) {
      savedExc.drop();
      return resumeMode;
    }
  }

  vp.setUndefined();
  return ResumeMode::Continue;
}

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

  Maybe<AutoRealm> ar;
  ar.emplace(cx, object);

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

  // 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;
  }
  // NB: Even though we don't care about what goes into it, we have to pass vp
  // to handleUncaughtException so that it parses resumption values from the
  // uncaughtExceptionHook and tells the caller whether we should execute the
  // rest of the onNewGlobalObject hooks or not.
  ResumeMode resumeMode =
      ok ? ResumeMode::Continue : handleUncaughtException(ar, vp);
  MOZ_ASSERT(!cx->isExceptionPending());
  return resumeMode;
}

void Debugger::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.
  AutoObjectVector 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;
    }
  }

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

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

    // We disallow resumption values from onNewGlobalObject hooks, because we
    // want the debugger hooks for global object creation to be infallible.
    // But if an onNewGlobalObject hook throws, and the uncaughtExceptionHook
    // decides to raise an error, we want to at least avoid invoking the rest
    // of the onNewGlobalObject handlers in the list (not for any super
    // compelling reason, just because it seems like the right thing to do).
    // So we ignore whatever comes out in |value|, but break out of the loop
    // if a non-success resume mode is returned.
    if (dbg->observesNewGlobalObject()) {
      resumeMode = dbg->fireNewGlobalObject(cx, global, &value);
      if (resumeMode != ResumeMode::Continue &&
          resumeMode != ResumeMode::Return) {
        break;
      }
    }
  }
  MOZ_ASSERT(!cx->isExceptionPending());
}

/* static */ bool Debugger::slowPathOnLogAllocationSite(
    JSContext* cx, HandleObject obj, HandleSavedFrame frame,
    mozilla::TimeStamp when, GlobalObject::DebuggerVector& dbgs) {
  MOZ_ASSERT(!dbgs.empty());
  mozilla::DebugOnly<ReadBarriered<Debugger*>*> begin = dbgs.begin();

  // Root all the Debuggers while we're iterating over them;
  // appendAllocationSite calls Compartment::wrap, and thus can GC.
  //
  // SpiderMonkey protocol is generally for the caller to prove that it has
  // rooted the stuff it's asking you to operate on (i.e. by passing a
  // Handle), but in this case, we're iterating over a global's list of
  // Debuggers, and globals only hold their Debuggers weakly.
  Rooted<GCVector<JSObject*>> activeDebuggers(cx, GCVector<JSObject*>(cx));
  for (auto dbgp = dbgs.begin(); dbgp < dbgs.end(); dbgp++) {
    if (!activeDebuggers.append((*dbgp)->object)) {
      return false;
    }
  }

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

    if ((*dbgp)->trackingAllocationSites && (*dbgp)->enabled &&
        !(*dbgp)->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,
                                    HandleSavedFrame frame,
                                    mozilla::TimeStamp when) {
  MOZ_ASSERT(trackingAllocationSites && enabled);

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

  // Try to get the constructor name from the ObjectGroup's TypeNewScript.
  // This is only relevant for native objects.
  RootedAtom ctorName(cx);
  if (obj->is<NativeObject>()) {
    AutoRealm ar(cx, obj);
    if (!JSObject::constructorDisplayAtom(cx, obj, &ctorName)) {
      return false;
    }
  }
  if (ctorName) {
    cx->markAtom(ctorName);
  }

  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, ctorName, size,
                                  inNursery)) {
    ReportOutOfMemory(cx);
    return false;
  }

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

  return true;
}

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

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

  Maybe<AutoRealm> ar;
  ar.emplace(cx, object);

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

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

  ResumeMode resumeMode =
      ok ? ResumeMode::Continue : handleUncaughtException(ar, vp);
  MOZ_ASSERT(!cx->isExceptionPending());
  return resumeMode;
}

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

  RootedValue rval(cx);
  ResumeMode resumeMode = dispatchHook(
      cx, [hook](Debugger* dbg) -> bool { return dbg->getHook(hook); },
      [&](Debugger* dbg) -> ResumeMode {
        (void)dbg->firePromiseHook(cx, hook, promise, &rval);
        return ResumeMode::Continue;
      });

  if (resumeMode == ResumeMode::Terminate) {
    // The dispatch hook function might fail to append into the list of
    // Debuggers which are watching for the hook.
    cx->clearPendingException();
    return;
  }

  // Promise hooks are infallible and we ignore errors from uncaught
  // exceptions by design.
  MOZ_ASSERT(resumeMode == ResumeMode::Continue);
}

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

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

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

  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());
  }

  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

// 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 Debugger::ExecutionObservableSet {
  AbstractFramePtr frame_;

 public:
  explicit ExecutionObservableFrame(
      AbstractFramePtr frame MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
      : frame_(frame) {
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
  }

  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_;
  }

  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

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

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

  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_;
  }

  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

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

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

  AbstractFramePtr oldestEnabledFrame;
  for (FrameIter 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(!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 MarkTypeScriptActiveIfObservable(
    JSScript* script, const Debugger::ExecutionObservableSet& obs) {
  if (obs.shouldRecompileOrInvalidate(script)) {
    script->types()->setActive();
  }
}

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

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

  AutoSuppressProfilerSampling suppressProfilerSampling(cx);

  FreeOp* fop = cx->runtime()->defaultFreeOp();

  Vector<JSScript*> scripts(cx);

  // Iterate through observable scripts, invalidating their Ion scripts and
  // appending them to a vector for discarding their baseline scripts later.
  {
    AutoEnterAnalysis enter(fop, zone);
    if (JSScript* script = obs.singleScriptForZoneInvalidation()) {
      if (obs.shouldRecompileOrInvalidate(script)) {
        if (!AppendAndInvalidateScript(cx, zone, script, scripts)) {
          return false;
        }
      }
    } else {
      for (auto iter = zone->cellIter<JSScript>(); !iter.done(); iter.next()) {
        JSScript* script = iter;
        if (gc::IsAboutToBeFinalizedUnbarriered(&script)) {
          continue;
        }
        if (obs.shouldRecompileOrInvalidate(script)) {
          if (!AppendAndInvalidateScript(cx, zone, script, scripts)) {
            return false;
          }
        }
      }
    }
  }

  // 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:
          MarkTypeScriptActiveIfObservable(frame.script(), obs);
          break;
        case FrameType::IonJS:
          MarkTypeScriptActiveIfObservable(frame.script(), obs);
          for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more();
               ++inlineIter) {
            MarkTypeScriptActiveIfObservable(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]->types()->active()) {
      FinishDiscardBaselineScript(fop, scripts[i]);
    }
    scripts[i]->types()->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::IsObserving::Observing;
      instance->debug().ensureEnterFrameTrapsState(cx, enableTrap);
    }
  }

  return true;
}

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

  typedef ExecutionObservableSet::ZoneRange 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::forEachDebuggerFrame(AbstractFramePtr frame,
                                                 FrameFn fn) {
  GlobalObject* global = frame.global();
  if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
    for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
      Debugger* dbg = *p;
      if (FrameMap::Ptr entry = dbg->frames.lookup(frame)) {
        fn(entry->value());
      }
    }
  }
}

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

/* static */ bool Debugger::updateExecutionObservability(
    JSContext* cx, 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 Debugger::ensureExecutionObservabilityOfOsrFrame(
    JSContext* cx, InterpreterFrame* frame) {
  MOZ_ASSERT(frame->isDebuggee());
  if (frame->script()->hasBaselineScript() &&
      frame->script()->baselineScript()->hasDebugInstrumentation()) {
    return true;
  }
  ExecutionObservableFrame obs(frame);
  return 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 (enabled && !!getHook(OnEnterFrame)) {
    return Observing;
  }
  return NotObserving;
}

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

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

// 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();
  }
}

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

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

/* static */ bool Debugger::isObservedByDebuggerTrackingAllocations(
    const GlobalObject& debuggee) {
  if (auto* v = debuggee.getDebuggers()) {
    for (auto p = v->begin(); p != v->end(); p++) {
      // Use unbarrieredGet() to prevent triggering read barrier while
      // collecting, this is safe as long as dbg does not escape.
      Debugger* dbg = p->unbarrieredGet();
      if (dbg->trackingAllocationSites && dbg->enabled) {
        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(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 (isObservedByDebuggerTrackingAllocations(global)) {
    global.realm()->chooseAllocationSamplingProbability();
    return;
  }

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

void Debugger::traceCrossCompartmentEdges(JSTracer* trc) {
  generatorFrames.traceCrossCompartmentEdges<DebuggerFrame_trace>(trc);
  objects.traceCrossCompartmentEdges<DebuggerObject_trace>(trc);
  environments.traceCrossCompartmentEdges<DebuggerEnv_trace>(trc);
  scripts.traceCrossCompartmentEdges<DebuggerScript_trace>(trc);
  lazyScripts.traceCrossCompartmentEdges<DebuggerScript_trace>(trc);
  sources.traceCrossCompartmentEdges<DebuggerSource_trace>(trc);
  wasmInstanceScripts.traceCrossCompartmentEdges<DebuggerScript_trace>(trc);
  wasmInstanceSources.traceCrossCompartmentEdges<DebuggerSource_trace>(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 Debugger::traceIncomingCrossCompartmentEdges(JSTracer* trc) {
  JSRuntime* rt = trc->runtime();
  gc::State state = rt->gc.state();
  MOZ_ASSERT(state == gc::State::MarkRoots || state == gc::State::Compact);

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

/*
 * This method has two tasks:
 *   1. Mark Debugger objects that are unreachable except for debugger hooks
 *      that may yet be called.
 *   2. Mark breakpoint handlers.
 *
 * This happens during the iterative part of the GC mark phase. This method
 * returns true if it has to mark anything; GC calls it repeatedly until it
 * returns false.
 */
/* static */ bool Debugger::markIteratively(GCMarker* marker) {
  MOZ_ASSERT(JS::RuntimeHeapIsCollecting(),
             "This method should be called during GC.");
  bool markedAny = false;

  // Find all Debugger objects in danger of GC. This code is a little
  // convoluted since the easiest way to find them is via their debuggees.
  JSRuntime* rt = marker->runtime();
  for (RealmsIter r(rt); !r.done(); r.next()) {
    if (r->isDebuggee()) {
      GlobalObject* global = r->unsafeUnbarrieredMaybeGlobal();
      if (!IsMarkedUnbarriered(rt, &global)) {
        continue;
      }

      // Every debuggee has at least one debugger, so in this case
      // getDebuggers can't return nullptr.
      const GlobalObject::DebuggerVector* debuggers = global->getDebuggers();
      MOZ_ASSERT(debuggers);
      for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
        Debugger* dbg = p->unbarrieredGet();

        // dbg is a Debugger with at least one debuggee. Check three things:
        //   - dbg is actually in a compartment that is being marked
        //   - it isn't already marked
        //   - it actually has hooks that might be called
        GCPtrNativeObject& dbgobj = dbg->toJSObjectRef();
        if (!dbgobj->zone()->isGCMarking()) {
          continue;
        }

        bool dbgMarked = IsMarked(rt, &dbgobj);
        if (!dbgMarked && dbg->hasAnyLiveHooks(rt)) {
          // obj could be reachable only via its live, enabled
          // debugger hooks, which may yet be called.
          TraceEdge(marker, &dbgobj, "enabled Debugger");
          markedAny = true;
          dbgMarked = true;
        }

        if (dbgMarked) {
          // Search for breakpoints to mark.
          for (Breakpoint* bp = dbg->firstBreakpoint(); bp;
               bp = bp->nextInDebugger()) {
            switch (bp->site->type()) {
              case BreakpointSite::Type::JS:
                if (IsMarkedUnbarriered(rt, &bp->site->asJS()->script)) {
                  // The debugger and the script are both live.
                  // Therefore the breakpoint handler is live.
                  if (!IsMarked(rt, &bp->getHandlerRef())) {
                    TraceEdge(marker, &bp->getHandlerRef(),
                              "breakpoint handler");
                    markedAny = true;
                  }
                }
                break;
              case BreakpointSite::Type::Wasm:
                if (IsMarkedUnbarriered(rt, &bp->asWasm()->wasmInstance)) {
                  // The debugger and the wasm instance are both live.
                  // Therefore the breakpoint handler is live.
                  if (!IsMarked(rt, &bp->getHandlerRef())) {
                    TraceEdge(marker, &bp->getHandlerRef(),
                              "wasm breakpoint handler");
                    markedAny = true;
                  }
                }
                break;
            }
          }
        }
      }
    }
  }
  return markedAny;
}

/* static */ void Debugger::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()) {
    TraceManuallyBarrieredEdge(trc, e.mutableFront().unsafeGet(),
                               "Global Object");
  }

  for (Breakpoint* bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
    switch (bp->site->type()) {
      case BreakpointSite::Type::JS:
        TraceManuallyBarrieredEdge(trc, &bp->site->asJS()->script,
                                   "breakpoint script");
        break;
      case BreakpointSite::Type::Wasm:
        TraceManuallyBarrieredEdge(trc, &bp->asWasm()->wasmInstance,
                                   "breakpoint wasm instance");
        break;
    }
    TraceEdge(trc, &bp->getHandlerRef(), "breakpoint handler");
  }
}

/* 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. These are all reachable from JS, because the
  // corresponding JS frames are still on the stack.
  //
  // (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->getPrivate(frameobj->numFixedSlotsMaybeForwarded()));
  }

  allocationsLog.trace(trc);

  generatorFrames.trace(trc);
  scripts.trace(trc);
  lazyScripts.trace(trc);
  sources.trace(trc);
  objects.trace(trc);
  environments.trace(trc);
  wasmInstanceScripts.trace(trc);
  wasmInstanceSources.trace(trc);
}

/* static */ void Debugger::sweepAll(FreeOp* fop) {
  JSRuntime* rt = fop->runtime();

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

    // 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(fop, e.front().unbarrieredGet(), &e);
      }
    }

    if (debuggerDying) {
      fop->delete_(dbg);
    }

    dbg = next;
  }
}

/* static */ void Debugger::detachAllDebuggersFromGlobal(FreeOp* fop,
                                                         GlobalObject* global) {
  const GlobalObject::DebuggerVector* debuggers = global->getDebuggers();
  MOZ_ASSERT(!debuggers->empty());
  while (!debuggers->empty()) {
    debuggers->back()->removeDebuggeeGlobal(fop, global, nullptr);
  }
}

/* static */ bool Debugger::findSweepGroupEdges(Zone* zone) {
  JSRuntime* rt = zone->runtimeFromMainThread();
  for (Debugger* dbg : rt->debuggerList()) {
    Zone* debuggerZone = dbg->object->zone();
    if (!debuggerZone->isGCMarking()) {
      continue;
    }

    if (debuggerZone == zone) {
      // Add edges to debuggee zones. These are weak references that are
      // not in the cross compartment wrapper map.
      for (auto e = dbg->debuggeeZones.all(); !e.empty(); e.popFront()) {
        Zone* debuggeeZone = e.front();
        if (debuggeeZone->isGCMarking()) {
          if (!zone->addSweepGroupEdgeTo(debuggeeZone)) {
            return false;
          }
        }
      }
    } else {
      // For debugger cross compartment wrappers, add edges in the
      // opposite direction to those already added by
      // Compartment::findOutgoingEdges and above. This ensure that
      // debuggers and their debuggees are finalized in the same group.
      if (dbg->debuggeeZones.has(zone) ||
          dbg->generatorFrames.hasKeyInZone(zone) ||
          dbg->scripts.hasKeyInZone(zone) ||
          dbg->lazyScripts.hasKeyInZone(zone) ||
          dbg->sources.hasKeyInZone(zone) || dbg->objects.hasKeyInZone(zone) ||
          dbg->environments.hasKeyInZone(zone) ||
          dbg->wasmInstanceScripts.hasKeyInZone(zone) ||
          dbg->wasmInstanceSources.hasKeyInZone(zone)) {
        if (!zone->addSweepGroupEdgeTo(debuggerZone)) {
          return false;
        }
      }
    }
  }
  return true;
}

const ClassOps Debugger::classOps_ = {nullptr, /* addProperty */
                                      nullptr, /* delProperty */
                                      nullptr, /* enumerate   */
                                      nullptr, /* newEnumerate */
                                      nullptr, /* resolve     */
                                      nullptr, /* mayResolve  */
                                      nullptr, /* finalize    */
                                      nullptr, /* call        */
                                      nullptr, /* hasInstance */
                                      nullptr, /* construct   */
                                      Debugger::traceObject};

const Class Debugger::class_ = {
    "Debugger",
    JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT),
    &Debugger::classOps_};

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

  // Forbid Debugger.prototype, which is of the Debugger JSClass but isn't
  // really a Debugger object. The prototype object is distinguished by
  // having a nullptr private value.
  Debugger* dbg = Debugger::fromJSObject(thisobj);
  if (!dbg) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_INCOMPATIBLE_PROTO, "Debugger", fnname,
                              "prototype object");
  }
  return dbg;
}

#define THIS_DEBUGGER(cx, argc, vp, fnname, args, dbg)      \
  CallArgs args = CallArgsFromVp(argc, vp);                 \
  Debugger* dbg = Debugger_fromThisValue(cx, args, fnname); \
  if (!dbg) return false

/* static */ bool Debugger::getEnabled(JSContext* cx, unsigned argc,
                                       Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "get enabled", args, dbg);
  args.rval().setBoolean(dbg->enabled);
  return true;
}

/* static */ bool Debugger::setEnabled(JSContext* cx, unsigned argc,
                                       Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "set enabled", args, dbg);
  if (!args.requireAtLeast(cx, "Debugger.set enabled", 1)) {
    return false;
  }

  bool wasEnabled = dbg->enabled;
  dbg->enabled = ToBoolean(args[0]);

  if (wasEnabled != dbg->enabled) {
    if (dbg->trackingAllocationSites) {
      if (wasEnabled) {
        dbg->removeAllocationsTrackingForAllDebuggees();
      } else {
        if (!dbg->addAllocationsTrackingForAllDebuggees(cx)) {
          dbg->enabled = false;
          return false;
        }
      }
    }

    for (Breakpoint* bp = dbg->firstBreakpoint(); bp;
         bp = bp->nextInDebugger()) {
      if (!wasEnabled) {
        bp->site->inc(cx->runtime()->defaultFreeOp());
      } else {
        bp->site->dec(cx->runtime()->defaultFreeOp());
      }
    }

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

    // Ensure the compartment is observable if we are re-enabling a
    // Debugger with hooks that observe all execution.
    if (!dbg->updateObservesAllExecutionOnDebuggees(
            cx, dbg->observesAllExecution())) {
      return false;
    }

    // Note: To toogle code coverage, we currently need to have no live
    // stack frame, thus the coverage does not depend on the enabled flag.

    dbg->updateObservesAsmJSOnDebuggees(dbg->observesAsmJS());
  }

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

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

/* static */ bool Debugger::setHookImpl(JSContext* cx, 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;
  }
  uint32_t slot = JSSLOT_DEBUG_HOOK_START + 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;
    }
  }
  args.rval().setUndefined();
  return true;
}

/* static */ bool Debugger::getOnDebuggerStatement(JSContext* cx, unsigned argc,
                                                   Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(get onDebuggerStatement)", args, dbg);
  return getHookImpl(cx, args, *dbg, OnDebuggerStatement);
}

/* static */ bool Debugger::setOnDebuggerStatement(JSContext* cx, unsigned argc,
                                                   Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(set onDebuggerStatement)", args, dbg);
  return setHookImpl(cx, args, *dbg, OnDebuggerStatement);
}

/* static */ bool Debugger::getOnExceptionUnwind(JSContext* cx, unsigned argc,
                                                 Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(get onExceptionUnwind)", args, dbg);
  return getHookImpl(cx, args, *dbg, OnExceptionUnwind);
}

/* static */ bool Debugger::setOnExceptionUnwind(JSContext* cx, unsigned argc,
                                                 Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(set onExceptionUnwind)", args, dbg);
  return setHookImpl(cx, args, *dbg, OnExceptionUnwind);
}

/* static */ bool Debugger::getOnNewScript(JSContext* cx, unsigned argc,
                                           Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(get onNewScript)", args, dbg);
  return getHookImpl(cx, args, *dbg, OnNewScript);
}

/* static */ bool Debugger::setOnNewScript(JSContext* cx, unsigned argc,
                                           Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(set onNewScript)", args, dbg);
  return setHookImpl(cx, args, *dbg, OnNewScript);
}

/* static */ bool Debugger::getOnNewPromise(JSContext* cx, unsigned argc,
                                            Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(get onNewPromise)", args, dbg);
  return getHookImpl(cx, args, *dbg, OnNewPromise);
}

/* static */ bool Debugger::setOnNewPromise(JSContext* cx, unsigned argc,
                                            Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(set onNewPromise)", args, dbg);
  return setHookImpl(cx, args, *dbg, OnNewPromise);
}

/* static */ bool Debugger::getOnPromiseSettled(JSContext* cx, unsigned argc,
                                                Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(get onPromiseSettled)", args, dbg);
  return getHookImpl(cx, args, *dbg, OnPromiseSettled);
}

/* static */ bool Debugger::setOnPromiseSettled(JSContext* cx, unsigned argc,
                                                Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(set onPromiseSettled)", args, dbg);
  return setHookImpl(cx, args, *dbg, OnPromiseSettled);
}

/* static */ bool Debugger::getOnEnterFrame(JSContext* cx, unsigned argc,
                                            Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(get onEnterFrame)", args, dbg);
  return getHookImpl(cx, args, *dbg, OnEnterFrame);
}

/* static */ bool Debugger::setOnEnterFrame(JSContext* cx, unsigned argc,
                                            Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(set onEnterFrame)", args, dbg);
  return setHookImpl(cx, args, *dbg, OnEnterFrame);
}

/* static */ bool Debugger::getOnNewGlobalObject(JSContext* cx, unsigned argc,
                                                 Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "(get onNewGlobalObject)", args, dbg);
  return getHookImpl(cx, args, *dbg, OnNewGlobalObject);
}

/* static */ bool Debugger::setOnNewGlobalObject(JSContext* cx, unsigned argc,
                                                 Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "setOnNewGlobalObject", args, dbg);
  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.
  if (dbg->enabled) {
    JSObject* newHook = dbg->getHook(OnNewGlobalObject);
    if (!oldHook && newHook) {
      cx->runtime()->onNewGlobalObjectWatchers().pushBack(dbg);
    } else if (oldHook && !newHook) {
      cx->runtime()->onNewGlobalObjectWatchers().remove(dbg);
    }
  }

  return true;
}

/* static */ bool Debugger::getUncaughtExceptionHook(JSContext* cx,
                                                     unsigned argc, Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "get uncaughtExceptionHook", args, dbg);
  args.rval().setObjectOrNull(dbg->uncaughtExceptionHook);
  return true;
}

/* static */ bool Debugger::setUncaughtExceptionHook(JSContext* cx,
                                                     unsigned argc, Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "set uncaughtExceptionHook", args, dbg);
  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;
}

/* static */ bool Debugger::getAllowUnobservedAsmJS(JSContext* cx,
                                                    unsigned argc, Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "get allowUnobservedAsmJS", args, dbg);
  args.rval().setBoolean(dbg->allowUnobservedAsmJS);
  return true;
}

/* static */ bool Debugger::setAllowUnobservedAsmJS(JSContext* cx,
                                                    unsigned argc, Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "set allowUnobservedAsmJS", args, dbg);
  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;
}

/* static */ bool Debugger::getCollectCoverageInfo(JSContext* cx, unsigned argc,
                                                   Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "get collectCoverageInfo", args, dbg);
  args.rval().setBoolean(dbg->collectCoverageInfo);
  return true;
}

/* static */ bool Debugger::setCollectCoverageInfo(JSContext* cx, unsigned argc,
                                                   Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "set collectCoverageInfo", args, dbg);
  if (!args.requireAtLeast(cx, "Debugger.set collectCoverageInfo", 1)) {
    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;
}

/* static */ bool Debugger::getMemory(JSContext* cx, unsigned argc, Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "get memory", args, dbg);
  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 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>();
}

/* static */ bool Debugger::addDebuggee(JSContext* cx, unsigned argc,
                                        Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "addDebuggee", args, dbg);
  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;
}

/* static */ bool Debugger::addAllGlobalsAsDebuggees(JSContext* cx,
                                                     unsigned argc, Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "addAllGlobalsAsDebuggees", args, dbg);
  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;
      }
      r->compartment()->gcState.scheduledForDestruction = false;
      GlobalObject* global = r->maybeGlobal();
      if (global) {
        Rooted<GlobalObject*> rg(cx, global);
        if (!dbg->addDebuggeeGlobal(cx, rg)) {
          return false;
        }
      }
    }
  }

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

/* static */ bool Debugger::removeDebuggee(JSContext* cx, unsigned argc,
                                           Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "removeDebuggee", args, dbg);

  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->runtime()->defaultFreeOp(), global, nullptr);

    // 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->getDebuggers()->empty() && !obs.add(global->realm())) {
      return false;
    }
    if (!updateExecutionObservability(cx, obs, NotObserving)) {
      return false;
    }
  }

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

/* static */ bool Debugger::removeAllDebuggees(JSContext* cx, unsigned argc,
                                               Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg);

  ExecutionObservableRealms obs(cx);

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

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

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

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

/* static */ bool Debugger::hasDebuggee(JSContext* cx, unsigned argc,
                                        Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "hasDebuggee", args, dbg);
  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;
}

/* static */ bool Debugger::getDebuggees(JSContext* cx, unsigned argc,
                                         Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "getDebuggees", args, dbg);

  // 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();
  AutoValueVector 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());
    }
  }

  RootedArrayObject arrobj(cx, NewDenseFullyAllocatedArray(cx, count));
  if (!arrobj) {
    return false;
  }
  arrobj->ensureDenseInitializedLength(cx, 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;
}

/* static */ bool Debugger::getNewestFrame(JSContext* cx, unsigned argc,
                                           Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);

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

/* static */ bool Debugger::clearAllBreakpoints(JSContext* cx, unsigned argc,
                                                Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "clearAllBreakpoints", args, dbg);
  for (WeakGlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty();
       r.popFront()) {
    r.front()->realm()->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg,
                                           nullptr);
  }
  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 = NonNullObject(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;
  }
  RootedNativeObject proto(cx, &v.toObject().as<NativeObject>());
  MOZ_ASSERT(proto->getClass() == &Debugger::class_);

  // 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.
  RootedNativeObject obj(cx, NewNativeObjectWithGivenProto(
                                 cx, &Debugger::class_, proto, TenuredObject));
  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());

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

    debugger = dbg.release();
    obj->setPrivate(debugger);  // owns the released pointer
  }

  // 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()) {
      GlobalObject::DebuggerVector* v = realm->maybeGlobal()->getDebuggers();
      for (auto p = v->begin(); p != v->end(); p++) {
        Realm* next = (*p)->object->realm();
        if (Find(visited, next) == visited.end() && !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. it must be in zone->getDebuggers(),
  // 4. the debuggee's zone must be in this->debuggeeZones,
  // 5. if we are tracking allocations, the SavedStacksMetadataBuilder must be
  //    installed for this realm, and
  // 6. Realm::isDebuggee()'s bit must be set.
  //
  // All six indications must be kept consistent.

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

  // (1)
  auto* globalDebuggers = GlobalObject::getOrCreateDebuggers(cx, global);
  if (!globalDebuggers) {
    return false;
  }
  if (!globalDebuggers->append(this)) {
    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)
  auto* zoneDebuggers = zone->getOrCreateDebuggers(cx);
  if (!zoneDebuggers) {
    return false;
  }
  if (addingZoneRelation && !zoneDebuggers->append(this)) {
    ReportOutOfMemory(cx);
    return false;
  }
  auto zoneDebuggersGuard = MakeScopeExit([&] {
    if (addingZoneRelation) {
      zoneDebuggers->popBack();
    }
  });

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

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

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

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

  globalDebuggersGuard.release();
  debuggeesGuard.release();
  zoneDebuggersGuard.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>
static T* findDebuggerInVector(Debugger* dbg,
                               Vector<T, 0, js::SystemAllocPolicy>* vec) {
  T* p;
  for (p = vec->begin(); p != vec->end(); p++) {
    if (*p == dbg) {
      break;
    }
  }
  MOZ_ASSERT(p != vec->end());
  return p;
}

// a ReadBarriered version for findDebuggerInVector
// TODO: Bug 1515934 - findDebuggerInVector<T> triggers read barriers.
static ReadBarriered<Debugger*>* findDebuggerInVector(
    Debugger* dbg,
    Vector<ReadBarriered<Debugger*>, 0, js::SystemAllocPolicy>* vec) {
  ReadBarriered<Debugger*>* p;
  for (p = vec->begin(); p != vec->end(); p++) {
    if (p->unbarrieredGet() == dbg) {
      break;
    }
  }
  MOZ_ASSERT(p != vec->end());
  return p;
}

void Debugger::removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global,
                                    WeakGlobalObjectSet::Enum* debugEnum) {
  // 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);

  // FIXME Debugger::slowPathOnLeaveFrame needs to kill all Debugger.Frame
  // objects referring to a particular JS stack frame. This is hard if
  // Debugger objects that are no longer debugging the relevant global might
  // have live Frame objects. So we take the easy way out and kill them here.
  // This is a bug, since it's observable and contrary to the spec. One
  // possible fix would be to put such objects into a compartment-wide bag
  // which slowPathOnLeaveFrame would have to examine.
  for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
    AbstractFramePtr frame = e.front().key();
    DebuggerFrame* frameobj = e.front().value();
    if (frame.hasGlobal(global)) {
      frameobj->freeFrameIterData(fop);
      DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame,
                                                           frameobj);
      e.removeFront();
    }
  }

  // Clear this global's generators from generatorFrames as well.
  //
  // This method can be called either from script (dbg.removeDebuggee) or
  // from an awkward time during GC sweeping. In the latter case, skip this
  // loop to avoid touching dead objects. It's correct because, when we're
  // called from GC, all `global`'s generators are guaranteed to be dying:
  // live generators would keep the global alive and we wouldn't be here. GC
  // will sweep dead keys from the weakmap.
  if (!global->zone()->isGCSweeping()) {
    generatorFrames.removeIf([global](JSObject* key) {
      GeneratorObject& genObj = key->as<GeneratorObject>();
      return genObj.isClosed() || &genObj.callee().global() == global;
    });
  }

  auto* globalDebuggersVector = global->getDebuggers();
  auto* zoneDebuggersVector = global->zone()->getDebuggers();

  // 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();

  if (!debuggeeZones.has(global->zone())) {
    zoneDebuggersVector->erase(findDebuggerInVector(this, zoneDebuggersVector));
  }

  // Remove all breakpoints for the debuggee.
  Breakpoint* nextbp;
  for (Breakpoint* bp = firstBreakpoint(); bp; bp = nextbp) {
    nextbp = bp->nextInDebugger();
    switch (bp->site->type()) {
      case BreakpointSite::Type::JS:
        if (bp->site->asJS()->script->realm() == global->realm()) {
          bp->destroy(fop);
        }
        break;
      case BreakpointSite::Type::Wasm:
        if (bp->asWasm()->wasmInstance->realm() == global->realm()) {
          bp->destroy(fop);
        }
        break;
      default:
        MOZ_CRASH("unknown breakpoint type");
    }
  }
  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->getDebuggers()->empty()) {
    global->realm()->unsetIsDebuggee();
  } else {
    global->realm()->updateDebuggerObservesAllExecution();
    global->realm()->updateDebuggerObservesAsmJS();
    global->realm()->updateDebuggerObservesCoverage();
  }
}

static inline DebuggerSourceReferent GetSourceReferent(JSObject* obj);

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

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

  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),
        hasSource(false),
        source(cx, AsVariant(static_cast<ScriptSourceObject*>(nullptr))),
        hasLine(false),
        line(0),
        innermost(false),
        innermostForRealm(cx->zone()),
        scriptVector(cx, ScriptVector(cx)),
        lazyScriptVector(cx, LazyScriptVector(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().getClass() != &DebuggerSource_class) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_UNEXPECTED_TYPE,
                                  "query object's 'source' property",
                                  "not undefined nor a Debugger.Source object");
        return false;
      }

      Value owner =
          debuggerSource.toObject().as<NativeObject>().getReservedSlot(
              JSSLOT_DEBUGSOURCE_OWNER);

      // The given source must have an owner. Otherwise, it's a
      // Debugger.Source.prototype, which would match no scripts, and is
      // probably a mistake.
      if (!owner.isObject()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_DEBUG_PROTO, "Debugger.Source",
                                  "Debugger.Source");
        return false;
      }

      // 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 (&owner.toObject() != debugger->object) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_DEBUG_WRONG_OWNER, "Debugger.Source");
        return false;
      }

      hasSource = true;
      source = GetSourceReferent(&debuggerSource.toObject());
    }

    // 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);
        return false;
      }
      double doubleLine = lineProperty.toNumber();
      if (doubleLine <= 0 || (unsigned int)doubleLine != doubleLine) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_DEBUG_BAD_LINE);
        return false;
      }
      hasLine = true;
      line = doubleLine;
    } else {
      JS_ReportErrorNumberASCII(
          cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
          "query object's 'line' property", "neither undefined nor an integer");
      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;
    }

    bool delazified = false;
    if (needsDelazifyBeforeQuery()) {
      if (!delazifyScripts()) {
        return false;
      }
      delazified = true;
    }

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

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

    // For most queries, we just accumulate results in 'scriptVector' and
    // 'lazyScriptVector' as we find them. But if this is an 'innermost'
    // query, then we've accumulated the results in the 'innermostForRealm'
    // map. In that case, we now need to walk that map and
    // populate 'scriptVector'.
    if (innermost) {
      for (RealmToScriptMap::Range r = innermostForRealm.all(); !r.empty();
           r.popFront()) {
        JS::ExposeScriptToActiveJS(r.front().value());
        if (!scriptVector.append(r.front().value())) {
          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<ScriptVector> foundScripts() const { return scriptVector; }
  Handle<LazyScriptVector> foundLazyScripts() const { return lazyScriptVector; }

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

 private:
  /* 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. */
  RootedLinearString 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;
  Rooted<DebuggerSourceReferent> source;

  /* True if the query contained a 'line' property. */
  bool hasLine;

  /* The line matching scripts must cover. */
  unsigned int line;

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

  using RealmToScriptMap =
      HashMap<Realm*, JSScript*, DefaultHasher<Realm*>, ZoneAllocPolicy>;

  /*
   * For 'innermost' queries, a map from realms to the innermost script
   * we've seen so far in that realm. (Template instantiation code size
   * explosion ho!)
   */
  RealmToScriptMap innermostForRealm;

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

  /*
   * 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()) {
      urlCString = JS_EncodeStringToLatin1(cx, url.toString());
      if (!urlCString) {
        return false;
      }
    }

    return true;
  }

  bool delazifyScripts() {
    // All scripts in debuggee realms must be visible, so delazify
    // everything.
    for (auto r = realms.all(); !r.empty(); r.popFront()) {
      Realm* realm = r.front();
      if (!realm->ensureDelazifyScriptsForDebugger(cx)) {
        return false;
      }
    }
    return true;
  }

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

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

  bool needsDelazifyBeforeQuery() const {
    // * innermost
    //   Currently not supported, since this is not used outside of test.
    //
    // * hasLine
    //   Only JSScript supports GetScriptLineExtent.
    return innermost || hasLine;
  }

  template <typename T>
  MOZ_MUST_USE 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| or place it
   * in |innermostForRealm|, as appropriate. Set |oom| if an out of memory
   * condition occurred.
   */
  void consider(JSScript* script, const JS::AutoRequireNoGC& nogc) {
    if (oom || script->selfHosted()) {
      return;
    }
    Realm* realm = script->realm();
    if (!realms.has(realm)) {
      return;
    }
    if (!commonFilter(script, nogc)) {
      return;
    }
    if (hasLine) {
      if (line < script->lineno() ||
          script->lineno() + GetScriptLineExtent(script) < line) {
        return;
      }
    }

    if (innermost) {
      // For 'innermost' queries, we don't place scripts in
      // |scriptVector| right away; we may later find another script that
      // is nested inside this one. Instead, we record the innermost
      // script we've found so far for each realm in innermostForRealm,
      // and only populate |scriptVector| at the bottom of findScripts,
      // when we've traversed all the scripts.
      //
      // So: check this script against the innermost one we've found so
      // far (if any), as recorded in innermostForRealm, and replace that
      // if it's better.
      RealmToScriptMap::AddPtr p = innermostForRealm.lookupForAdd(realm);
      if (p) {
        // Is our newly found script deeper than the last one we found?
        JSScript* incumbent = p->value();
        if (script->innermostScope()->chainLength() >
            incumbent->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)) {
          oom = true;
          return;
        }
      }
    } else {
      // Record this matching script in the results scriptVector.
      if (!scriptVector.append(script)) {
        oom = true;
        return;
      }
    }
  }

  void consider(LazyScript* lazyScript, const JS::AutoRequireNoGC& nogc) {
    MOZ_ASSERT(!needsDelazifyBeforeQuery());

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

    // If the script is already delazified, it should be in scriptVector.
    if (lazyScript->maybeScript()) {
      return;
    }

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

    /* Record this matching script in the results lazyScriptVector. */
    if (!lazyScriptVector.append(lazyScript)) {
      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;
    }
  }
};

/* static */ bool Debugger::findScripts(JSContext* cx, unsigned argc,
                                        Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "findScripts", args, dbg);

  ScriptQuery query(cx, dbg);

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

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

  Handle<ScriptVector> scripts(query.foundScripts());
  Handle<LazyScriptVector> lazyScripts(query.foundLazyScripts());
  Handle<WasmInstanceObjectVector> wasmInstances(query.foundWasmInstances());

  size_t resultLength =
      scripts.length() + lazyScripts.length() + wasmInstances.length();
  RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, resultLength));
  if (!result) {
    return false;
  }

  result->ensureDenseInitializedLength(cx, 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 lazyStart = scripts.length();
  for (size_t i = 0; i < lazyScripts.length(); i++) {
    JSObject* scriptObject = dbg->wrapLazyScript(cx, lazyScripts[i]);
    if (!scriptObject) {
      return false;
    }
    result->setDenseElement(lazyStart + i, ObjectValue(*scriptObject));
  }

  size_t wasmStart = scripts.length() + lazyScripts.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::MovableCellHasher<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);
    IterateLazyScripts(cx, singletonRealm, this, considerLazyScript);
    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, JSScript* script,
                             const JS::AutoRequireNoGC& nogc) {
    SourceQuery* self = static_cast<SourceQuery*>(data);
    self->consider(script, nogc);
  }

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

  void consider(JSScript* script, const JS::AutoRequireNoGC& nogc) {
    if (oom || script->selfHosted()) {
      return;
    }
    Realm* realm = script->realm();
    if (!realms.has(realm)) {
      return;
    }

    if (!script->sourceObject()) {
      return;
    }

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

  void consider(LazyScript* lazyScript, const JS::AutoRequireNoGC& nogc) {
    if (oom) {
      return;
    }
    Realm* realm = lazyScript->realm();
    if (!realms.has(realm)) {
      return;
    }

    // If the script is already delazified, it should already be handled.
    if (lazyScript->maybeScript()) {
      return;
    }

    ScriptSourceObject* source = &lazyScript->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>());
}

/* static */ bool Debugger::findSources(JSContext* cx, unsigned argc,
                                        Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "findSources", args, dbg);

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

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

  size_t resultLength = sources.count();
  RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, resultLength));
  if (!result) {
    return false;
  }

  result->ensureDenseInitializedLength(cx, 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), className(cx) {}

  /* The vector that we are accumulating results in. */
  AutoObjectVector 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()) {
      if (!cls.isString()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_UNEXPECTED_TYPE,
                                  "query object's 'class' property",
                                  "neither undefined nor a string");
        return false;
      }
      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",
            "not a string containing only ASCII characters");
        return false;
      }
      className = cls;
    }
    return true;
  }

  /* Set up this ObjectQuery appropriately for a missing query argument. */
  void omittedQuery() { className.setUndefined(); }

  /*
   * 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.
      Maybe<JS::AutoCheckCannotGC> maybeNoGC;
      RootedObject dbgObj(cx, dbg->object);
      JS::ubi::RootList rootList(cx, maybeNoGC);
      if (!rootList.init(dbgObj)) {
        ReportOutOfMemory(cx);
        return false;
      }

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

      return traversal.addStart(JS::ubi::Node(&rootList)) &&
             traversal.traverse();
    }
  }

  /*
   * |ubi::Node::BreadthFirst| interface.
   */
  class NodeData {};
  typedef JS::ubi::BreadthFirst<ObjectQuery> Traversal;
  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>();

    if (!className.isUndefined()) {
      const char* objClassName = obj->getClass()->name;
      if (strcmp(objClassName, classNameCString.get()) != 0) {
        return true;
      }
    }

    return objects.append(obj);
  }

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

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

  /*
   * If this is non-null, matching objects will have a class whose name is
   * this property.
   */
  RootedValue className;

  /* The className member, as a C string. */
  UniqueChars classNameCString;

  /*
   * Given that either omittedQuery or parseQuery has been called, prepare the
   * query for matching objects.
   */
  bool prepareQuery() {
    if (className.isString()) {
      classNameCString = JS_EncodeStringToASCII(cx, className.toString());
      if (!classNameCString) {
        return false;
      }
    }

    return true;
  }
};

bool Debugger::findObjects(JSContext* cx, unsigned argc, Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "findObjects", args, dbg);

  ObjectQuery query(cx, dbg);

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

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

  size_t length = query.objects.length();
  RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
  if (!result) {
    return false;
  }

  result->ensureDenseInitializedLength(cx, 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;
}

/* static */ bool Debugger::findAllGlobals(JSContext* cx, unsigned argc,
                                           Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "findAllGlobals", args, dbg);

  AutoObjectVector 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;
      }

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

      GlobalObject* global = r->maybeGlobal();

      if (cx->runtime()->isSelfHostingGlobal(global)) {
        continue;
      }

      if (global) {
        // 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;
}

/* static */ bool Debugger::makeGlobalObjectReference(JSContext* cx,
                                                      unsigned argc,
                                                      Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "makeGlobalObjectReference", args, dbg);
  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;

  CompileOptions options(cx);
  frontend::UsedNameTracker usedNames(cx);

  RootedScriptSourceObject sourceObject(
      cx, frontend::CreateScriptSourceObject(cx, options, Nothing()));
  if (!sourceObject) {
    return false;
  }

  JS::AutoSuppressWarningReporter suppressWarnings(cx);
  frontend::Parser<frontend::FullParseHandler, char16_t> parser(
      cx, cx->tempLifoAlloc(), options, chars.twoByteChars(), length,
      /* foldConstants = */ true, usedNames, nullptr, nullptr, sourceObject,
      frontend::ParseGoal::Script);
  if (!parser.checkOptions() || !parser.parse()) {
    // We ran into an error. If it was because we ran out of memory we report
    // it in the usual way.
    if (cx->isThrowingOutOfMemory()) {
      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;
    }

    cx->clearPendingException();
  }

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

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

  if (mozilla::recordreplay::IsMiddleman()) {
    JSString* str = JS_NewStringCopyZ(cx, "Middleman");
    if (!str) {
      return false;
    }
    args.rval().setString(str);
  } else if (mozilla::recordreplay::IsRecordingOrReplaying()) {
    JSString* str = JS_NewStringCopyZ(cx, "RecordingReplaying");
    if (!str) {
      return false;
    }
    args.rval().setString(str);
  } else {
    args.rval().setUndefined();
  }
  return true;
}

bool Debugger::adoptDebuggeeValue(JSContext* cx, unsigned argc, Value* vp) {
  THIS_DEBUGGER(cx, argc, vp, "adoptDebuggeeValue", args, dbg);
  if (!args.requireAtLeast(cx, "Debugger.adoptDebuggeeValue", 1)) {
    return false;
  }

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

    obj.set(static_cast<JSObject*>(ndobj->getPrivate()));
    v = ObjectValue(*obj);

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

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

const JSPropertySpec Debugger::properties[] = {
    JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0),
    JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement,
            Debugger::setOnDebuggerStatement, 0),
    JS_PSGS("onExceptionUnwind", Debugger::getOnExceptionUnwind,
            Debugger::setOnExceptionUnwind, 0),
    JS_PSGS("onNewScript", Debugger::getOnNewScript, Debugger::setOnNewScript,
            0),
    JS_PSGS("onNewPromise", Debugger::getOnNewPromise,
            Debugger::setOnNewPromise, 0),
    JS_PSGS("onPromiseSettled", Debugger::getOnPromiseSettled,
            Debugger::setOnPromiseSettled, 0),
    JS_PSGS("onEnterFrame", Debugger::getOnEnterFrame,
            Debugger::setOnEnterFrame, 0),
    JS_PSGS("onNewGlobalObject", Debugger::getOnNewGlobalObject,
            Debugger::setOnNewGlobalObject, 0),
    JS_PSGS("uncaughtExceptionHook", Debugger::getUncaughtExceptionHook,
            Debugger::setUncaughtExceptionHook, 0),
    JS_PSGS("allowUnobservedAsmJS", Debugger::getAllowUnobservedAsmJS,
            Debugger::setAllowUnobservedAsmJS, 0),
    JS_PSGS("collectCoverageInfo", Debugger::getCollectCoverageInfo,
            Debugger::setCollectCoverageInfo, 0),
    JS_PSG("memory", Debugger::getMemory, 0),
    JS_PS_END};

const JSFunctionSpec Debugger::methods[] = {
    JS_FN("addDebuggee", Debugger::addDebuggee, 1, 0),
    JS_FN("addAllGlobalsAsDebuggees", Debugger::addAllGlobalsAsDebuggees, 0, 0),
    JS_FN("removeDebuggee", Debugger::removeDebuggee, 1, 0),
    JS_FN("removeAllDebuggees", Debugger::removeAllDebuggees, 0, 0),
    JS_FN("hasDebuggee", Debugger::hasDebuggee, 1, 0),
    JS_FN("getDebuggees", Debugger::getDebuggees, 0, 0),
    JS_FN("getNewestFrame", Debugger::getNewestFrame, 0, 0),
    JS_FN("clearAllBreakpoints", Debugger::clearAllBreakpoints, 0, 0),
    JS_FN("findScripts", Debugger::findScripts, 1, 0),
    JS_FN("findSources", Debugger::findSources, 1, 0),
    JS_FN("findObjects", Debugger::findObjects, 1, 0),
    JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0),
    JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1,
          0),
    JS_FN("adoptDebuggeeValue", Debugger::adoptDebuggeeValue, 1, 0),
    JS_FS_END};

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

/*** Debugger.Script ********************************************************/

// Get the Debugger.Script referent as bare Cell. This should only be used for
// GC operations like tracing. Please use GetScriptReferent below.
static inline gc::Cell* GetScriptReferentCell(JSObject* obj) {
  MOZ_ASSERT(obj->getClass() == &DebuggerScript_class);
  return static_cast<gc::Cell*>(obj->as<NativeObject>().getPrivate());
}

static inline DebuggerScriptReferent GetScriptReferent(JSObject* obj) {
  MOZ_ASSERT(obj->getClass() == &DebuggerScript_class);
  if (gc::Cell* cell = GetScriptReferentCell(obj)) {
    if (cell->is<JSScript>()) {
      return AsVariant(cell->as<JSScript>());
    }
    if (cell->is<LazyScript>()) {
      return AsVariant(cell->as<LazyScript>());
    }
    MOZ_ASSERT(cell->is<JSObject>());
    return AsVariant(
        &static_cast<NativeObject*>(cell)->as<WasmInstanceObject>());
  }
  return AsVariant(static_cast<JSScript*>(nullptr));
}

void DebuggerScript_trace(JSTracer* trc, JSObject* obj) {
  // This comes from a private pointer, so no barrier needed.
  gc::Cell* cell = GetScriptReferentCell(obj);
  if (cell) {
    if (cell->is<JSScript>()) {
      JSScript* script = cell->as<JSScript>();
      TraceManuallyBarrieredCrossCompartmentEdge(
          trc, obj, &script, "Debugger.Script script referent");
      obj->as<NativeObject>().setPrivateUnbarriered(script);
    } else if (cell->is<LazyScript>