Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  Debugger.h   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#ifndef debugger_Debugger_h
#define debugger_Debugger_h

#include "mozilla/Assertions.h"        // for MOZ_ASSERT_HELPER1
#include "mozilla/Attributes.h"        // for MOZ_RAII
#include "mozilla/DoublyLinkedList.h"  // for DoublyLinkedListElement
#include "mozilla/HashTable.h"         // for HashSet, DefaultHasher (ptr only)
#include "mozilla/LinkedList.h"        // for LinkedList (ptr only)
#include "mozilla/Maybe.h"             // for Maybe, Nothing
#include "mozilla/Range.h"             // for Range
#include "mozilla/Result.h"            // for Result
#include "mozilla/TimeStamp.h"         // for TimeStamp
#include "mozilla/Variant.h"           // for Variant

#include <stddef.h>  // for size_t
#include <stdint.h>  // for uint32_t, uint64_t, uintptr_t
#include <utility>   // for std::move

#include "jstypes.h"           // for JS_GC_ZEAL
#include "NamespaceImports.h"  // for Value, HandleObject

#include "debugger/DebugAPI.h"      // for DebugAPI
#include "debugger/Object.h"        // for DebuggerObject
#include "ds/TraceableFifo.h"       // for TraceableFifo
#include "gc/Barrier.h"             //
#include "gc/Tracer.h"              // for TraceNullableEdge, TraceEdge
#include "gc/WeakMap.h"             // for WeakMap
#include "gc/ZoneAllocator.h"       // for ZoneAllocPolicy
#include "js/Debug.h"               // JS_DefineDebuggerObject
#include "js/GCAPI.h"               // for GarbageCollectionEvent
#include "js/GCVariant.h"           // for GCVariant
#include "js/Proxy.h"               // for PropertyDescriptor
#include "js/RootingAPI.h"          // for Handle
#include "js/TracingAPI.h"          // for TraceRoot
#include "js/Wrapper.h"             // for UncheckedUnwrap
#include "proxy/DeadObjectProxy.h"  // for IsDeadProxyObject
#include "vm/GeneratorObject.h"     // for AbstractGeneratorObject
#include "vm/GlobalObject.h"        // for GlobalObject
#include "vm/JSContext.h"           // for JSContext
#include "vm/JSObject.h"            // for JSObject
#include "vm/JSScript.h"            // for JSScript, ScriptSourceObject
#include "vm/NativeObject.h"        // for NativeObject
#include "vm/Runtime.h"             // for JSRuntime
#include "vm/SavedFrame.h"          // for SavedFrame
#include "vm/Stack.h"               // for AbstractFramePtr, FrameIter
#include "vm/StringType.h"          // for JSAtom
#include "wasm/WasmJS.h"            // for WasmInstanceObject

class JS_PUBLIC_API JSFunction;

namespace JS {
class JS_PUBLIC_API AutoStableStringChars;
class JS_PUBLIC_API Compartment;
class JS_PUBLIC_API Realm;
class JS_PUBLIC_API Zone;
/* namespace JS */

namespace js {
class AutoRealm;
class CrossCompartmentKey;
class Debugger;
class DebuggerEnvironment;
class PromiseObject;
namespace gc {
struct Cell;
/* namespace gc */
namespace wasm {
class Instance;
/* namespace wasm */
/* namespace js */

/*
 * Windows 3.x used a cooperative multitasking model, with a Yield macro that
 * let you relinquish control to other cooperative threads. Microsoft replaced
 * it with an empty macro long ago. We should be free to use it in our code.
 */

#undef Yield

namespace js {

class Breakpoint;
class DebuggerFrame;
class DebuggerScript;
class DebuggerSource;
class DebuggerMemory;
class ScriptedOnStepHandler;
class ScriptedOnPopHandler;
class DebuggerDebuggeeLink;

/**
 * Tells how the JS engine should resume debuggee execution after firing a
 * debugger hook.  Most debugger hooks get to choose how the debuggee proceeds;
 * see js/src/doc/Debugger/Conventions.md under "Resumption Values".
 *
 * Debugger::processHandlerResult() translates between JavaScript values and
 * this enum.
 */

enum class ResumeMode {
  /**
   * The debuggee should continue unchanged.
   *
   * This corresponds to a resumption value of `undefined`.
   */

  Continue,

  /**
   * Throw an exception in the debuggee.
   *
   * This corresponds to a resumption value of `{throw: <value>}`.
   */

  Throw,

  /**
   * Terminate the debuggee, as if it had been cancelled via the "slow
   * script" ribbon.
   *
   * This corresponds to a resumption value of `null`.
   */

  Terminate,

  /**
   * Force the debuggee to return from the current frame.
   *
   * This corresponds to a resumption value of `{return: <value>}`.
   */

  Return,
};

/**
 * A completion value, describing how some sort of JavaScript evaluation
 * completed. This is used to tell an onPop handler what's going on with the
 * frame, and to report the outcome of call, apply, setProperty, and getProperty
 * operations.
 *
 * Local variables of type Completion should be held in Rooted locations,
 * and passed using Handle and MutableHandle.
 */

class Completion {
 public:
  struct Return {
    explicit Return(const Value& value) : value(value) {}
    Value value;

    void trace(JSTracer* trc) {
      JS::TraceRoot(trc, &value, "js::Completion::Return::value");
    }
  };

  struct Throw {
    Throw(const Value& exception, SavedFrame* stack)
        : exception(exception), stack(stack) {}
    Value exception;
    SavedFrame* stack;

    void trace(JSTracer* trc) {
      JS::TraceRoot(trc, &exception, "js::Completion::Throw::exception");
      JS::TraceRoot(trc, &stack, "js::Completion::Throw::stack");
    }
  };

  struct Terminate {
    void trace(JSTracer* trc) {}
  };

  struct InitialYield {
    explicit InitialYield(AbstractGeneratorObject* generatorObject)
        : generatorObject(generatorObject) {}
    AbstractGeneratorObject* generatorObject;

    void trace(JSTracer* trc) {
      JS::TraceRoot(trc, &generatorObject,
                    "js::Completion::InitialYield::generatorObject");
    }
  };

  struct Yield {
    Yield(AbstractGeneratorObject* generatorObject, const Value& iteratorResult)
        : generatorObject(generatorObject), iteratorResult(iteratorResult) {}
    AbstractGeneratorObject* generatorObject;
    Value iteratorResult;

    void trace(JSTracer* trc) {
      JS::TraceRoot(trc, &generatorObject,
                    "js::Completion::Yield::generatorObject");
      JS::TraceRoot(trc, &iteratorResult,
                    "js::Completion::Yield::iteratorResult");
    }
  };

  struct Await {
    Await(AbstractGeneratorObject* generatorObject, const Value& awaitee)
        : generatorObject(generatorObject), awaitee(awaitee) {}
    AbstractGeneratorObject* generatorObject;
    Value awaitee;

