js/src/vm/Debugger.h
author Jon Coppeard <jcoppeard@mozilla.com>
Wed, 23 Mar 2016 09:41:17 +0000
changeset 290013 9f7c4f40087a4391e68768c0f85bbe6881301a6c
parent 289335 a803bb1e41a2bbecc47ff6f2998d2920c01dc18f
child 290302 27e149c2ecdc284b8a53f7ef14ea744c41aa333d
permissions -rw-r--r--
Bug 1258314 - Use TraceNullableEdge where appropriate throughout the engine r=terrence

/* -*- 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 {
    JSTRAP_ERROR,
    JSTRAP_CONTINUE,
    JSTRAP_RETURN,
    JSTRAP_THROW,
    JSTRAP_LIMIT
};

namespace js {

class Breakpoint;
class DebuggerMemory;

typedef HashSet<ReadBarrieredGlobalObject,
                MovableCellHasher<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<RelocatablePtr<UnbarrieredKey>, RelocatablePtrObject,
                                        MovableCellHasher<RelocatablePtr<UnbarrieredKey>>>
{
  private:
    typedef RelocatablePtr<UnbarrieredKey> Key;
    typedef RelocatablePtrObject Value;

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

    CountMap zoneCounts;
    JSCompartment* compartment;

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

    explicit DebuggerWeakMap(JSContext* cx)
        : Base(cx),
          zoneCounts(cx->runtime()),
          compartment(cx->compartment())
    { }

  public:
    /* 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);
        MOZ_ASSERT(!k->compartment()->creationOptions().mergeable());
        MOZ_ASSERT_IF(!InvisibleKeysOk,
                      !k->compartment()->creationOptions().invisibleToDebugger());
        MOZ_ASSERT(!Base::has(k));
        if (!incZoneCount(k->zone()))
            return false;
        bool ok = Base::relookupOrAdd(p, k, v);
        if (!ok)
            decZoneCount(k->zone());
        return ok;
    }

    void remove(const Lookup& l) {
        MOZ_ASSERT(Base::has(l));
        Base::remove(l);
        decZoneCount(l->zone());
    }

  public:
    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())
                e.rekeyFront(key);
            key.unsafeSet(nullptr);
        }
    }

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

  private:
    /* 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())) {
                decZoneCount(e.front().key()->zone());
                e.removeFront();
            }
        }
        Base::assertEntriesNotAboutToBeFinalized();
    }

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

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

class LeaveDebuggeeNoExecute;

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

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

    ~AutoSuppressDebuggeeNoExecuteChecks() {
        MOZ_ASSERT(!*stack_);
        *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 Call, Block, With, and DeclEnv objects, 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);

  public:
    enum Hook {
        OnDebuggerStatement,
        OnExceptionUnwind,
        OnNewScript,
        OnEnterFrame,
        OnNewGlobalObject,
        OnNewPromise,
        OnPromiseSettled,
        OnGarbageCollection,
        HookCount
    };
    enum {
        JSSLOT_DEBUG_PROTO_START,
        JSSLOT_DEBUG_FRAME_PROTO = JSSLOT_DEBUG_PROTO_START,
        JSSLOT_DEBUG_ENV_PROTO,
        JSSLOT_DEBUG_OBJECT_PROTO,
        JSSLOT_DEBUG_SCRIPT_PROTO,
        JSSLOT_DEBUG_SOURCE_PROTO,
        JSSLOT_DEBUG_MEMORY_PROTO,
        JSSLOT_DEBUG_PROTO_STOP,
        JSSLOT_DEBUG_HOOK_START = JSSLOT_DEBUG_PROTO_STOP,
        JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount,
        JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP,
        JSSLOT_DEBUG_COUNT
    };

    class ExecutionObservableSet
    {
      public:
        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),
              when(when),
              className(className),
              ctorName(ctorName),
              size(size),
              inNursery(inNursery)
        {
            MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>());
        };

        RelocatablePtrObject frame;
        double when;
        const char* className;
        RelocatablePtrAtom 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) {
        InternalBarrierMethods<JSObject*>::readBarrier(dbg->object);
    }
    static void writeBarrierPost(Debugger** vp, Debugger* prev, Debugger* next) {}

  private:
    HeapPtrNativeObject 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::HeapPtrObject 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,
                    RelocatablePtrNativeObject,
                    DefaultHasher<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.
     */
#ifdef NIGHTLY_BUILD
    uint32_t traceLoggerLastDrainedSize;
    uint32_t traceLoggerLastDrainedIteration;
#endif
    uint32_t traceLoggerScriptedCallsLastDrainedSize;
    uint32_t traceLoggerScriptedCallsLastDrainedIteration;

    class FrameRange;
    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 Class jsclass;

    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);
#ifdef NIGHTLY_BUILD
    static bool setupTraceLogger(JSContext* cx, unsigned argc, Value* vp);
    static bool drainTraceLogger(JSContext* cx, unsigned argc, Value* vp);
#endif
    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);

  public:
    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;

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

    JSObject* newVariantWrapper(JSContext* cx, Handle<DebuggerScriptReferent> referent) {
        return newDebuggerScript(cx, referent);
    }
    JSObject* 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, CrossCompartmentKey::Kind keyKind,
                                  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|.
     */
    JSObject* newDebuggerScript(JSContext* cx, Handle<DebuggerScriptReferent> referent);

    /*
     * Allocate and initialize a Debugger.Source instance whose referent is
     * |referent|.
     */
    JSObject* 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);

  public:
    Debugger(JSContext* cx, NativeObject* dbg);
    ~Debugger();

    bool init(JSContext* cx);
    inline const js::HeapPtrNativeObject& toJSObject() const;
    inline js::HeapPtrNativeObject& 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::ComponentFinder<JS::Zone>& 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);

  private:
    Debugger(const Debugger&) = delete;
    Debugger & operator=(const Debugger&) = delete;
};

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

  public:
    JSScript* script;
    jsbytecode * const pc;

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

  public:
    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;

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

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

Breakpoint*
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::HeapPtrNativeObject&
Debugger::toJSObject() const
{
    MOZ_ASSERT(object);
    return object;
}

js::HeapPtrNativeObject&
Debugger::toJSObjectRef()
{
    MOZ_ASSERT(object);
    return object;
}

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

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

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

bool
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() &&
                  !script->selfHosted(),
                  script->compartment()->firedOnNewGlobalObject);
    if (script->compartment()->isDebuggee())
        slowPathOnNewScript(cx, script);
}

/* static */ void
Debugger::onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global)
{
    MOZ_ASSERT(!global->compartment()->firedOnNewGlobalObject);
#ifdef DEBUG
    global->compartment()->firedOnNewGlobalObject = true;
#endif
    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 */