author Jim Blandy <jimb@mozilla.com>
Wed, 25 May 2016 14:33:40 -0700
changeset 299129 3a42cbfb7f70656842e85facfe18b72ccd33baf8
parent 298793 f18e46bce0a40e6b73435efb633c50a38fdb18e8
child 299605 11c4daf94661a3e5a8d775827468167857e1b5b1
permissions -rw-r--r--
Bug 1275730: Update docs for Debugger's js::Env typedef. DONTBUILD 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/. */

#ifndef vm_Debugger_h
#define vm_Debugger_h

#include "mozilla/GuardObjects.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Range.h"
#include "mozilla/Vector.h"

#include "jsclist.h"
#include "jscntxt.h"
#include "jscompartment.h"
#include "jsweakmap.h"
#include "jswrapper.h"

#include "asmjs/WasmModule.h"
#include "ds/TraceableFifo.h"
#include "gc/Barrier.h"
#include "js/Debug.h"
#include "js/GCVariant.h"
#include "js/HashTable.h"
#include "vm/GlobalObject.h"
#include "vm/SavedStacks.h"

enum JSTrapStatus {

namespace js {

class Breakpoint;
class DebuggerMemory;

typedef HashSet<ReadBarrieredGlobalObject,
                RuntimeAllocPolicy> WeakGlobalObjectSet;

 * A weakmap from GC thing keys to JSObject values that supports the keys being
 * in different compartments to the values. All values must be in the same
 * compartment.
 * The purpose of this is to allow the garbage collector to easily find edges
 * from debuggee object compartments to debugger compartments when calculating
 * the compartment groups.  Note that these edges are the inverse of the edges
 * stored in the cross compartment map.
 * The current implementation results in all debuggee object compartments being
 * swept in the same group as the debugger.  This is a conservative approach,
 * and compartments may be unnecessarily grouped, however it results in a
 * simpler and faster implementation.
 * If InvisibleKeysOk is true, then the map can have keys in invisible-to-
 * debugger compartments. If it is false, we assert that such entries are never
 * created.
 * Also note that keys in these weakmaps can be in any compartment, debuggee or
 * not, because they cannot be deleted when a compartment is no longer a
 * debuggee: the values need to maintain object identity across add/remove/add
 * transitions.
template <class UnbarrieredKey, bool InvisibleKeysOk=false>
class DebuggerWeakMap : private WeakMap<HeapPtr<UnbarrieredKey>, HeapPtr<JSObject*>,
    typedef HeapPtr<UnbarrieredKey> Key;
    typedef HeapPtr<JSObject*> Value;

    typedef HashMap<JS::Zone*,
                    RuntimeAllocPolicy> CountMap;

    CountMap zoneCounts;
    JSCompartment* compartment;

    typedef WeakMap<Key, Value, MovableCellHasher<Key>> Base;

    explicit DebuggerWeakMap(JSContext* cx)
        : Base(cx),
    { }

    /* Expose those parts of HashMap public interface that are used by Debugger methods. */

    typedef typename Base::Entry Entry;
    typedef typename Base::Ptr Ptr;
    typedef typename Base::AddPtr AddPtr;
    typedef typename Base::Range Range;
    typedef typename Base::Enum Enum;
    typedef typename Base::Lookup Lookup;

    /* Expose WeakMap public interface */

    using Base::lookupForAdd;
    using Base::all;
    using Base::trace;