    void trace(JSTracer* trc) {
      JS::TraceRoot(trc, &generatorObject,
                    "js::Completion::Await::generatorObject");
      JS::TraceRoot(trc, &awaitee, "js::Completion::Await::awaitee");
    }
  };

  // The JS::Result macros want to assign to an existing variable, so having a
  // default constructor is handy.
  Completion() : variant(Terminate()) {}

  // Construct a completion from a specific variant.
  //
  // Unfortunately, using a template here would prevent the implicit definitions
  // of the copy and move constructor and assignment operators, which is icky.
  explicit Completion(Return&& variant)
      : variant(std::forward<Return>(variant)) {}
  explicit Completion(Throw&& variant)
      : variant(std::forward<Throw>(variant)) {}
  explicit Completion(Terminate&& variant)
      : variant(std::forward<Terminate>(variant)) {}
  explicit Completion(InitialYield&& variant)
      : variant(std::forward<InitialYield>(variant)) {}
  explicit Completion(Yield&& variant)
      : variant(std::forward<Yield>(variant)) {}
  explicit Completion(Await&& variant)
      : variant(std::forward<Await>(variant)) {}

  // Capture a JavaScript operation result as a Completion value. This clears
  // any exception and stack from cx, taking ownership of them itself.
  static Completion fromJSResult(JSContext* cx, bool ok, const Value& rv);

  // Construct a completion given an AbstractFramePtr that is being popped. This
  // clears any exception and stack from cx, taking ownership of them itself.
  static Completion fromJSFramePop(JSContext* cx, AbstractFramePtr frame,
                                   const jsbytecode* pc, bool ok);

  template <typename V>
  bool is() const {
    return variant.template is<V>();
  }

  template <typename V>
  V& as() {
    return variant.template as<V>();
  }

  template <typename V>
  const V& as() const {
    return variant.template as<V>();
  }

  void trace(JSTracer* trc);

  /* True if this completion is a suspension of a generator or async call. */
  bool suspending() const {
    return variant.is<InitialYield>() || variant.is<Yield>() ||
           variant.is<Await>();
  }

  /* Set `result` to a Debugger API completion value describing this completion.
   */

  bool buildCompletionValue(JSContext* cx, Debugger* dbg,
                            MutableHandleValue result) const;

  /*
   * Set `resumeMode`, `value`, and `exnStack` to values describing this
   * completion.
   */

  void toResumeMode(ResumeMode& resumeMode, MutableHandleValue value,
                    MutableHandle<SavedFrame*> exnStack) const;
  /*
   * Given a `ResumeMode` and value (typically derived from a resumption value
   * returned by a Debugger hook), update this completion as requested.
   */

  void updateFromHookResult(ResumeMode resumeMode, HandleValue value);

 private:
  using Variant =
      mozilla::Variant<ReturnThrow, Terminate, InitialYield, Yield, Await>;
  struct BuildValueMatcher;
  struct ToResumeModeMatcher;

  Variant variant;
};

using WeakGlobalObjectSet =
    HashSet<WeakHeapPtr<GlobalObject*>,
            StableCellHasher<WeakHeapPtr<GlobalObject*>>, ZoneAllocPolicy>;

#ifdef DEBUG
extern void CheckDebuggeeThing(BaseScript* script, bool invisibleOk);

extern void CheckDebuggeeThing(JSObject* obj, bool invisibleOk);
#endif

/*
 * [SMDOC] Cross-compartment weakmap entries for Debugger API objects
 *
 * The Debugger API creates objects like Debugger.Object, Debugger.Script,
 * Debugger.Environment, etc. to refer to things in the debuggee. Each Debugger
 * gets at most one Debugger.Mumble for each referent: Debugger.Mumbles are
 * unique per referent per Debugger. This is accomplished by storing the
 * debugger objects in a DebuggerWeakMap, using the debuggee thing as the key.
 *
 * Since a Debugger and its debuggee must be in different compartments, a
 * Debugger.Mumble's pointer to its referent is a cross-compartment edge, from
 * the debugger's compartment into the debuggee compartment. Like any other sort
 * of cross-compartment edge, the GC needs to be able to find all of these edges
 * readily. The GC therefore consults the debugger's weakmap tables as
 * necessary.  This allows the garbage collector to easily find edges between
 * debuggee object compartments and debugger compartments when calculating the
 * zone sweep groups.
 *
 * 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 this results in a simpler
 * and faster implementation.
 */


/*
 * 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.
 *
 * 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.
 *
 * Note that keys in these weakmaps can be in any compartment, debuggee or not,
 * because they are not deleted when a compartment is no longer a debuggee: the
 * values need to maintain object identity across add/remove/add
 * transitions. (Frames are an exception to the rule. Existing Debugger.Frame
 * objects are killed if their realm is removed as a debugger; if the realm
 * beacomes a debuggee again later, new Frame objects are created.)
 */

template <class Referent, class Wrapper, bool InvisibleKeysOk = false>
class DebuggerWeakMap : private WeakMap<HeapPtr<Referent*>, HeapPtr<Wrapper*>> {
 private:
  using Key = HeapPtr<Referent*>;
  using Value = HeapPtr<Wrapper*>;

  JS::Compartment* compartment;

 public:
  using Base = WeakMap<Key, Value>;
  using ReferentType = Referent;
  using WrapperType = Wrapper;

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

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

  using Entry = typename Base::Entry;
  using Ptr = typename Base::Ptr;
  using AddPtr = typename Base::AddPtr;
  using Range = typename Base::Range;
  using Lookup = typename Base::Lookup;

  // Expose WeakMap public interface.

  using Base::all;
  using Base::has;
  using Base::lookup;
  using Base::lookupForAdd;
  using Base::lookupUnbarriered;
  using Base::remove;
  using Base::trace;
  using Base::zone;
#ifdef DEBUG
  using Base::hasEntry;
#endif

  class Enum : public Base::Enum {
   public:
    explicit Enum(DebuggerWeakMap& map) : Base::Enum(map) {}
  };

  template <typename KeyInput, typename ValueInput>
  bool relookupOrAdd(AddPtr& p, const KeyInput& k, const ValueInput& v) {
    MOZ_ASSERT(v->compartment() == this->compartment);
#ifdef DEBUG
    CheckDebuggeeThing(k, InvisibleKeysOk);
#endif
    MOZ_ASSERT(!Base::has(k));
    bool ok = Base::relookupOrAdd(p, k, v);
    return ok;
  }

 public:
  void traceCrossCompartmentEdges(JSTracer* tracer) {
    for (Enum e(*this); !e.empty(); e.popFront()) {
      TraceEdge(tracer, &e.front().mutableKey(), "Debugger WeakMap key");
      e.front().value()->trace(tracer);
    }
  }

