/* -*- 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/LinkedList.h"
#include "mozilla/Range.h"

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

#include "gc/Barrier.h"
#include "js/Debug.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;

/*
 * 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 debugee 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.
 */
template <class UnbarrieredKey, bool InvisibleKeysOk=false>
class DebuggerWeakMap : private WeakMap<PreBarriered<UnbarrieredKey>, RelocatablePtrObject>
{
  private:
    typedef PreBarriered<UnbarrieredKey> Key;
    typedef RelocatablePtrObject Value;

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

    CountMap zoneCounts;

  public:
    typedef WeakMap<Key, Value, DefaultHasher<Key> > Base;
    explicit DebuggerWeakMap(JSContext* cx)
        : Base(cx), zoneCounts(cx->runtime()) { }

  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() == Base::compartment);
        MOZ_ASSERT(!k->compartment()->options_.mergeable());
        MOZ_ASSERT_IF(!InvisibleKeysOk, !k->compartment()->options_.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();
            gc::Mark(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()) {
            Key k(e.front().key());
            if (gc::IsAboutToBeFinalized(&k)) {
                e.removeFront();
                decZoneCount(k->zone());
            } else {
                // markKeys() should have done any necessary relocation.
                MOZ_ASSERT(k == e.front().key());
            }
        }
        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);
    }
};

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

class Debugger : private mozilla::LinkedListElement<Debugger>
{
    friend class Breakpoint;
    friend class DebuggerMemory;
    friend class SavedStacks;
    friend class mozilla::LinkedListElement<Debugger>;
    friend bool (::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj);
    friend bool (::JS::dbg::IsDebugger)(JS::Value val);
    friend bool SavedStacksMetadataCallback(JSContext* cx, JSObject** pmetadata);
    friend void JS::dbg::onNewPromise(JSContext* cx, HandleObject promise);
    friend void JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise);

  public:
    enum Hook {
        OnDebuggerStatement,
        OnExceptionUnwind,
        OnNewScript,
        OnEnterFrame,
        OnNewGlobalObject,
        OnNewPromise,
        OnPromiseSettled,
        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 isDebuggee(const JSCompartment* compartment) const;

  private:
    HeapPtrNativeObject object;         /* The Debugger object. Strong reference. */
    GlobalObjectSet debuggees;          /* Debuggee globals. Cross-compartment weak references. */
    js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */
    bool enabled;
    JSCList breakpoints;                /* Circular list of all js::Breakpoints in this debugger */

    struct AllocationSite : public mozilla::LinkedListElement<AllocationSite>
    {
        AllocationSite(HandleObject frame, int64_t when) : frame(frame), when(when) {
            MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>());
        };
        RelocatablePtrObject frame;
        int64_t when;
    };
    typedef mozilla::LinkedList<AllocationSite> AllocationSiteList;

    bool allowUnobservedAsmJS;

    bool trackingAllocationSites;
    double allocationSamplingProbability;
    AllocationSiteList allocationsLog;
    size_t allocationsLogLength;
    size_t maxAllocationsLogLength;
    bool allocationsLogOverflowed;

    static const size_t DEFAULT_MAX_ALLOCATIONS_LOG_LENGTH = 5000;

    bool appendAllocationSite(JSContext* cx, HandleSavedFrame frame, int64_t when);
    void emptyAllocationsLog();

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

    /*
     * Keep track of tracelogger last drained identifiers to know if there are
     * lost events.
     */
#ifdef NIGHTLY_BUILD
    uint32_t traceLoggerLastDrainedId;
    uint32_t traceLoggerLastDrainedIteration;
#endif
    uint32_t traceLoggerScriptedCallsLastDrainedId;
    uint32_t traceLoggerScriptedCallsLastDrainedIteration;

    class FrameRange;
    class ScriptQuery;
    class ObjectQuery;

    bool addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> obj);
    void removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global, GlobalObjectSet::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);

    JSTrapStatus handleUncaughtExceptionHelper(mozilla::Maybe<AutoCompartment>& ac,
                                               MutableHandleValue* vp, bool callHook);

    /*
     * 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,
                                      MutableHandleValue vp, bool callHook = true);

    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 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 getHookImpl(JSContext* cx, unsigned argc, Value* vp, Hook which);
    static bool setHookImpl(JSContext* cx, unsigned argc, Value* vp, Hook which);
    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 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);
#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 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;

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

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

    static JSTrapStatus slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame);
    static bool slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, 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 slowPathOnNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
    static bool slowPathOnLogAllocationSite(JSContext* cx, HandleSavedFrame frame,
                                            int64_t when, GlobalObject::DebuggerVector& dbgs);
    static void slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise);
    static JSTrapStatus dispatchHook(JSContext* cx, MutableHandleValue vp, Hook which,
                                     HandleObject payload);

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

    /*
     * Allocate and initialize a Debugger.Script instance whose referent is
     * |script|.
     */
    JSObject* newDebuggerScript(JSContext* cx, HandleScript script);

    /*
     * Allocate and initialize a Debugger.Source instance whose referent is
     * |source|.
     */
    JSObject* newDebuggerSource(JSContext* cx, js::HandleScriptSource source);

    /*
     * Receive a "new script" event from the engine. A new script was compiled
     * or deserialized.
     */
    void fireNewScript(JSContext* cx, HandleScript script);

    /*
     * 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(JSObject* obj);
    static Debugger* fromChildJSObject(JSObject* obj);

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

    GlobalObjectSet::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 markAllCrossCompartmentEdges(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 findCompartmentEdges(JS::Zone* v, gc::ComponentFinder<JS::Zone>& finder);

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

    static inline void onNewScript(JSContext* cx, HandleScript script);
    static inline void onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
    static inline bool onLogAllocationSite(JSContext* cx, HandleSavedFrame frame, int64_t 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 void assertNotInFrameMaps(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 unwrapPropDescInto(JSContext* cx, HandleObject obj, Handle<PropDesc> wrapped,
                            MutableHandle<PropDesc> unwrapped);

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

  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
 * JSCompartment::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
{
    return debuggees.has(global);
}

/* 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()->options().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, HandleSavedFrame frame, int64_t when)
{
    GlobalObject::DebuggerVector* dbgs = cx->global()->getDebuggers();
    if (!dbgs || dbgs->empty())
        return true;
    return Debugger::slowPathOnLogAllocationSite(cx, frame, when, *dbgs);
}

bool ReportObjectRequired(JSContext* cx);

} /* namespace js */

#endif /* vm_Debugger_h */