    bool init(uint32_t len = 16) {
        return Base::init(len) && zoneCounts.init();

    template<typename KeyInput, typename ValueInput>
    bool relookupOrAdd(AddPtr& p, const KeyInput& k, const ValueInput& v) {
        MOZ_ASSERT(v->compartment() == this->compartment);
        if (!incZoneCount(k->zone()))
            return false;
        bool ok = Base::relookupOrAdd(p, k, v);
        if (!ok)
        return ok;

    void remove(const Lookup& l) {

    template <void (traceValueEdges)(JSTracer*, JSObject*)>
    void markCrossCompartmentEdges(JSTracer* tracer) {
        for (Enum e(*static_cast<Base*>(this)); !e.empty(); e.popFront()) {
            traceValueEdges(tracer, e.front().value());
            Key key = e.front().key();
            TraceEdge(tracer, &key, "Debugger WeakMap key");
            if (key != e.front().key())

    bool hasKeyInZone(JS::Zone* zone) {
        CountMap::Ptr p = zoneCounts.lookup(zone);
        MOZ_ASSERT_IF(p.found(), p->value() > 0);
        return p.found();

    /* Override sweep method to also update our edge cache. */
    void sweep() {
        for (Enum e(*static_cast<Base*>(this)); !e.empty(); e.popFront()) {
            if (gc::IsAboutToBeFinalized(&e.front().mutableKey())) {

    bool incZoneCount(JS::Zone* zone) {
        CountMap::Ptr p = zoneCounts.lookupWithDefault(zone, 0);
        if (!p)
            return false;
        return true;

    void decZoneCount(JS::Zone* zone) {
        CountMap::Ptr p = zoneCounts.lookup(zone);
        MOZ_ASSERT(p->value() > 0);
        if (p->value() == 0)

class LeaveDebuggeeNoExecute;

// Suppresses all debuggee NX checks, i.e., allow all execution. Used to allow
// certain whitelisted operations to execute code.
// WARNING Do not use this unless you know what you are doing!
class AutoSuppressDebuggeeNoExecuteChecks
    EnterDebuggeeNoExecute** stack_;
    EnterDebuggeeNoExecute* prev_;

    explicit AutoSuppressDebuggeeNoExecuteChecks(JSContext* cx) {
        stack_ = &cx->runtime()->noExecuteDebuggerTop;
        prev_ = *stack_;
        *stack_ = nullptr;

    ~AutoSuppressDebuggeeNoExecuteChecks() {
        *stack_ = prev_;

 * Env is the type of what ES5 calls "lexical environments" (runtime activations
 * of lexical scopes). This is currently just JSObject, and is implemented by
 * CallObject, ClonedBlockObject, DynamicWithObject, and DeclEnvObject, among
 * others--but environments and objects are really two different concepts.
typedef JSObject Env;

// Either a real JSScript or synthesized.
// If synthesized, the referent is one of the following:
//   1. A WasmModuleObject, denoting a synthesized toplevel wasm module
//      script.
//   2. A wasm JSFunction, denoting a synthesized wasm function script.
//      NYI!
typedef mozilla::Variant<JSScript*, WasmModuleObject*> DebuggerScriptReferent;

// Either a ScriptSourceObject, for ordinary JS, or a WasmModuleObject,
// denoting the synthesized source of a wasm module.
typedef mozilla::Variant<ScriptSourceObject*, WasmModuleObject*> DebuggerSourceReferent;

class Debugger : private mozilla::LinkedListElement<Debugger>
    friend class Breakpoint;
    friend class DebuggerMemory;
    friend class SavedStacks;
    friend class mozilla::LinkedListElement<Debugger>;
    friend class mozilla::LinkedList<Debugger>;
    friend bool (::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj);
    friend bool (::JS::dbg::IsDebugger)(JSObject&);
    friend bool (::JS::dbg::GetDebuggeeGlobals)(JSContext*, JSObject&, AutoObjectVector&);
    friend void JS::dbg::onNewPromise(JSContext* cx, HandleObject promise);
    friend void JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise);
    friend bool JS::dbg::FireOnGarbageCollectionHook(JSContext* cx,
                                                     JS::dbg::GarbageCollectionEvent::Ptr&& data);

    enum Hook {
    enum {

    class ExecutionObservableSet
        typedef HashSet<Zone*>::Range ZoneRange;

        virtual Zone* singleZone() const { return nullptr; }
        virtual JSScript* singleScriptForZoneInvalidation() const { return nullptr; }
        virtual const HashSet<Zone*>* zones() const { return nullptr; }

        virtual bool shouldRecompileOrInvalidate(JSScript* script) const = 0;
        virtual bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const = 0;

    // This enum is converted to and compare with bool values; NotObserving
    // must be 0 and Observing must be 1.
    enum IsObserving {
        NotObserving = 0,
        Observing = 1

    // Return true if the given compartment is a debuggee of this debugger,
    // false otherwise.
    bool isDebuggeeUnbarriered(const JSCompartment* compartment) const;

    // Return true if this Debugger observed a debuggee that participated in the
    // GC identified by the given GC number. Return false otherwise.
    // May return false negatives if we have hit OOM.
    bool observedGC(uint64_t majorGCNumber) const {
        return observedGCs.has(majorGCNumber);

    // Notify this Debugger that one or more of its debuggees is participating
    // in the GC identified by the given GC number.
    bool debuggeeIsBeingCollected(uint64_t majorGCNumber) {
        return observedGCs.put(majorGCNumber);

    bool isEnabled() const {
        return enabled;

    static SavedFrame* getObjectAllocationSite(JSObject& obj);

    struct AllocationsLogEntry
        AllocationsLogEntry(HandleObject frame, double when, const char* className,
                            HandleAtom ctorName, size_t size, bool inNursery)
            : frame(frame),
            MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>());

        HeapPtr<JSObject*> frame;
        double when;
        const char* className;
        HeapPtr<JSAtom*> ctorName;
        size_t size;
        bool inNursery;

        void trace(JSTracer* trc) {
            TraceNullableEdge(trc, &frame, "Debugger::AllocationsLogEntry::frame");
            TraceNullableEdge(trc, &ctorName, "Debugger::AllocationsLogEntry::ctorName");

    // Barrier methods so we can have ReadBarriered<Debugger*>.
    static void readBarrier(Debugger* dbg) {
    static void writeBarrierPost(Debugger** vp, Debugger* prev, Debugger* next) {}

    GCPtrNativeObject object; /* The Debugger object. Strong reference. */
    WeakGlobalObjectSet debuggees; /* Debuggee globals. Cross-compartment weak references. */
    JS::ZoneSet debuggeeZones; /* Set of zones that we have debuggees in. */
    js::GCPtrObject uncaughtExceptionHook; /* Strong reference. */
    bool enabled;
    bool allowUnobservedAsmJS;

    // Whether to enable code coverage on the Debuggee.
    bool collectCoverageInfo;

    JSCList breakpoints;                /* Circular list of all js::Breakpoints in this debugger */

    // The set of GC numbers for which one or more of this Debugger's observed
    // debuggees participated in.
    using GCNumberSet = HashSet<uint64_t, DefaultHasher<uint64_t>, RuntimeAllocPolicy>;
    GCNumberSet observedGCs;

    using AllocationsLog = js::TraceableFifo<AllocationsLogEntry>;

    AllocationsLog allocationsLog;
    bool trackingAllocationSites;
    double allocationSamplingProbability;
    size_t maxAllocationsLogLength;
    bool allocationsLogOverflowed;

    static const size_t DEFAULT_MAX_LOG_LENGTH = 5000;

    bool appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
                              double when);

     * Recompute the set of debuggee zones based on the set of debuggee globals.
    void recomputeDebuggeeZoneSet();

     * Return true if there is an existing object metadata callback for the
     * given global's compartment that will prevent our instrumentation of
     * allocations.
    static bool cannotTrackAllocations(const GlobalObject& global);

     * Return true if the given global is being observed by at least one
     * Debugger that is tracking allocations.
    static bool isObservedByDebuggerTrackingAllocations(const GlobalObject& global);

     * Add allocations tracking for objects allocated within the given
     * debuggee's compartment. The given debuggee global must be observed by at
     * least one Debugger that is enabled and tracking allocations.
    static bool addAllocationsTracking(JSContext* cx, Handle<GlobalObject*> debuggee);

     * Remove allocations tracking for objects allocated within the given
     * global's compartment. This is a no-op if there are still Debuggers
     * observing this global and who are tracking allocations.
    static void removeAllocationsTracking(GlobalObject& global);

     * Add or remove allocations tracking for all debuggees.
    bool addAllocationsTrackingForAllDebuggees(JSContext* cx);
    void removeAllocationsTrackingForAllDebuggees();

     * If this Debugger is enabled, and has a onNewGlobalObject handler, then
     * this link is inserted into the circular list headed by
     * JSRuntime::onNewGlobalObjectWatchers. Otherwise, this is set to a
     * singleton cycle.
    JSCList onNewGlobalObjectWatchersLink;

     * Map from stack frames that are currently on the stack to Debugger.Frame
     * instances.
     * The keys are always live stack frames. We drop them from this map as
     * soon as they leave the stack (see slowPathOnLeaveFrame) and in
     * removeDebuggee.
     * We don't trace the keys of this map (the frames are on the stack and
     * thus necessarily live), but we do trace the values. It's like a WeakMap
     * that way, but since stack frames are not gc-things, the implementation
     * has to be different.
    typedef HashMap<AbstractFramePtr,
                    RuntimeAllocPolicy> FrameMap;
    FrameMap frames;

    /* An ephemeral map from JSScript* to Debugger.Script instances. */
    typedef DebuggerWeakMap<JSScript*> ScriptWeakMap;
    ScriptWeakMap scripts;

    /* The map from debuggee source script objects to their Debugger.Source instances. */
    typedef DebuggerWeakMap<JSObject*, true> SourceWeakMap;
    SourceWeakMap sources;

    /* The map from debuggee objects to their Debugger.Object instances. */
    typedef DebuggerWeakMap<JSObject*> ObjectWeakMap;
    ObjectWeakMap objects;

    /* The map from debuggee Envs to Debugger.Environment instances. */
    ObjectWeakMap environments;

    /* The map from WasmModuleObjects to synthesized Debugger.Script instances. */
    typedef DebuggerWeakMap<WasmModuleObject*> WasmModuleWeakMap;
    WasmModuleWeakMap wasmModuleScripts;

    /* The map from WasmModuleObjects to synthesized Debugger.Source instances. */
    WasmModuleWeakMap wasmModuleSources;

     * Keep track of tracelogger last drained identifiers to know if there are
     * lost events.
    uint32_t traceLoggerLastDrainedSize;
    uint32_t traceLoggerLastDrainedIteration;
    uint32_t traceLoggerScriptedCallsLastDrainedSize;
    uint32_t traceLoggerScriptedCallsLastDrainedIteration;

    class ScriptQuery;
    class ObjectQuery;

    bool addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> obj);
    void removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global,
                              WeakGlobalObjectSet::Enum* debugEnum);

     * Cope with an error or exception in a debugger hook.
     * If callHook is true, then call the uncaughtExceptionHook, if any. If, in
     * addition, vp is given, then parse the value returned by
     * uncaughtExceptionHook as a resumption value.
     * If there is no uncaughtExceptionHook, or if it fails, report and clear
     * the pending exception on ac.context and return JSTRAP_ERROR.
     * This always calls ac.leave(); ac is a parameter because this method must
     * do some things in the debugger compartment and some things in the
     * debuggee compartment.
    JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment>& ac, bool callHook);
    JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment>& ac, MutableHandleValue vp, bool callHook,
                                         const mozilla::Maybe<HandleValue>& thisVForCheck = mozilla::Nothing(),
                                         AbstractFramePtr frame = NullFramePtr());

    JSTrapStatus handleUncaughtExceptionHelper(mozilla::Maybe<AutoCompartment>& ac,
                                               MutableHandleValue* vp, bool callHook,
                                               const mozilla::Maybe<HandleValue>& thisVForCheck, AbstractFramePtr frame);

     * Handle the result of a hook that is expected to return a resumption
     * value <https://wiki.mozilla.org/Debugger#Resumption_Values>. This is called
     * when we return from a debugging hook to debuggee code. The interpreter wants
     * a (JSTrapStatus, Value) pair telling it how to proceed.
     * Precondition: ac is entered. We are in the debugger compartment.
     * Postcondition: This called ac.leave(). See handleUncaughtException.
     * If ok is false, the hook failed. If an exception is pending in
     * ac.context(), return handleUncaughtException(ac, vp, callhook).
     * Otherwise just return JSTRAP_ERROR.
     * If ok is true, there must be no exception pending in ac.context(). rv may be:
     *     undefined - Return JSTRAP_CONTINUE to continue execution normally.
     *     {return: value} or {throw: value} - Call unwrapDebuggeeValue to
     *         unwrap value. Store the result in *vp and return JSTRAP_RETURN
     *         or JSTRAP_THROW. The interpreter will force the current frame to
     *         return or throw an exception.
     *     null - Return JSTRAP_ERROR to terminate the debuggee with an
     *         uncatchable error.
     *     anything else - Make a new TypeError the pending exception and
     *         return handleUncaughtException(ac, vp, callHook).
    JSTrapStatus parseResumptionValue(mozilla::Maybe<AutoCompartment>& ac, bool OK, const Value& rv,
                                      AbstractFramePtr frame, jsbytecode* pc, MutableHandleValue vp,
                                      bool callHook = true);

     * When we run the onEnterFrame hook, the |this| slot hasn't been fully
     * initialized, because the initialzation happens in the function's
     * prologue. To combat this, we pass the this for the primitive return
     * check directly. When bug 1249193 is fixed, this overload should be
     * removed.
    JSTrapStatus parseResumptionValue(mozilla::Maybe<AutoCompartment>& ac, bool OK, const Value& rv,
                                      const Value& thisVForCheck, AbstractFramePtr frame,
                                      MutableHandleValue vp, bool callHook = true);

    JSTrapStatus parseResumptionValueHelper(mozilla::Maybe<AutoCompartment>& ac, bool ok, const Value& rv,
                                            const mozilla::Maybe<HandleValue>& thisVForCheck, AbstractFramePtr frame,
                                            MutableHandleValue vp, bool callHook);

    GlobalObject* unwrapDebuggeeArgument(JSContext* cx, const Value& v);

    static void traceObject(JSTracer* trc, JSObject* obj);
    void trace(JSTracer* trc);
    static void finalize(FreeOp* fop, JSObject* obj);
    void markCrossCompartmentEdges(JSTracer* tracer);

    static const ClassOps classOps_;
    static const Class class_;

    static bool getHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which);
    static bool setHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which);

    static Debugger* fromThisValue(JSContext* cx, const CallArgs& ca, const char* fnname);
    static bool getEnabled(JSContext* cx, unsigned argc, Value* vp);
    static bool setEnabled(JSContext* cx, unsigned argc, Value* vp);
    static bool getOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp);
    static bool setOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp);
    static bool getOnExceptionUnwind(JSContext* cx, unsigned argc, Value* vp);
    static bool setOnExceptionUnwind(JSContext* cx, unsigned argc, Value* vp);
    static bool getOnNewScript(JSContext* cx, unsigned argc, Value* vp);
    static bool setOnNewScript(JSContext* cx, unsigned argc, Value* vp);
    static bool getOnEnterFrame(JSContext* cx, unsigned argc, Value* vp);
    static bool setOnEnterFrame(JSContext* cx, unsigned argc, Value* vp);
    static bool getOnNewGlobalObject(JSContext* cx, unsigned argc, Value* vp);
    static bool setOnNewGlobalObject(JSContext* cx, unsigned argc, Value* vp);
    static bool getOnNewPromise(JSContext* cx, unsigned argc, Value* vp);
    static bool setOnNewPromise(JSContext* cx, unsigned argc, Value* vp);
    static bool getOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp);
    static bool setOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp);
    static bool getUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp);
    static bool setUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp);
    static bool getAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
    static bool setAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
    static bool getCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp);
    static bool setCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp);
    static bool getMemory(JSContext* cx, unsigned argc, Value* vp);
    static bool addDebuggee(JSContext* cx, unsigned argc, Value* vp);
    static bool addAllGlobalsAsDebuggees(JSContext* cx, unsigned argc, Value* vp);
    static bool removeDebuggee(JSContext* cx, unsigned argc, Value* vp);
    static bool removeAllDebuggees(JSContext* cx, unsigned argc, Value* vp);
    static bool hasDebuggee(JSContext* cx, unsigned argc, Value* vp);
    static bool getDebuggees(JSContext* cx, unsigned argc, Value* vp);
    static bool getNewestFrame(JSContext* cx, unsigned argc, Value* vp);
    static bool clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp);
    static bool findScripts(JSContext* cx, unsigned argc, Value* vp);
    static bool findObjects(JSContext* cx, unsigned argc, Value* vp);
    static bool findAllGlobals(JSContext* cx, unsigned argc, Value* vp);
    static bool makeGlobalObjectReference(JSContext* cx, unsigned argc, Value* vp);
    static bool setupTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp);
    static bool drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp);
    static bool startTraceLogger(JSContext* cx, unsigned argc, Value* vp);
    static bool endTraceLogger(JSContext* cx, unsigned argc, Value* vp);
    static bool isCompilableUnit(JSContext* cx, unsigned argc, Value* vp);
    static bool setupTraceLogger(JSContext* cx, unsigned argc, Value* vp);
    static bool drainTraceLogger(JSContext* cx, unsigned argc, Value* vp);
    static bool adoptDebuggeeValue(JSContext* cx, unsigned argc, Value* vp);
    static bool construct(JSContext* cx, unsigned argc, Value* vp);
    static const JSPropertySpec properties[];
    static const JSFunctionSpec methods[];
    static const JSFunctionSpec static_methods[];