  bool findSweepGroupEdges() override;

 private:
#ifdef JS_GC_ZEAL
  // Let the weak map marking verifier know that this map can
  // contain keys in other zones.
  virtual bool allowKeysInOtherZones() const override { return true; }
#endif
};

class LeaveDebuggeeNoExecute;

class MOZ_RAII EvalOptions {
 public:
  enum class EnvKind {
    Frame,
    FrameWithExtraBindings,
    Global,
    GlobalWithExtraOuterBindings,
    GlobalWithExtraInnerBindings,
  };

 private:
  JS::UniqueChars filename_;
  unsigned lineno_ = 1;
  bool hideFromDebugger_ = false;
  EnvKind kind_;

 public:
  explicit EvalOptions(EnvKind kind) : kind_(kind) {};
  ~EvalOptions() = default;
  const char* filename() const { return filename_.get(); }
  unsigned lineno() const { return lineno_; }
  bool hideFromDebugger() const { return hideFromDebugger_; }
  EnvKind kind() const { return kind_; }
  void setUseInnerBindings() {
    MOZ_ASSERT(kind_ == EvalOptions::EnvKind::GlobalWithExtraOuterBindings);
    kind_ = EvalOptions::EnvKind::GlobalWithExtraInnerBindings;
  }
  [[nodiscard]] bool setFilename(JSContext* cx, const char* filename);
  void setLineno(unsigned lineno) { lineno_ = lineno; }
  void setHideFromDebugger(bool hide) { hideFromDebugger_ = hide; }
};

/*
 * Env is the type of what ECMA-262 calls "lexical environments" (the records
 * that represent scopes and bindings). See vm/EnvironmentObject.h.
 *
 * This is JSObject rather than js::EnvironmentObject because GlobalObject and
 * some proxies, despite not being in the EnvironmentObject class hierarchy,
 * can be in environment chains.
 */

using Env = JSObject;

// The referent of a Debugger.Script.
//
// - For most scripts, we point at their BaseScript.
//
// - For Web Assembly instances for which we are presenting a script-like
//   interface, we point at their WasmInstanceObject.
//
// The DebuggerScript object itself simply stores a Cell* in its private
// pointer, but when we're working with that pointer in C++ code, we'd rather
// not pass around a Cell* and be constantly asserting that, yes, this really
// does point to something okay. Instead, we immediately build an instance of
// this type from the Cell* and use that instead, so we can benefit from
// Variant's static checks.
using DebuggerScriptReferent =
    mozilla::Variant<BaseScript*, WasmInstanceObject*>;

// The referent of a Debugger.Source.
//
// - For most sources, this is a ScriptSourceObject.
//
// - For Web Assembly instances for which we are presenting a source-like
//   interface, we point at their WasmInstanceObject.
//
// The DebuggerSource object actually simply stores a Cell* in its private
// pointer. See the comments for DebuggerScriptReferent for the rationale for
// this type.
using DebuggerSourceReferent =
    mozilla::Variant<ScriptSourceObject*, WasmInstanceObject*>;

template <typename HookIsEnabledFun /* bool (Debugger*) */>
class MOZ_RAII DebuggerList {
 private:
  // Note: In the general case, 'debuggers' contains references to objects in
  // different compartments--every compartment *except* the debugger's.
  RootedValueVector debuggers;
  HookIsEnabledFun hookIsEnabled;

 public:
  /**
   * The hook function will be called during `init()` to build the list of
   * active debuggers, and again during dispatch to validate that the hook is
   * still active for the given debugger.
   */

  DebuggerList(JSContext* cx, HookIsEnabledFun hookIsEnabled)
      : debuggers(cx), hookIsEnabled(hookIsEnabled) {}

  [[nodiscard]] bool init(JSContext* cx);

  bool empty() { return debuggers.empty(); }

  template <typename FireHookFun /* ResumeMode (Debugger*) */>
  bool dispatchHook(JSContext* cx, FireHookFun fireHook);

  template <typename FireHookFun /* void (Debugger*) */>
  void dispatchQuietHook(JSContext* cx, FireHookFun fireHook);

  template <typename FireHookFun /* bool (Debugger*, ResumeMode&, MutableHandleValue) */>
  [[nodiscard]] bool dispatchResumptionHook(JSContext* cx,
                                           AbstractFramePtr frame,
                                           FireHookFun fireHook);
};

// The Debugger.prototype object.
class DebuggerPrototypeObject : public NativeObject {
 public:
  static const JSClass class_;
};

class DebuggerInstanceObject : public NativeObject {
 private:
  static const JSClassOps classOps_;

 public:
  static const JSClass class_;
};

class Debugger : private mozilla::LinkedListElement<Debugger> {
  friend class DebugAPI;
  friend class Breakpoint;
  friend class DebuggerFrame;
  friend class DebuggerMemory;
  friend class DebuggerInstanceObject;

  template <typename>
  friend class DebuggerList;
  friend struct JSRuntime::GlobalObjectWatchersLinkAccess<Debugger>;
  friend struct JSRuntime::GarbageCollectionWatchersLinkAccess<Debugger>;
  friend class SavedStacks;
  friend class ScriptedOnStepHandler;
  friend class ScriptedOnPopHandler;
  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&,
                                             MutableHandleObjectVector);
  friend bool JS::dbg::FireOnGarbageCollectionHookRequired(JSContext* cx);
  friend bool JS::dbg::FireOnGarbageCollectionHook(
      JSContext* cx, JS::dbg::GarbageCollectionEvent::Ptr&& data);

 public:
  enum Hook {
    OnDebuggerStatement,
    OnExceptionUnwind,
    OnNewScript,
    OnEnterFrame,
    OnNativeCall,
    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_DEBUGGER = JSSLOT_DEBUG_PROTO_STOP,
    JSSLOT_DEBUG_HOOK_START,
    JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount,
    JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP,
    JSSLOT_DEBUG_DEBUGGEE_LINK,
    JSSLOT_DEBUG_COUNT
  };

  // Bring DebugAPI::IsObserving into the Debugger namespace.
  using IsObserving = DebugAPI::IsObserving;
  static const IsObserving Observing = DebugAPI::Observing;
  static const IsObserving NotObserving = DebugAPI::NotObserving;

  // Return true if the given realm is a debuggee of this debugger,
  // false otherwise.
  bool isDebuggeeUnbarriered(const Realm* realm) 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);
  }

  static SavedFrame* getObjectAllocationSite(JSObject& obj);

  struct AllocationsLogEntry {
    AllocationsLogEntry(HandleObject frame, mozilla::TimeStamp when,
                        const char* className, size_t size, bool inNursery)
        : frame(frame),
          when(when),
          className(className),
          size(size),
          inNursery(inNursery) {
      MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>() ||
                               IsDeadProxyObject(frame));
    }

