js/src/vm/Debugger.cpp
author Jeff Walden <jwalden@mit.edu>
Thu, 08 Nov 2018 18:42:48 -0800
changeset 502944 56eaf6c976d377cba4e44a302bdeda9e7420bd94
parent 502936 c489ba287b497b77e6bb7d4b7550e0792670726c
child 502951 24670a08843eba386bc29ad58c8289c39ac969e7
permissions -rw-r--r--
Bug 1485800 - Rename SourceBufferHolder to SourceText, and add a <typename Unit> template parameter to it so it can hold putative UTF-8 or UTF-16 source text. r=tcampbell, r=fitzgen

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * 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/BytecodeCompiler.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/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::dbg::AutoEntryMonitor;
using JS::dbg::Builder;
using js::frontend::IsIdentifier;
using JS::SourceOwnership;
using JS::SourceText;
using mozilla::DebugOnly;
using mozilla::MakeScopeExit;
using mozilla::Maybe;
using mozilla::Some;
using mozilla::Nothing;
using mozilla::AsVariant;
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),
    allowWasmBinarySource(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);

    Maybe<AutoRealm> ar;
    if (hook == OnNewPromise) {
        ar.emplace(cx, promise);
    }

    cx->check(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
MarkBaselineScriptActiveIfObservable(JSScript* script, const Debugger::ExecutionObservableSet& obs)
{
    if (obs.shouldRecompileOrInvalidate(script)) {
        script->baselineScript()->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 (obs.shouldRecompileOrInvalidate(script) &&
                    !gc::IsAboutToBeFinalizedUnbarriered(&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:
                MarkBaselineScriptActiveIfObservable(frame.script(), obs);
                break;
              case FrameType::IonJS:
                MarkBaselineScriptActiveIfObservable(frame.script(), obs);
                for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more(); ++inlineIter) {
                    MarkBaselineScriptActiveIfObservable(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);
        FinishDiscardBaselineScript(fop, scripts[i]);
    }

    // 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::observesBinarySource() const
{
    if (enabled && allowWasmBinarySource) {
        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();
    }
}

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

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

        realm->updateDebuggerObservesBinarySource();
    }
}


/*** 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++) {
            if ((*p)->trackingAllocationSites && (*p)->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)
{
    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;

                // 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 */ void
Debugger::findZoneEdges(Zone* zone, js::gc::ZoneComponentFinder& finder)
{
    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()) {
                    finder.addEdgeTo(debuggeeZone);
                }
            }
        } 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))
            {
                finder.addEdgeTo(debuggerZone);
            }
        }
    }
}

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

    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::getAllowWasmBinarySource(JSContext* cx, unsigned argc, Value* vp)
{
    THIS_DEBUGGER(cx, argc, vp, "get allowWasmBinarySource", args, dbg);
    args.rval().setBoolean(dbg->allowWasmBinarySource);
    return true;
}

/* static */ bool
Debugger::setAllowWasmBinarySource(JSContext* cx, unsigned argc, Value* vp)
{
    THIS_DEBUGGER(cx, argc, vp, "set allowWasmBinarySource", args, dbg);
    if (!args.requireAtLeast(cx, "Debugger.set allowWasmBinarySource", 1)) {
        return false;
    }
    dbg->allowWasmBinarySource = ToBoolean(args[0]);

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

    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.
    obj = CheckedUnwrap(obj);
    if (!obj) {
        ReportAccessDenied(cx);
        return nullptr;
    }

    // If that produced a WindowProxy, get the Window (global).
    obj = ToWindowIfWindowProxy(obj);

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

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.global() == 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()->updateDebuggerObservesBinarySource();
        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 (hasLine) {
            if (line < script->lineno() || script->lineno() + GetScriptLineExtent(script) < line) {
                return;
            }
        }
        if (!commonFilter(script, nogc)) {
            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);

    if (gc::GCRuntime::temporaryAbortIfWasmGc(cx)) {
        JS_ReportErrorASCII(cx, "API temporarily unavailable under wasm gc");
        return false;
    }

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

    if (gc::GCRuntime::temporaryAbortIfWasmGc(cx)) {
        JS_ReportErrorASCII(cx, "API temporarily unavailable under wasm gc");
        return false;
    }

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

    frontend::Parser<frontend::FullParseHandler, char16_t> parser(cx, cx->tempLifoAlloc(),
                                                                  options, chars.twoByteChars(),
                                                                  length,
                                                                  /* foldConstants = */ true,
                                                                  usedNames, nullptr, nullptr,
                                                                  sourceObject,
                                                                  frontend::ParseGoal::Script);
    JS::WarningReporter older = JS::SetWarningReporter(cx, nullptr);
    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()) {
            JS::SetWarningReporter(cx, older);
            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();
    }
    JS::SetWarningReporter(cx, older);
    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