    static void removeFromFrameMapsAndClearBreakpointsIn(JSContext* cx, AbstractFramePtr frame);
    static bool updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObservableSet& obs,
                                                     IsObserving observing);
    static bool updateExecutionObservabilityOfScripts(JSContext* cx, const ExecutionObservableSet& obs,
                                                      IsObserving observing);
    static bool updateExecutionObservability(JSContext* cx, ExecutionObservableSet& obs,
                                             IsObserving observing);

    template <typename FrameFn /* void (NativeObject*) */>
    static void forEachDebuggerFrame(AbstractFramePtr frame, FrameFn fn);

     * Return a vector containing all Debugger.Frame instances referring to
     * |frame|. |global| is |frame|'s global object; if nullptr or omitted, we
     * compute it ourselves from |frame|.
    using DebuggerFrameVector = GCVector<NativeObject*>;
    static bool getDebuggerFrames(AbstractFramePtr frame,
                                  MutableHandle<DebuggerFrameVector> frames);

    static bool ensureExecutionObservabilityOfOsrFrame(JSContext* cx, InterpreterFrame* frame);

    // Public for DebuggerScript_setBreakpoint.
    static bool ensureExecutionObservabilityOfScript(JSContext* cx, JSScript* script);

    // Whether the Debugger instance needs to observe all non-AOT JS
    // execution of its debugees.
    IsObserving observesAllExecution() const;