    HeapPtr<JSObject*> frame;
    mozilla::TimeStamp when;
    const char* className;
    size_t size;
    bool inNursery;

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

 private:
  HeapPtr<NativeObject*> 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. */
  HeapPtr<JSObject*> uncaughtExceptionHook; /* Strong reference. */
  bool allowUnobservedAsmJS;
  bool allowUnobservedWasm;

  // When this flag is true, this debugger should be the only one to have its
  // hooks called when it evaluates via Frame.evalWithBindings,
  // Object.executeInGlobalWithBindings or Object.call.
  bool exclusiveDebuggerOnEval;

  // When this flag is true, the onNativeCall hook is called with additional
  // arguments which are the native function call arguments and well as a
  // reference to the object on which the function call (if any).
  bool inspectNativeCallArguments;

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

  // Whether to ask avoid side-effects in the native code.
  // See JS::dbg::ShouldAvoidSideEffects.
  bool shouldAvoidSideEffects;

  template <typename T>
  struct DebuggerLinkAccess {
    static mozilla::DoublyLinkedListElement<T>& Get(T* aThis) {
      return aThis->debuggerLink;
    }
  };

  // List of all js::Breakpoints in this debugger.
  using BreakpointList =
      mozilla::DoublyLinkedList<js::Breakpoint,
                                DebuggerLinkAccess<js::Breakpoint>>;
  BreakpointList breakpoints;

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

  [[nodiscard]] bool appendAllocationSite(JSContext* cx, HandleObject obj,
                                          Handle<SavedFrame*> frame,
                                          mozilla::TimeStamp 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);

  /*
   * 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 tracking allocations.
   */

  [[nodiscard]] 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.
   */

  [[nodiscard]] bool addAllocationsTrackingForAllDebuggees(JSContext* cx);
  void removeAllocationsTrackingForAllDebuggees();

  /*
   * If this Debugger has a onNewGlobalObject handler, then
   * this link is inserted into the list headed by
   * JSRuntime::onNewGlobalObjectWatchers.
   */

  mozilla::DoublyLinkedListElement<Debugger> onNewGlobalObjectWatchersLink;

  /*
   * If this Debugger has a onGarbageCollection handler, then
   * this link is inserted into the list headed by
   * JSRuntime::onGarbageCollectionWatchers.
   */

  mozilla::DoublyLinkedListElement<Debugger> onGarbageCollectionWatchersLink;

  /*
   * 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.
   *
   * Wasm JS PI allows suspending/resuming a portion of the stack, only
   * frame pointers and activations are changed. The stack frames are still
   * live, and shall be present in the frames map if DebuggerFrame is created.
   *
   * 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.
   */

  using FrameMap = HashMap<AbstractFramePtr, HeapPtr<DebuggerFrame*>,
                           DefaultHasher<AbstractFramePtr>, ZoneAllocPolicy>;
  FrameMap frames;

  /*
   * Map from generator objects to their Debugger.Frame instances.
   *
   * When a Debugger.Frame is created for a generator frame, it is added to
   * this map and remains there for the lifetime of the generator, whether
   * that frame is on the stack at the moment or not.  This is in addition to
   * the entry in `frames` that exists as long as the generator frame is on
   * the stack.
   *
   * We need to keep the Debugger.Frame object alive to deliver it to the
   * onEnterFrame handler on resume, and to retain onStep and onPop hooks.
   *
   * An entry is present in this table when:
   *  - both the debuggee generator object and the Debugger.Frame object exists
   *  - the debuggee generator object belongs to a realm that is a debuggee of
   *    the Debugger.Frame's owner.
   *
   * regardless of whether the frame is currently suspended. (This list is
   * meant to explain why we update the table in the particular places where
   * we do so.)
   *
   * An entry in this table exists if and only if the Debugger.Frame's
   * GENERATOR_INFO_SLOT is set.
   */

  using GeneratorWeakMap =
      DebuggerWeakMap<AbstractGeneratorObject, DebuggerFrame>;
  GeneratorWeakMap generatorFrames;

  // An ephemeral map from BaseScript* to Debugger.Script instances.
  using ScriptWeakMap = DebuggerWeakMap<BaseScript, DebuggerScript>;
  ScriptWeakMap scripts;

  using BaseScriptVector = JS::GCVector<BaseScript*>;

  // The map from debuggee source script objects to their Debugger.Source
  // instances.
  using SourceWeakMap =
      DebuggerWeakMap<ScriptSourceObject, DebuggerSource, true>;
  SourceWeakMap sources;

  // The map from debuggee objects to their Debugger.Object instances.
  using ObjectWeakMap = DebuggerWeakMap<JSObject, DebuggerObject>;
  ObjectWeakMap objects;

  // The map from debuggee Envs to Debugger.Environment instances.
  using EnvironmentWeakMap = DebuggerWeakMap<JSObject, DebuggerEnvironment>;
  EnvironmentWeakMap environments;

  // The map from WasmInstanceObjects to synthesized Debugger.Script
  // instances.
  using WasmInstanceScriptWeakMap =
      DebuggerWeakMap<WasmInstanceObject, DebuggerScript>;
  WasmInstanceScriptWeakMap wasmInstanceScripts;

  // The map from WasmInstanceObjects to synthesized Debugger.Source
  // instances.
  using WasmInstanceSourceWeakMap =
      DebuggerWeakMap<WasmInstanceObject, DebuggerSource>;
  WasmInstanceSourceWeakMap wasmInstanceSources;

  class QueryBase;
  class ScriptQuery;
  class SourceQuery;
  class ObjectQuery;

  enum class FromSweep { No, Yes };

  [[nodiscard]] bool addDebuggeeGlobal(JSContext* cx,
                                       Handle<GlobalObject*> obj);
  void removeDebuggeeGlobal(JS::GCContext* gcx, GlobalObject* global,
                            WeakGlobalObjectSet::Enum* debugEnum,
                            FromSweep fromSweep);

  /*
   * 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.
   *
   * If `success` is false, the hook failed. If an exception is pending in
   * ar.context(), attempt to handle it via the uncaught exception hook,
   * otherwise report it to the AutoRealm's global.
   *
   * If `success` is true, there must be no exception pending in ar.context().
   * `rv` may be:
   *
   *     undefined - Set `resultMode` to `ResumeMode::Continue` to continue
   *         execution normally.
   *
   *     {return: value} or {throw: value} - Call unwrapDebuggeeValue to
   *         unwrap `value`. Store the result in `vp` and set `resultMode` to
   *         `ResumeMode::Return` or `ResumeMode::Throw`. The interpreter
   *         will force the current frame to return or throw an exception.
   *
   *     null - Set `resultMode` to `ResumeMode::Terminate` to terminate the
   *         debuggee with an uncatchable error.
   *
   *     anything else - Make a new TypeError the pending exception and
   *         attempt to handle it with the uncaught exception handler.
   */

  [[nodiscard]] bool processHandlerResult(
      JSContext* cx, bool success, HandleValue rv, AbstractFramePtr frame,
      jsbytecode* pc, ResumeMode& resultMode, MutableHandleValue vp);

  [[nodiscard]] bool processParsedHandlerResult(
      JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc, bool success,
      ResumeMode resumeMode, HandleValue value, ResumeMode& resultMode,
      MutableHandleValue vp);

  /**
   * Given a resumption return value from a hook, parse and validate it based
   * on the given frame, and split the result into a ResumeMode and Value.
   */

  [[nodiscard]] bool prepareResumption(JSContext* cx, AbstractFramePtr frame,
                                       const jsbytecode* pc,
                                       ResumeMode& resumeMode,
                                       MutableHandleValue vp);

  /**
   * If there is a pending exception and a handler, call the handler with the
   * exception so that it can attempt to resolve the error.
   */

  [[nodiscard]] bool callUncaughtExceptionHandler(JSContext* cx,
                                                  MutableHandleValue vp);

  /**
   * If the context has a pending exception, report it to the current global.
   */

  void reportUncaughtException(JSContext* cx);

  /*
   * Call the uncaught exception handler if there is one, returning true
   * if it handled the error, or false otherwise.
   */

  [[nodiscard]] bool handleUncaughtException(JSContext* cx);

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

  static void traceObject(JSTracer* trc, JSObject* obj);

  void trace(JSTracer* trc);

  void traceForMovingGC(JSTracer* trc);
  void traceCrossCompartmentEdges(JSTracer* tracer);

 private:
  template <typename F>
  void forEachWeakMap(const F& f);

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

  [[nodiscard]] static bool getGarbageCollectionHook(JSContext* cx,
                                                     const CallArgs& args,
                                                     Debugger& dbg);
  [[nodiscard]] static bool setGarbageCollectionHook(JSContext* cx,
                                                     const CallArgs& args,
                                                     Debugger& dbg);

  static bool isCompilableUnit(JSContext* cx, unsigned argc, Value* vp);
  static bool recordReplayProcessKind(JSContext* cx, unsigned argc, Value* vp);
  static bool construct(JSContext* cx, unsigned argc, Value* vp);

  struct CallData;

  static const JSPropertySpec properties[];
  static const JSFunctionSpec methods[];
  static const JSPropertySpec static_properties[];
  static const JSFunctionSpec static_methods[];

  /**
   * Suspend the DebuggerFrame, clearing on-stack data but leaving it linked
   * with the AbstractGeneratorObject so it can be re-used later.
   */

  static void suspendGeneratorDebuggerFrames(JSContext* cx,
                                             AbstractFramePtr frame);

  /**
   * Terminate the DebuggerFrame, clearing all data associated with the frame
   * so that it cannot be used to introspect stack frame data.
   */

  static void terminateDebuggerFrames(JSContext* cx, AbstractFramePtr frame);

  /**
   * Terminate a given DebuggerFrame, removing all internal state and all
   * references to the frame from the Debugger itself. If the frame is being
   * terminated while 'frames' or 'generatorFrames' are being iterated, pass a
   * pointer to the iteration Enum to remove the entry and ensure that iteration
   * behaves properly.
   *
   * The AbstractFramePtr may be omited in a call so long as it is either
   * called again later with the correct 'frame', or the frame itself has never
   * had on-stack data or a 'frames' entry and has never had an onStep handler.
   */

  static void terminateDebuggerFrame(
      JS::GCContext* gcx, Debugger* dbg, DebuggerFrame* dbgFrame,
      AbstractFramePtr frame, FrameMap::Enum* maybeFramesEnum = nullptr,
      GeneratorWeakMap::Enum* maybeGeneratorFramesEnum = nullptr);

  static bool updateExecutionObservabilityOfFrames(
      JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
      IsObserving observing);
  static bool updateExecutionObservabilityOfScripts(
      JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
      IsObserving observing);
  static bool updateExecutionObservability(
      JSContext* cx, DebugAPI::ExecutionObservableSet& obs,
      IsObserving observing);