    // Whether the Debugger instance needs to observe AOT-compiled asm.js
    // execution of its debuggees.
    IsObserving observesAsmJS() const;

    // Whether the Debugger instance needs to observe coverage of any JavaScript
    // execution.
    IsObserving observesCoverage() const;

    static bool ensureExecutionObservabilityOfFrame(JSContext* cx, AbstractFramePtr frame);
    static bool ensureExecutionObservabilityOfCompartment(JSContext* cx, JSCompartment* comp);

    static bool hookObservesAllExecution(Hook which);

    bool updateObservesAllExecutionOnDebuggees(JSContext* cx, IsObserving observing);
    bool updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing);
    void updateObservesAsmJSOnDebuggees(IsObserving observing);

    JSObject* getHook(Hook hook) const;
    bool hasAnyLiveHooks() const;

    static bool slowPathCheckNoExecute(JSContext* cx, HandleScript script);
    static JSTrapStatus slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame);
    static bool slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok);
    static JSTrapStatus slowPathOnDebuggerStatement(JSContext* cx, AbstractFramePtr frame);
    static JSTrapStatus slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame);
    static void slowPathOnNewScript(JSContext* cx, HandleScript script);
    static void slowPathOnNewWasmModule(JSContext* cx, Handle<WasmModuleObject*> wasmModule);
    static void slowPathOnNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
    static bool slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
                                            double when, GlobalObject::DebuggerVector& dbgs);
    static void slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise);

    template <typename HookIsEnabledFun /* bool (Debugger*) */,
              typename FireHookFun /* JSTrapStatus (Debugger*) */>
    static JSTrapStatus dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled,
                                     FireHookFun fireHook);

    JSTrapStatus fireDebuggerStatement(JSContext* cx, MutableHandleValue vp);
    JSTrapStatus fireExceptionUnwind(JSContext* cx, MutableHandleValue vp);
    JSTrapStatus fireEnterFrame(JSContext* cx, AbstractFramePtr frame, MutableHandleValue vp);
    JSTrapStatus fireNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global, MutableHandleValue vp);
    JSTrapStatus firePromiseHook(JSContext* cx, Hook hook, HandleObject promise, MutableHandleValue vp);

    NativeObject* newVariantWrapper(JSContext* cx, Handle<DebuggerScriptReferent> referent) {
        return newDebuggerScript(cx, referent);
    NativeObject* newVariantWrapper(JSContext* cx, Handle<DebuggerSourceReferent> referent) {
        return newDebuggerSource(cx, referent);

     * Helper function to help wrap Debugger objects whose referents may be
     * variants. Currently Debugger.Script and Debugger.Source referents may
     * be variants.
     * Prefer using wrapScript, wrapWasmScript, wrapSource, and wrapWasmSource
     * whenever possible.
    template <typename ReferentVariant, typename Referent, typename Map>
    JSObject* wrapVariantReferent(JSContext* cx, Map& map, Handle<CrossCompartmentKey> key,
                                  Handle<ReferentVariant> referent);
    JSObject* wrapVariantReferent(JSContext* cx, Handle<DebuggerScriptReferent> referent);
    JSObject* wrapVariantReferent(JSContext* cx, Handle<DebuggerSourceReferent> referent);

     * Allocate and initialize a Debugger.Script instance whose referent is
     * |referent|.
    NativeObject* newDebuggerScript(JSContext* cx, Handle<DebuggerScriptReferent> referent);

     * Allocate and initialize a Debugger.Source instance whose referent is
     * |referent|.
    NativeObject* newDebuggerSource(JSContext* cx, Handle<DebuggerSourceReferent> referent);

     * Receive a "new script" event from the engine. A new script was compiled
     * or deserialized.
    void fireNewScript(JSContext* cx, Handle<DebuggerScriptReferent> scriptReferent);

     * Receive a "garbage collection" event from the engine. A GC cycle with the
     * given data was recently completed.
    void fireOnGarbageCollectionHook(JSContext* cx,
                                     const JS::dbg::GarbageCollectionEvent::Ptr& gcData);

     * Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy
     * its data if we need to make a new Debugger.Frame.
    bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
                                const ScriptFrameIter* maybeIter, MutableHandleValue vp);

    inline Breakpoint* firstBreakpoint() const;

    static inline Debugger* fromOnNewGlobalObjectWatchersLink(JSCList* link);

    static bool replaceFrameGuts(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to,
                                 ScriptFrameIter& iter);

    Debugger(JSContext* cx, NativeObject* dbg);

    bool init(JSContext* cx);
    inline const js::GCPtrNativeObject& toJSObject() const;
    inline js::GCPtrNativeObject& toJSObjectRef();
    static inline Debugger* fromJSObject(const JSObject* obj);
    static Debugger* fromChildJSObject(JSObject* obj);

    bool hasMemory() const;
    DebuggerMemory& memory() const;

    WeakGlobalObjectSet::Range allDebuggees() const { return debuggees.all(); }

    /*********************************** Methods for interaction with the GC. */

     * A Debugger object is live if:
     *   * the Debugger JSObject is live (Debugger::trace handles this case); OR
     *   * it is in the middle of dispatching an event (the event dispatching
     *     code roots it in this case); OR
     *   * it is enabled, and it is debugging at least one live compartment,
     *     and at least one of the following is true:
     *       - it has a debugger hook installed
     *       - it has a breakpoint set on a live script
     *       - it has a watchpoint set on a live object.
     * Debugger::markAllIteratively handles the last case. If it finds any
     * Debugger objects that are definitely live but not yet marked, it marks
     * them and returns true. If not, it returns false.
    static void markIncomingCrossCompartmentEdges(JSTracer* tracer);
    static bool markAllIteratively(GCMarker* trc);
    static void markAll(JSTracer* trc);
    static void sweepAll(FreeOp* fop);
    static void detachAllDebuggersFromGlobal(FreeOp* fop, GlobalObject* global);
    static void findZoneEdges(JS::Zone* v, gc::ZoneComponentFinder& finder);

    // Checks it the current compartment is allowed to execute code.
    static inline bool checkNoExecute(JSContext* cx, HandleScript script);

     * JSTrapStatus Overview
     * ---------------------
     * The |onEnterFrame|, |onDebuggerStatement|, and |onExceptionUnwind|
     * methods below return a JSTrapStatus code that indicates how execution
     * should proceed:
     * - JSTRAP_CONTINUE: Continue execution normally.
     * - JSTRAP_THROW: Throw an exception. The method has set |cx|'s
     *   pending exception to the value to be thrown.
     * - JSTRAP_ERROR: Terminate execution (as is done when a script is terminated
     *   for running too long). The method has cleared |cx|'s pending
     *   exception.
     * - JSTRAP_RETURN: Return from the new frame immediately. The method has
     *   set the youngest JS frame's return value appropriately.

     * Announce to the debugger that the context has entered a new JavaScript
     * frame, |frame|. Call whatever hooks have been registered to observe new
     * frames.
    static inline JSTrapStatus onEnterFrame(JSContext* cx, AbstractFramePtr frame);

     * Announce to the debugger a |debugger;| statement on has been
     * encountered on the youngest JS frame on |cx|. Call whatever hooks have
     * been registered to observe this.
     * Note that this method is called for all |debugger;| statements,
     * regardless of the frame's debuggee-ness.
    static inline JSTrapStatus onDebuggerStatement(JSContext* cx, AbstractFramePtr frame);

     * Announce to the debugger that an exception has been thrown and propagated
     * to |frame|. Call whatever hooks have been registered to observe this.
    static inline JSTrapStatus onExceptionUnwind(JSContext* cx, AbstractFramePtr frame);

     * Announce to the debugger that the thread has exited a JavaScript frame, |frame|.
     * If |ok| is true, the frame is returning normally; if |ok| is false, the frame
     * is throwing an exception or terminating.
     * Change cx's current exception and |frame|'s return value to reflect the changes
     * in behavior the hooks request, if any. Return the new error/success value.
     * This function may be called twice for the same outgoing frame; only the
     * first call has any effect. (Permitting double calls simplifies some
     * cases where an onPop handler's resumption value changes a return to a
     * throw, or vice versa: we can redirect to a complete copy of the
     * alternative path, containing its own call to onLeaveFrame.)
    static inline bool onLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok);

    static inline void onNewScript(JSContext* cx, HandleScript script);
    static inline void onNewWasmModule(JSContext* cx, Handle<WasmModuleObject*> wasmModule);
    static inline void onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
    static inline bool onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame,
                                           double when);
    static JSTrapStatus onTrap(JSContext* cx, MutableHandleValue vp);
    static JSTrapStatus onSingleStep(JSContext* cx, MutableHandleValue vp);
    static bool handleBaselineOsr(JSContext* cx, InterpreterFrame* from, jit::BaselineFrame* to);
    static bool handleIonBailout(JSContext* cx, jit::RematerializedFrame* from, jit::BaselineFrame* to);
    static void handleUnrecoverableIonBailoutError(JSContext* cx, jit::RematerializedFrame* frame);
    static void propagateForcedReturn(JSContext* cx, AbstractFramePtr frame, HandleValue rval);
    static bool hasLiveHook(GlobalObject* global, Hook which);
    static bool inFrameMaps(AbstractFramePtr frame);

    /************************************* Functions for use by Debugger.cpp. */

    inline bool observesEnterFrame() const;
    inline bool observesNewScript() const;
    inline bool observesNewGlobalObject() const;
    inline bool observesGlobal(GlobalObject* global) const;
    bool observesFrame(AbstractFramePtr frame) const;
    bool observesFrame(const ScriptFrameIter& iter) const;
    bool observesScript(JSScript* script) const;

     * If env is nullptr, call vp->setNull() and return true. Otherwise, find
     * or create a Debugger.Environment object for the given Env. On success,
     * store the Environment object in *vp and return true.
    bool wrapEnvironment(JSContext* cx, Handle<Env*> env, MutableHandleValue vp);

     * Like cx->compartment()->wrap(cx, vp), but for the debugger compartment.
     * Preconditions: *vp is a value from a debuggee compartment; cx is in the
     * debugger's compartment.
     * If *vp is an object, this produces a (new or existing) Debugger.Object
     * wrapper for it. Otherwise this is the same as JSCompartment::wrap.
     * If *vp is a magic JS_OPTIMIZED_OUT value, this produces a plain object
     * of the form { optimizedOut: true }.
     * If *vp is a magic JS_OPTIMIZED_ARGUMENTS value signifying missing
     * arguments, this produces a plain object of the form { missingArguments:
     * true }.
     * If *vp is a magic JS_UNINITIALIZED_LEXICAL value signifying an
     * unaccessible uninitialized binding, this produces a plain object of the
     * form { uninitialized: true }.
    bool wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp);

     * Unwrap a Debug.Object, without rewrapping it for any particular debuggee
     * compartment.
     * Preconditions: cx is in the debugger compartment. *vp is a value in that
     * compartment. (*vp should be a "debuggee value", meaning it is the
     * debugger's reflection of a value in the debuggee.)
     * If *vp is a Debugger.Object, store the referent in *vp. Otherwise, if *vp
     * is an object, throw a TypeError, because it is not a debuggee
     * value. Otherwise *vp is a primitive, so leave it alone.
     * When passing values from the debuggee to the debugger:
     *     enter debugger compartment;
     *     call wrapDebuggeeValue;  // compartment- and debugger-wrapping
     * When passing values from the debugger to the debuggee:
     *     call unwrapDebuggeeValue;  // debugger-unwrapping
     *     enter debuggee compartment;
     *     call cx->compartment()->wrap;  // compartment-rewrapping
     * (Extreme nerd sidebar: Unwrapping happens in two steps because there are
     * two different kinds of symmetry at work: regardless of which direction
     * we're going, we want any exceptions to be created and thrown in the
     * debugger compartment--mirror symmetry. But compartment wrapping always
     * happens in the target compartment--rotational symmetry.)
    bool unwrapDebuggeeValue(JSContext* cx, MutableHandleValue vp);
    bool unwrapDebuggeeObject(JSContext* cx, MutableHandleObject obj);
    bool unwrapPropertyDescriptor(JSContext* cx, HandleObject obj,
                                  MutableHandle<PropertyDescriptor> desc);

     * Store the Debugger.Frame object for frame in *vp.
     * Use this if you have already access to a frame pointer without having
     * to incur the cost of walking the stack.
    bool getScriptFrame(JSContext* cx, AbstractFramePtr frame, MutableHandleValue vp) {
        return getScriptFrameWithIter(cx, frame, nullptr, vp);

     * Store the Debugger.Frame object for iter in *vp. Eagerly copies a
     * ScriptFrameIter::Data.
     * Use this if you had to make a ScriptFrameIter to get the required
     * frame, in which case the cost of walking the stack has already been
     * paid.
    bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter, MutableHandleValue vp) {
        return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp);

     * Set |*status| and |*value| to a (JSTrapStatus, Value) pair reflecting a
     * standard SpiderMonkey call state: a boolean success value |ok|, a return
     * value |rv|, and a context |cx| that may or may not have an exception set.
     * If an exception was pending on |cx|, it is cleared (and |ok| is asserted
     * to be false).
    static void resultToCompletion(JSContext* cx, bool ok, const Value& rv,
                                   JSTrapStatus* status, MutableHandleValue value);

     * Set |*result| to a JavaScript completion value corresponding to |status|
     * and |value|. |value| should be the return value or exception value, not
     * wrapped as a debuggee value. |cx| must be in the debugger compartment.
    bool newCompletionValue(JSContext* cx, JSTrapStatus status, Value value,
                            MutableHandleValue result);

     * Precondition: we are in the debuggee compartment (ac is entered) and ok
     * is true if the operation in the debuggee compartment succeeded, false on
     * error or exception.
     * Postcondition: we are in the debugger compartment, having called
     * ac.leave() even if an error occurred.
     * On success, a completion value is in vp and ac.context does not have a
     * pending exception. (This ordinarily returns true even if the ok argument
     * is false.)
    bool receiveCompletionValue(mozilla::Maybe<AutoCompartment>& ac, bool ok,
                                HandleValue val,
                                MutableHandleValue vp);

     * Return the Debugger.Script object for |script|, or create a new one if
     * needed. The context |cx| must be in the debugger compartment; |script|
     * must be a script in a debuggee compartment.
    JSObject* wrapScript(JSContext* cx, HandleScript script);

     * Return the Debugger.Script object for |wasmModule| (the toplevel
     * script), synthesizing a new one if needed. The context |cx| must be in
     * the debugger compartment; |wasmModule| must be a WasmModuleObject in
     * the debuggee compartment.
    JSObject* wrapWasmScript(JSContext* cx, Handle<WasmModuleObject*> wasmModule);

     * Return the Debugger.Source object for |source|, or create a new one if
     * needed. The context |cx| must be in the debugger compartment; |source|
     * must be a script source object in a debuggee compartment.
    JSObject* wrapSource(JSContext* cx, js::HandleScriptSource source);

     * Return the Debugger.Source object for |wasmModule| (the entire module),
     * synthesizing a new one if needed. The context |cx| must be in the
     * debugger compartment; |wasmModule| must be a WasmModuleObject in the
     * debuggee compartment.
    JSObject* wrapWasmSource(JSContext* cx, Handle<WasmModuleObject*> wasmModule);

    Debugger(const Debugger&) = delete;
    Debugger & operator=(const Debugger&) = delete;

class DebuggerObject : public NativeObject
    static const Class class_;

    static NativeObject* initClass(JSContext* cx, HandleObject obj, HandleObject debugCtor);
    static DebuggerObject* create(JSContext* cx, HandleObject proto, HandleObject obj,
                                  HandleNativeObject debugger);

    static bool isExtensible(JSContext* cx, Handle<DebuggerObject*> object, bool& result);
    static bool isSealed(JSContext* cx, Handle<DebuggerObject*> object, bool& result);
    static bool isFrozen(JSContext* cx, Handle<DebuggerObject*> object, bool& result);
    static bool getOwnPropertyNames(JSContext* cx, Handle<DebuggerObject*> object,
                                    MutableHandle<IdVector> result);
    static bool getOwnPropertySymbols(JSContext* cx, Handle<DebuggerObject*> object,
                                      MutableHandle<IdVector> result);
    static bool getOwnPropertyDescriptor(JSContext* cx, Handle<DebuggerObject*> object,
                                         HandleId id, MutableHandle<PropertyDescriptor> desc);
    static bool preventExtensions(JSContext* cx, Handle<DebuggerObject*> object);
    static bool seal(JSContext* cx, Handle<DebuggerObject*> object);
    static bool freeze(JSContext* cx, Handle<DebuggerObject*> object);
    static bool defineProperty(JSContext* cx, Handle<DebuggerObject*> object, HandleId id,
                               Handle<PropertyDescriptor> desc);
    static bool defineProperties(JSContext* cx, Handle<DebuggerObject*> object,
                                 Handle<IdVector> ids,
                                 Handle<PropertyDescriptorVector> descs);
    static bool deleteProperty(JSContext* cx, Handle<DebuggerObject*> object, HandleId id,
                               ObjectOpResult& result);
    static bool call(JSContext* cx, Handle<DebuggerObject*> object, HandleValue thisv,
                     Handle<ValueVector> args, MutableHandleValue result);

    enum {

    static const unsigned RESERVED_SLOTS = 1;

    static const JSPropertySpec properties_[];
    static const JSPropertySpec promiseProperties_[];
    static const JSFunctionSpec methods_[];

    JSObject* referent() const {
        JSObject* obj = (JSObject*) getPrivate();
        return obj;

    Debugger* owner() const;

class BreakpointSite {
    friend class Breakpoint;
    friend struct ::JSCompartment;
    friend class ::JSScript;
    friend class Debugger;

    JSScript* script;
    jsbytecode * const pc;

    JSCList breakpoints;  /* cyclic list of all js::Breakpoints at this instruction */
    size_t enabledCount;  /* number of breakpoints in the list that are enabled */

    void recompile(FreeOp* fop);

    BreakpointSite(JSScript* script, jsbytecode* pc);
    Breakpoint* firstBreakpoint() const;
    bool hasBreakpoint(Breakpoint* bp);

    void inc(FreeOp* fop);
    void dec(FreeOp* fop);
    void destroyIfEmpty(FreeOp* fop);

 * Each Breakpoint is a member of two linked lists: its debugger's list and its
 * site's list.
 * GC rules:
 *   - script is live, breakpoint exists, and debugger is enabled
 *      ==> debugger is live
 *   - script is live, breakpoint exists, and debugger is live
 *      ==> retain the breakpoint and the handler object is live
 * Debugger::markAllIteratively implements these two rules. It uses
 * Debugger::hasAnyLiveHooks to check for rule 1.
 * Nothing else causes a breakpoint to be retained, so if its script or
 * debugger is collected, the breakpoint is destroyed during GC sweep phase,
 * even if the debugger compartment isn't being GC'd. This is implemented in
 * Zone::sweepBreakpoints.
class Breakpoint {
    friend struct ::JSCompartment;
    friend class Debugger;

    Debugger * const debugger;
    BreakpointSite * const site;
    /* |handler| is marked unconditionally during minor GC. */
    js::PreBarrieredObject handler;
    JSCList debuggerLinks;
    JSCList siteLinks;

    static Breakpoint* fromDebuggerLinks(JSCList* links);
    static Breakpoint* fromSiteLinks(JSCList* links);
    Breakpoint(Debugger* debugger, BreakpointSite* site, JSObject* handler);
    void destroy(FreeOp* fop);
    Breakpoint* nextInDebugger();
    Breakpoint* nextInSite();
    const PreBarrieredObject& getHandler() const { return handler; }
    PreBarrieredObject& getHandlerRef() { return handler; }

Debugger::firstBreakpoint() const
    if (JS_CLIST_IS_EMPTY(&breakpoints))
        return nullptr;
    return Breakpoint::fromDebuggerLinks(JS_NEXT_LINK(&breakpoints));

/* static */ Debugger*
Debugger::fromOnNewGlobalObjectWatchersLink(JSCList* link) {
    char* p = reinterpret_cast<char*>(link);
    return reinterpret_cast<Debugger*>(p - offsetof(Debugger, onNewGlobalObjectWatchersLink));

const js::GCPtrNativeObject&
Debugger::toJSObject() const
    return object;

    return object;

Debugger::observesEnterFrame() const
    return enabled && getHook(OnEnterFrame);

Debugger::observesNewScript() const
    return enabled && getHook(OnNewScript);

Debugger::observesNewGlobalObject() const
    return enabled && getHook(OnNewGlobalObject);

Debugger::observesGlobal(GlobalObject* global) const
    ReadBarriered<GlobalObject*> debuggee(global);
    return debuggees.has(debuggee);

/* static */ void
Debugger::onNewScript(JSContext* cx, HandleScript script)
    // We early return in slowPathOnNewScript for self-hosted scripts, so we can
    // ignore those in our assertion here.
    MOZ_ASSERT_IF(!script->compartment()->creationOptions().invisibleToDebugger() &&
    if (script->compartment()->isDebuggee())
        slowPathOnNewScript(cx, script);

/* static */ void
Debugger::onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global)
#ifdef DEBUG
    global->compartment()->firedOnNewGlobalObject = true;
    if (!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers))
        Debugger::slowPathOnNewGlobalObject(cx, global);

/* static */ bool
Debugger::onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame, double when)
    GlobalObject::DebuggerVector* dbgs = cx->global()->getDebuggers();
    if (!dbgs || dbgs->empty())
        return true;
    RootedObject hobj(cx, obj);
    return Debugger::slowPathOnLogAllocationSite(cx, hobj, frame, when, *dbgs);

bool ReportObjectRequired(JSContext* cx);

} /* namespace js */

#endif /* vm_Debugger_h */