  template <typename FrameFn /* void (Debugger*, DebuggerFrame*) */>
  static void forEachOnStackDebuggerFrame(AbstractFramePtr frame,
                                          const JS::AutoRequireNoGC& nogc,
                                          FrameFn fn);
  template <typename FrameFn /* void (Debugger*, DebuggerFrame*) */>
  static void forEachOnStackOrSuspendedDebuggerFrame(
      JSContext* cx, AbstractFramePtr frame, const JS::AutoRequireNoGC& nogc,
      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<DebuggerFrame*, 0, SystemAllocPolicy>;
  [[nodiscard]] static bool getDebuggerFrames(
      AbstractFramePtr frame, MutableHandle<DebuggerFrameVector> frames);

 public:
  // Public for DebuggerScript::setBreakpoint.
  [[nodiscard]] 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 compiled Wasm
  // execution of its debuggees.
  IsObserving observesWasm() const;

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

  // Whether the Debugger instance needs to observe native call invocations.
  IsObserving observesNativeCalls() const;

  bool isExclusiveDebuggerOnEval() const;

 private:
  [[nodiscard]] static bool ensureExecutionObservabilityOfFrame(
      JSContext* cx, AbstractFramePtr frame);
  [[nodiscard]] static bool ensureExecutionObservabilityOfRealm(
      JSContext* cx, JS::Realm* realm);

  static bool hookObservesAllExecution(Hook which);

  [[nodiscard]] bool updateObservesAllExecutionOnDebuggees(
      JSContext* cx, IsObserving observing);
  [[nodiscard]] bool updateObservesCoverageOnDebuggees(JSContext* cx,
                                                       IsObserving observing);
  void updateObservesAsmJSOnDebuggees(IsObserving observing);
  void updateObservesWasmOnDebuggees(IsObserving observing);
  void updateObservesNativeCallOnDebuggees(IsObserving observing);

  JSObject* getHook(Hook hook) const;
  bool hasAnyLiveHooks() const;
  inline bool isHookCallAllowed(JSContext* cx) const;

  static void slowPathPromiseHook(JSContext* cx, Hook hook,
                                  Handle<PromiseObject*> promise);

  template <typename HookIsEnabledFun /* bool (Debugger*) */,
            typename FireHookFun /* void (Debugger*) */>
  static void dispatchQuietHook(JSContext* cx, HookIsEnabledFun hookIsEnabled,
                                FireHookFun fireHook);
  template <
      typename HookIsEnabledFun /* bool (Debugger*) */, typename FireHookFun /* bool (Debugger*, ResumeMode&, MutableHandleValue) */>
  [[nodiscard]] static bool dispatchResumptionHook(
      JSContext* cx, AbstractFramePtr frame, HookIsEnabledFun hookIsEnabled,
      FireHookFun fireHook);

  template <typename RunImpl /* bool () */>
  [[nodiscard]] bool enterDebuggerHook(JSContext* cx, RunImpl runImpl) {
    if (!isHookCallAllowed(cx)) {
      return true;
    }

    AutoRealm ar(cx, object);

    if (!runImpl()) {
      // We do not want errors within one hook to effect errors in other hooks,
      // so the only errors that we allow to propagate out of a debugger hook
      // are OOM errors and general terminations.
      if (!cx->isExceptionPending() || cx->isThrowingOutOfMemory()) {
        return false;
      }

      reportUncaughtException(cx);
    }
    MOZ_ASSERT(!cx->isExceptionPending());
    return true;
  }

  [[nodiscard]] bool fireDebuggerStatement(JSContext* cx,
                                           ResumeMode& resumeMode,
                                           MutableHandleValue vp);
  [[nodiscard]] bool fireExceptionUnwind(JSContext* cx, HandleValue exc,
                                         ResumeMode& resumeMode,
                                         MutableHandleValue vp);
  [[nodiscard]] bool fireEnterFrame(JSContext* cx, ResumeMode& resumeMode,
                                    MutableHandleValue vp);
  [[nodiscard]] bool fireNativeCall(JSContext* cx, const CallArgs& args,
                                    CallReason reason, ResumeMode& resumeMode,
                                    MutableHandleValue vp);
  [[nodiscard]] bool fireNewGlobalObject(JSContext* cx,
                                         Handle<GlobalObject*> global);
  [[nodiscard]] bool firePromiseHook(JSContext* cx, Hook hook,
                                     HandleObject promise);

  DebuggerScript* newVariantWrapper(JSContext* cx,
                                    Handle<DebuggerScriptReferent> referent) {
    return newDebuggerScript(cx, referent);
  }
  DebuggerSource* 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 ReferentType, typename Map>
  typename Map::WrapperType* wrapVariantReferent(
      JSContext* cx, Map& map,
      Handle<typename Map::WrapperType::ReferentVariant> referent);
  DebuggerScript* wrapVariantReferent(JSContext* cx,
                                      Handle<DebuggerScriptReferent> referent);
  DebuggerSource* wrapVariantReferent(JSContext* cx,
                                      Handle<DebuggerSourceReferent> referent);

  /*
   * Allocate and initialize a Debugger.Script instance whose referent is
   * |referent|.
   */

  DebuggerScript* newDebuggerScript(JSContext* cx,
                                    Handle<DebuggerScriptReferent> referent);

  /*
   * Allocate and initialize a Debugger.Source instance whose referent is
   * |referent|.
   */

  DebuggerSource* newDebuggerSource(JSContext* cx,
                                    Handle<DebuggerSourceReferent> referent);

  /*
   * Receive a "new script" event from the engine. A new script was compiled
   * or deserialized.
   */

  [[nodiscard]] bool fireNewScript(
      JSContext* cx, Handle<DebuggerScriptReferent> scriptReferent);

  /*
   * Receive a "garbage collection" event from the engine. A GC cycle with the
   * given data was recently completed.
   */

  [[nodiscard]] bool fireOnGarbageCollectionHook(
      JSContext* cx, const JS::dbg::GarbageCollectionEvent::Ptr& gcData);

  inline Breakpoint* firstBreakpoint() const;

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

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

  inline const js::HeapPtr<NativeObject*>& toJSObject() const;
  inline js::HeapPtr<NativeObject*>& toJSObjectRef();
  static inline Debugger* fromJSObject(const JSObject* obj);

#ifdef DEBUG
  static bool isChildJSObject(JSObject* obj);
#endif

  Zone* zone() const { return toJSObject()->zone(); }

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

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

#ifdef DEBUG
  static bool isDebuggerCrossCompartmentEdge(JSObject* obj,
                                             const js::gc::Cell* cell);
#endif

  static bool hasLiveHook(GlobalObject* global, Hook which);

  /*** 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 FrameIter& iter) const;
  bool observesScript(JSScript* script) const;
  bool observesWasm(wasm::Instance* instance) 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.
   */

  [[nodiscard]] bool wrapEnvironment(JSContext* cx, Handle<Env*> env,
                                     MutableHandleValue vp);
  [[nodiscard]] bool wrapEnvironment(
      JSContext* cx, Handle<Env*> env,
      MutableHandle<DebuggerEnvironment*> result);

  /*
   * Like cx->compartment()->wrap(cx, vp), but for the debugger realm.
   *
   * Preconditions: *vp is a value from a debuggee realm; 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 Compartment::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_MISSING_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 }.
   */

  [[nodiscard]] bool wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp);
  [[nodiscard]] bool wrapDebuggeeObject(JSContext* cx, HandleObject obj,
                                        MutableHandle<DebuggerObject*> result);
  [[nodiscard]] bool wrapNullableDebuggeeObject(
      JSContext* cx, HandleObject obj, MutableHandle<DebuggerObject*> result);

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

  [[nodiscard]] bool unwrapDebuggeeValue(JSContext* cx, MutableHandleValue vp);
  [[nodiscard]] bool unwrapDebuggeeObject(JSContext* cx,
                                          MutableHandleObject obj);
  [[nodiscard]] bool unwrapPropertyDescriptor(
      JSContext* cx, HandleObject obj, MutableHandle<PropertyDescriptor> desc);

  /*
   * Store the Debugger.Frame object for iter in *vp/result.
   *
   * If this Debugger does not already have a Frame object for the frame
   * `iter` points to, a new Frame object is created, and `iter`'s private
   * data is copied into it.
   */

  [[nodiscard]] bool getFrame(JSContext* cx, const FrameIter& iter,
                              MutableHandleValue vp);
  [[nodiscard]] bool getFrame(JSContext* cx,
                              MutableHandle<DebuggerFrame*> result);
  [[nodiscard]] bool getFrame(JSContext* cx, const FrameIter& iter,
                              MutableHandle<DebuggerFrame*> result);
  [[nodiscard]] bool getFrame(JSContext* cx,
                              Handle<AbstractGeneratorObject*> genObj,
                              MutableHandle<DebuggerFrame*> result);

  /*
   * Return the Debugger.Script object for |script|, or create a new one if
   * needed. The context |cx| must be in the debugger realm; |script| must be
   * a script in a debuggee realm.
   */

  DebuggerScript* wrapScript(JSContext* cx, Handle<BaseScript*> script);

  /*
   * Return the Debugger.Script object for |wasmInstance| (the toplevel
   * script), synthesizing a new one if needed. The context |cx| must be in
   * the debugger compartment; |wasmInstance| must be a WasmInstanceObject in
   * the debuggee realm.
   */

  DebuggerScript* wrapWasmScript(JSContext* cx,
                                 Handle<WasmInstanceObject*> wasmInstance);

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

  DebuggerSource* wrapSource(JSContext* cx,
                             js::Handle<ScriptSourceObject*> source);

  /*
   * Return the Debugger.Source object for |wasmInstance| (the entire module),
   * synthesizing a new one if needed. The context |cx| must be in the
   * debugger compartment; |wasmInstance| must be a WasmInstanceObject in the
   * debuggee realm.
   */

  DebuggerSource* wrapWasmSource(JSContext* cx,
                                 Handle<WasmInstanceObject*> wasmInstance);

  DebuggerDebuggeeLink* getDebuggeeLink();

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

// Specialize InternalBarrierMethods so we can have WeakHeapPtr<Debugger*>.
template <>
struct InternalBarrierMethods<Debugger*> {
  static bool isMarkable(Debugger* dbg) { return dbg->toJSObject(); }

  static void postBarrier(Debugger** vp, Debugger* prev, Debugger* next) {}

  static void readBarrier(Debugger* dbg) {
    InternalBarrierMethods<JSObject*>::readBarrier(dbg->toJSObject());
  }

#ifdef DEBUG
  static void assertThingIsNotGray(Debugger* dbg) {}
#endif
};

/**
 * This class exists for one specific reason. If a given Debugger object is in
 * a state where:
 *
 *   a) nothing in the system has a reference to the object
 *   b) the debugger is currently attached to a live debuggee
 *   c) the debugger has hooks like 'onEnterFrame'
 *
 * then we don't want the GC to delete the Debugger, because the system could
 * still call the hooks. This means we need to ensure that, whenever the global
 * gets marked, the Debugger will get marked as well. Critically, we _only_
 * want that to happen if the debugger has hooks. If it doesn't, then GCing
 * the debugger is the right think to do.
 *
 * Note that there are _other_ cases where the debugger may be held live, but
 * those are not addressed by this case.
 *
 * To accomplish this, we use a bit of roundabout link approach. Both the
 * Debugger and the debuggees can reach the link object:
 *
 *   Debugger  -> DebuggerDebuggeeLink  <- CCW <- Debuggee Global #1
 *      |                  |    ^   ^---<- CCW <- Debuggee Global #2
 *      \--<<-optional-<<--/     \------<- CCW <- Debuggee Global #3
 *
 * and critically, the Debugger is able to conditionally add or remove the link
 * going from the DebuggerDebuggeeLink _back_ to the Debugger. When this link
 * exists, the GC can trace all the way from the global to the Debugger,
 * meaning that any Debugger with this link will be kept alive as long as any
 * of its debuggees are alive.
 */

class DebuggerDebuggeeLink : public NativeObject {
 private:
  enum {
    DEBUGGER_LINK_SLOT,
    RESERVED_SLOTS,
  };

 public:
  static const JSClass class_;

  void setLinkSlot(Debugger& dbg);
  void clearLinkSlot();
};

/*
 * A Handler represents a Debugger API reflection object's handler function,
 * like a Debugger.Frame's onStep handler. These handler functions are called by
 * the Debugger API to notify the user of certain events. For each event type,
 * we define a separate subclass of Handler.
 *
 * When a reflection object accepts a Handler, it calls its 'hold' method; and
 * if the Handler is replaced by another, or the reflection object is finalized,
 * the reflection object calls the Handler's 'drop' method. The reflection
 * object does not otherwise manage the Handler's lifetime, say, by calling its
 * destructor or freeing its memory. A simple Handler implementation might have
 * an empty 'hold' method, and have its 'drop' method delete the Handler. A more
 * complex Handler might process many kinds of events, and thus inherit from
 * many Handler subclasses and be held by many reflection objects
 * simultaneously; a handler like this could use 'hold' and 'drop' to manage a
 * reference count.
 *
 * To support SpiderMonkey's memory use tracking, 'hold' and 'drop' also require
 * a pointer to the owning reflection object, so that the Holder implementation
 * can properly report changes in ownership to functions using the
 * js::gc::MemoryUse categories.
 */

struct Handler {
  virtual ~Handler() = default;

  /*
   * If this Handler is a reference to a callable JSObject, return that
   * JSObject. Otherwise, this method returns nullptr.
   *
   * The JavaScript getters for handler properties on reflection objects use
   * this method to obtain the callable the handler represents. When a Handler's
   * 'object' method returns nullptr, that handler is simply not visible to
   * JavaScript.
   */

  virtual JSObject* object() const = 0;

  /* Report that this Handler is now held by owner. See comment above. */
  virtual void hold(JSObject* owner) = 0;

  /* Report that this Handler is no longer held by owner. See comment above. */
  virtual void drop(JS::GCContext* gcx, JSObject* owner) = 0;

  /*
   * Trace the reference to the handler. This method will be called by the
   * reflection object holding this Handler whenever the former is traced.
   */

  virtual void trace(JSTracer* tracer) = 0;

  /* Allocation size in bytes for memory accounting purposes. */
  virtual size_t allocSize() const = 0;
};

class JSBreakpointSite;
class WasmBreakpointSite;

/**
 * Breakpoint GC rules:
 *
 * BreakpointSites and Breakpoints are owned by the code in which they are set.
 * Tracing a JSScript or WasmInstance traces all BreakpointSites set in it,
 * which traces all Breakpoints; and if the code is garbage collected, the
 * BreakpointSite and the Breakpoints set at it are freed as well. Doing so is
 * not observable to JS, since the handlers would never fire, and there is no
 * way to enumerate all breakpoints without specifying a specific script, in
 * which case it must not have been GC'd.
 *
 * Although BreakpointSites and Breakpoints are not GC things, they should be
 * treated as belonging to the code's compartment. This means that the
 * BreakpointSite concrete subclasses' pointers to the code are not
 * cross-compartment references, but a Breakpoint's pointers to its handler and
 * owning Debugger are cross-compartment references, and go through
 * cross-compartment wrappers.
 */


/**
 * A location in a JSScript or WasmInstance at which we have breakpoints. A
 * BreakpointSite owns a linked list of all the breakpoints set at its location.
 * In general, this list contains breakpoints set by multiple Debuggers in
 * various compartments.
 *
 * BreakpointSites are created only as needed, for locations at which
 * breakpoints are currently set. When the last breakpoint is removed from a
 * location, the BreakpointSite is removed as well.
 *
 * This is an abstract base class, with subclasses specialized for the different
 * sorts of code a breakpoint might be set in. JSBreakpointSite manages sites in
 * JSScripts, and WasmBreakpointSite manages sites in WasmInstances.
 */

class BreakpointSite {
  friend class DebugAPI;
  friend class Breakpoint;
  friend class Debugger;

 private:
  template <typename T>
  struct SiteLinkAccess {
    static mozilla::DoublyLinkedListElement<T>& Get(T* aThis) {
      return aThis->siteLink;
    }
  };

  // List of all js::Breakpoints at this instruction.
  using BreakpointList =
      mozilla::DoublyLinkedList<js::Breakpoint, SiteLinkAccess<js::Breakpoint>>;
  BreakpointList breakpoints;

 protected:
  BreakpointSite() = default;
  virtual ~BreakpointSite() = default;
  void finalize(JS::GCContext* gcx);
  virtual gc::Cell* owningCell() = 0;

 public:
  Breakpoint* firstBreakpoint() const;
  bool hasBreakpoint(Breakpoint* bp);

  bool isEmpty() const;
  virtual void trace(JSTracer* trc);
  virtual void remove(JS::GCContext* gcx) = 0;
  void destroyIfEmpty(JS::GCContext* gcx) {
    if (isEmpty()) {
      remove(gcx);
    }
  }
  virtual Realm* realm() const = 0;
};

/*
 * A breakpoint set at a given BreakpointSite, indicating the owning debugger
 * and the handler object. A Breakpoint is a member of two linked lists: its
 * owning debugger's list and its site's list.
 */

class Breakpoint {
  friend class DebugAPI;
  friend class Debugger;
  friend class BreakpointSite;

 public:
  /* Our owning debugger. */
  Debugger* const debugger;

  /**
   * A cross-compartment wrapper for our owning debugger's object, a CCW in the
   * code's compartment to the Debugger object in its own compartment. Holding
   * this lets the GC know about the effective cross-compartment reference from
   * the code to the debugger; see "Breakpoint GC Rules", above.
   *
   * This is almost redundant with the `debugger` field, except that we need
   * access to our owning `Debugger` regardless of the relative privilege levels
   * of debugger and debuggee, regardless of whether we're in the midst of a GC,
   * and so on - unwrapping is just too entangled.
   */

  const HeapPtr<JSObject*> wrappedDebugger;

  /* The site at which we're inserted. */
  BreakpointSite* const site;

 private:
  /**
   * The breakpoint handler object, via a cross-compartment wrapper in the
   * code's compartment.
   *
   * Although eventually we would like this to be a `js::Handler` instance, for
   * now it is just cross-compartment wrapper for the JS object supplied to
   * `setBreakpoint`, hopefully with a callable `hit` property.
   */

  const HeapPtr<JSObject*> handler;

  /**
   * Link elements for each list this breakpoint can be in.
   */

  mozilla::DoublyLinkedListElement<Breakpoint> debuggerLink;
  mozilla::DoublyLinkedListElement<Breakpoint> siteLink;

  void trace(JSTracer* trc);

 public:
  Breakpoint(Debugger* debugger, HandleObject wrappedDebugger,
             BreakpointSite* site, HandleObject handler);

  enum MayDestroySite { FalseTrue };

  /**
   * Unlink this breakpoint from its Debugger's and and BreakpointSite's lists,
   * and free its memory.
   *
   * This is the low-level primitive shared by breakpoint removal and script
   * finalization code. It is only concerned with cleaning up this Breakpoint;
   * it does not check for now-empty BreakpointSites, unneeded DebugScripts, or
   * the like.
   */

  void delete_(JS::GCContext* gcx);

  /**
   * Remove this breakpoint. Unlink it from its Debugger's and BreakpointSite's
   * lists, and if the BreakpointSite is now empty, clean that up and update JIT
   * code as necessary.
   */

  void remove(JS::GCContext* gcx);

  Breakpoint* nextInDebugger();
  Breakpoint* nextInSite();
  JSObject* getHandler() const { return handler; }
};

class JSBreakpointSite : public BreakpointSite {
 public:
  const HeapPtr<JSScript*> script;
  jsbytecode* const pc;

 public:
  JSBreakpointSite(JSScript* script, jsbytecode* pc);

  void trace(JSTracer* trc) override;
  void delete_(JS::GCContext* gcx);
  void remove(JS::GCContext* gcx) override;
  Realm* realm() const override;

 private:
  gc::Cell* owningCell() override;
};

class WasmBreakpointSite : public BreakpointSite {
 public:
  const HeapPtr<WasmInstanceObject*> instanceObject;
  uint32_t offset;

 public:
  WasmBreakpointSite(WasmInstanceObject* instanceObject, uint32_t offset);

  void trace(JSTracer* trc) override;
  void delete_(JS::GCContext* gcx);
  void remove(JS::GCContext* gcx) override;
  Realm* realm() const override;

 private:
  gc::Cell* owningCell() override;
};

Breakpoint* Debugger::firstBreakpoint() const {
  if (breakpoints.isEmpty()) {
    return nullptr;
  }
  return &(*breakpoints.begin());
}

const js::HeapPtr<NativeObject*>& Debugger::toJSObject() const {
  MOZ_ASSERT(object);
  return object;
}

js::HeapPtr<NativeObject*>& Debugger::toJSObjectRef() {
  MOZ_ASSERT(object);
  return object;
}

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

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

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

bool Debugger::observesGlobal(GlobalObject* global) const {
  WeakHeapPtr<GlobalObject*> debuggee(global);
  return debuggees.has(debuggee);
}

[[nodiscard]] bool ReportObjectRequired(JSContext* cx);

JSObject* IdVectorToArray(JSContext* cx, HandleIdVector ids);
bool IsInterpretedNonSelfHostedFunction(JSFunction* fun);
JSScript* GetOrCreateFunctionScript(JSContext* cx, HandleFunction fun);
ArrayObject* GetFunctionParameterNamesArray(JSContext* cx, HandleFunction fun);
bool ValueToIdentifier(JSContext* cx, HandleValue v, MutableHandleId id);
bool ValueToStableChars(JSContext* cx, const char* fnname, HandleValue value,
                        JS::AutoStableStringChars& stableChars);
bool ParseEvalOptions(JSContext* cx, HandleValue value, EvalOptions& options);

Result<Completion> DebuggerGenericEval(
    JSContext* cx, const mozilla::Range<const char16_t> chars,
    HandleObject bindings, const EvalOptions& options, Debugger* dbg,
    HandleObject envArg, FrameIter* iter);

bool ParseResumptionValue(JSContext* cx, HandleValue rval,
                          ResumeMode& resumeMode, MutableHandleValue vp);

#define JS_DEBUG_PSG(Name, Getter) \
  JS_PSG(Name, CallData::ToNative<&CallData::Getter>, 0)

#define JS_DEBUG_PSGS(Name, Getter, Setter)            \
  JS_PSGS(Name, CallData::ToNative<&CallData::Getter>, \
          CallData::ToNative<&CallData::Setter>, 0)

#define JS_DEBUG_FN(Name, Method, NumArgs) \
  JS_FN(Name, CallData::ToNative<&CallData::Method>, NumArgs, 0)

/* namespace js */

#endif /* debugger_Debugger_h */

Messung V0.5
C=91 H=97 G=93

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge