mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-06-10 02:29:43 +00:00
1123 lines
44 KiB
C++
1123 lines
44 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef vm_Debugger_h
|
|
#define vm_Debugger_h
|
|
|
|
#include "mozilla/GuardObjects.h"
|
|
#include "mozilla/LinkedList.h"
|
|
#include "mozilla/Range.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/Vector.h"
|
|
|
|
#include "jsclist.h"
|
|
#include "jscntxt.h"
|
|
#include "jscompartment.h"
|
|
#include "jsweakmap.h"
|
|
#include "jswrapper.h"
|
|
|
|
#include "ds/TraceableFifo.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 LSprinter;
|
|
|
|
class Breakpoint;
|
|
class DebuggerMemory;
|
|
|
|
typedef HashSet<ReadBarrieredGlobalObject,
|
|
MovableCellHasher<ReadBarrieredGlobalObject>,
|
|
SystemAllocPolicy> WeakGlobalObjectSet;
|
|
|
|
/*
|
|
* A weakmap from GC thing keys to JSObject values that supports the keys being
|
|
* in different compartments to the values. All values must be in the same
|
|
* compartment.
|
|
*
|
|
* The purpose of this is to allow the garbage collector to easily find edges
|
|
* from debuggee object compartments to debugger compartments when calculating
|
|
* the compartment groups. Note that these edges are the inverse of the edges
|
|
* stored in the cross compartment map.
|
|
*
|
|
* The current implementation results in all debuggee object compartments being
|
|
* swept in the same group as the debugger. This is a conservative approach,
|
|
* and compartments may be unnecessarily grouped, however it results in a
|
|
* simpler and faster implementation.
|
|
*
|
|
* If InvisibleKeysOk is true, then the map can have keys in invisible-to-
|
|
* debugger compartments. If it is false, we assert that such entries are never
|
|
* created.
|
|
*
|
|
* Also note that keys in these weakmaps can be in any compartment, debuggee or
|
|
* not, because they cannot be deleted when a compartment is no longer a
|
|
* debuggee: the values need to maintain object identity across add/remove/add
|
|
* transitions.
|
|
*/
|
|
template <class UnbarrieredKey, bool InvisibleKeysOk=false>
|
|
class DebuggerWeakMap : private WeakMap<RelocatablePtr<UnbarrieredKey>, RelocatablePtrObject,
|
|
MovableCellHasher<RelocatablePtr<UnbarrieredKey>>>
|
|
{
|
|
private:
|
|
typedef RelocatablePtr<UnbarrieredKey> Key;
|
|
typedef RelocatablePtrObject Value;
|
|
|
|
typedef HashMap<JS::Zone*,
|
|
uintptr_t,
|
|
DefaultHasher<JS::Zone*>,
|
|
RuntimeAllocPolicy> CountMap;
|
|
|
|
CountMap zoneCounts;
|
|
JSCompartment* compartment;
|
|
|
|
public:
|
|
typedef WeakMap<Key, Value, MovableCellHasher<Key>> Base;
|
|
|
|
explicit DebuggerWeakMap(JSContext* cx)
|
|
: Base(cx),
|
|
zoneCounts(cx->runtime()),
|
|
compartment(cx->compartment())
|
|
{ }
|
|
|
|
public:
|
|
/* Expose those parts of HashMap public interface that are used by Debugger methods. */
|
|
|
|
typedef typename Base::Entry Entry;
|
|
typedef typename Base::Ptr Ptr;
|
|
typedef typename Base::AddPtr AddPtr;
|
|
typedef typename Base::Range Range;
|
|
typedef typename Base::Enum Enum;
|
|
typedef typename Base::Lookup Lookup;
|
|
|
|
/* Expose WeakMap public interface */
|
|
|
|
using Base::lookupForAdd;
|
|
using Base::all;
|
|
using Base::trace;
|
|
|
|
bool init(uint32_t len = 16) {
|
|
return Base::init(len) && zoneCounts.init();
|
|
}
|
|
|
|
template<typename KeyInput, typename ValueInput>
|
|
bool relookupOrAdd(AddPtr& p, const KeyInput& k, const ValueInput& v) {
|
|
MOZ_ASSERT(v->compartment() == this->compartment);
|
|
MOZ_ASSERT(!k->compartment()->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();
|
|
TraceEdge(tracer, &key, "Debugger WeakMap key");
|
|
if (key != e.front().key())
|
|
e.rekeyFront(key);
|
|
key.unsafeSet(nullptr);
|
|
}
|
|
}
|
|
|
|
bool hasKeyInZone(JS::Zone* zone) {
|
|
CountMap::Ptr p = zoneCounts.lookup(zone);
|
|
MOZ_ASSERT_IF(p.found(), p->value() > 0);
|
|
return p.found();
|
|
}
|
|
|
|
private:
|
|
/* Override sweep method to also update our edge cache. */
|
|
void sweep() {
|
|
for (Enum e(*static_cast<Base*>(this)); !e.empty(); e.popFront()) {
|
|
if (gc::IsAboutToBeFinalized(&e.front().mutableKey())) {
|
|
decZoneCount(e.front().key()->zone());
|
|
e.removeFront();
|
|
}
|
|
}
|
|
Base::assertEntriesNotAboutToBeFinalized();
|
|
}
|
|
|
|
bool incZoneCount(JS::Zone* zone) {
|
|
CountMap::Ptr p = zoneCounts.lookupWithDefault(zone, 0);
|
|
if (!p)
|
|
return false;
|
|
++p->value();
|
|
return true;
|
|
}
|
|
|
|
void decZoneCount(JS::Zone* zone) {
|
|
CountMap::Ptr p = zoneCounts.lookup(zone);
|
|
MOZ_ASSERT(p);
|
|
MOZ_ASSERT(p->value() > 0);
|
|
--p->value();
|
|
if (p->value() == 0)
|
|
zoneCounts.remove(zone);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* 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 class mozilla::LinkedList<Debugger>;
|
|
friend bool (::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj);
|
|
friend bool (::JS::dbg::IsDebugger)(JSObject&);
|
|
friend bool (::JS::dbg::GetDebuggeeGlobals)(JSContext*, JSObject&, AutoObjectVector&);
|
|
friend void JS::dbg::onNewPromise(JSContext* cx, HandleObject promise);
|
|
friend void JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise);
|
|
friend bool JS::dbg::FireOnGarbageCollectionHook(JSContext* cx,
|
|
JS::dbg::GarbageCollectionEvent::Ptr&& data);
|
|
|
|
public:
|
|
enum Hook {
|
|
OnDebuggerStatement,
|
|
OnExceptionUnwind,
|
|
OnNewScript,
|
|
OnEnterFrame,
|
|
OnNewGlobalObject,
|
|
OnNewPromise,
|
|
OnPromiseSettled,
|
|
OnGarbageCollection,
|
|
OnIonCompilation,
|
|
HookCount
|
|
};
|
|
enum {
|
|
JSSLOT_DEBUG_PROTO_START,
|
|
JSSLOT_DEBUG_FRAME_PROTO = JSSLOT_DEBUG_PROTO_START,
|
|
JSSLOT_DEBUG_ENV_PROTO,
|
|
JSSLOT_DEBUG_OBJECT_PROTO,
|
|
JSSLOT_DEBUG_SCRIPT_PROTO,
|
|
JSSLOT_DEBUG_SOURCE_PROTO,
|
|
JSSLOT_DEBUG_MEMORY_PROTO,
|
|
JSSLOT_DEBUG_PROTO_STOP,
|
|
JSSLOT_DEBUG_HOOK_START = JSSLOT_DEBUG_PROTO_STOP,
|
|
JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount,
|
|
JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP,
|
|
JSSLOT_DEBUG_COUNT
|
|
};
|
|
|
|
class ExecutionObservableSet
|
|
{
|
|
public:
|
|
typedef HashSet<Zone*>::Range ZoneRange;
|
|
|
|
virtual Zone* singleZone() const { return nullptr; }
|
|
virtual JSScript* singleScriptForZoneInvalidation() const { return nullptr; }
|
|
virtual const HashSet<Zone*>* zones() const { return nullptr; }
|
|
|
|
virtual bool shouldRecompileOrInvalidate(JSScript* script) const = 0;
|
|
virtual bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const = 0;
|
|
};
|
|
|
|
// This enum is converted to and compare with bool values; NotObserving
|
|
// must be 0 and Observing must be 1.
|
|
enum IsObserving {
|
|
NotObserving = 0,
|
|
Observing = 1
|
|
};
|
|
|
|
// Return true if the given compartment is a debuggee of this debugger,
|
|
// false otherwise.
|
|
bool isDebuggeeUnbarriered(const JSCompartment* compartment) const;
|
|
|
|
// Return true if this Debugger observed a debuggee that participated in the
|
|
// GC identified by the given GC number. Return false otherwise.
|
|
bool observedGC(uint64_t majorGCNumber) const {
|
|
return observedGCs.has(majorGCNumber);
|
|
}
|
|
|
|
// Notify this Debugger that one or more of its debuggees is participating
|
|
// in the GC identified by the given GC number.
|
|
bool debuggeeIsBeingCollected(uint64_t majorGCNumber) {
|
|
return observedGCs.put(majorGCNumber);
|
|
}
|
|
|
|
bool isTrackingTenurePromotions() const {
|
|
return trackingTenurePromotions;
|
|
}
|
|
|
|
bool isEnabled() const {
|
|
return enabled;
|
|
}
|
|
|
|
void logTenurePromotion(JSRuntime* rt, JSObject& obj, double when);
|
|
static SavedFrame* getObjectAllocationSite(JSObject& obj);
|
|
|
|
struct TenurePromotionsLogEntry : public JS::Traceable
|
|
{
|
|
TenurePromotionsLogEntry(JSRuntime* rt, JSObject& obj, double when);
|
|
|
|
const char* className;
|
|
double when;
|
|
RelocatablePtrObject frame;
|
|
size_t size;
|
|
|
|
static void trace(TenurePromotionsLogEntry* e, JSTracer* trc) {
|
|
if (e->frame)
|
|
TraceEdge(trc, &e->frame, "Debugger::TenurePromotionsLogEntry::frame");
|
|
}
|
|
};
|
|
|
|
struct AllocationsLogEntry : public JS::Traceable
|
|
{
|
|
AllocationsLogEntry(HandleObject frame, double when, const char* className,
|
|
HandleAtom ctorName, size_t size, bool inNursery)
|
|
: frame(frame),
|
|
when(when),
|
|
className(className),
|
|
ctorName(ctorName),
|
|
size(size),
|
|
inNursery(inNursery)
|
|
{
|
|
MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>());
|
|
};
|
|
|
|
RelocatablePtrObject frame;
|
|
double when;
|
|
const char* className;
|
|
RelocatablePtrAtom ctorName;
|
|
size_t size;
|
|
bool inNursery;
|
|
|
|
static void trace(AllocationsLogEntry* e, JSTracer* trc) {
|
|
if (e->frame)
|
|
TraceEdge(trc, &e->frame, "Debugger::AllocationsLogEntry::frame");
|
|
if (e->ctorName)
|
|
TraceEdge(trc, &e->ctorName, "Debugger::AllocationsLogEntry::ctorName");
|
|
}
|
|
};
|
|
|
|
private:
|
|
HeapPtrNativeObject object; /* The Debugger object. Strong reference. */
|
|
WeakGlobalObjectSet debuggees; /* Debuggee globals. Cross-compartment weak references. */
|
|
JS::ZoneSet debuggeeZones; /* Set of zones that we have debuggees in. */
|
|
js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */
|
|
bool enabled;
|
|
bool allowUnobservedAsmJS;
|
|
|
|
// Wether to enable code coverage on the Debuggee.
|
|
bool collectCoverageInfo;
|
|
|
|
JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */
|
|
|
|
// The set of GC numbers for which one or more of this Debugger's observed
|
|
// debuggees participated in.
|
|
js::HashSet<uint64_t> observedGCs;
|
|
|
|
using TenurePromotionsLog = js::TraceableFifo<TenurePromotionsLogEntry>;
|
|
TenurePromotionsLog tenurePromotionsLog;
|
|
bool trackingTenurePromotions;
|
|
size_t maxTenurePromotionsLogLength;
|
|
bool tenurePromotionsLogOverflowed;
|
|
|
|
using AllocationsLog = js::TraceableFifo<AllocationsLogEntry>;
|
|
|
|
AllocationsLog allocationsLog;
|
|
bool trackingAllocationSites;
|
|
double allocationSamplingProbability;
|
|
size_t maxAllocationsLogLength;
|
|
bool allocationsLogOverflowed;
|
|
|
|
static const size_t DEFAULT_MAX_LOG_LENGTH = 5000;
|
|
|
|
bool appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
|
|
double when);
|
|
|
|
/*
|
|
* Recompute the set of debuggee zones based on the set of debuggee globals.
|
|
*/
|
|
void recomputeDebuggeeZoneSet();
|
|
|
|
/*
|
|
* Return true if there is an existing object metadata callback for the
|
|
* given global's compartment that will prevent our instrumentation of
|
|
* allocations.
|
|
*/
|
|
static bool cannotTrackAllocations(const GlobalObject& global);
|
|
|
|
/*
|
|
* Return true if the given global is being observed by at least one
|
|
* Debugger that is tracking allocations.
|
|
*/
|
|
static bool isObservedByDebuggerTrackingAllocations(const GlobalObject& global);
|
|
|
|
/*
|
|
* Add allocations tracking for objects allocated within the given
|
|
* debuggee's compartment. The given debuggee global must be observed by at
|
|
* least one Debugger that is enabled and tracking allocations.
|
|
*/
|
|
static bool addAllocationsTracking(JSContext* cx, Handle<GlobalObject*> debuggee);
|
|
|
|
/*
|
|
* Remove allocations tracking for objects allocated within the given
|
|
* global's compartment. This is a no-op if there are still Debuggers
|
|
* observing this global and who are tracking allocations.
|
|
*/
|
|
static void removeAllocationsTracking(GlobalObject& global);
|
|
|
|
/*
|
|
* Add or remove allocations tracking for all debuggees.
|
|
*/
|
|
bool addAllocationsTrackingForAllDebuggees(JSContext* cx);
|
|
void removeAllocationsTrackingForAllDebuggees();
|
|
|
|
/*
|
|
* If this Debugger is enabled, and has a onNewGlobalObject handler, then
|
|
* this link is inserted into the circular list headed by
|
|
* JSRuntime::onNewGlobalObjectWatchers. Otherwise, this is set to a
|
|
* singleton cycle.
|
|
*/
|
|
JSCList onNewGlobalObjectWatchersLink;
|
|
|
|
/*
|
|
* Map from stack frames that are currently on the stack to Debugger.Frame
|
|
* instances.
|
|
*
|
|
* The keys are always live stack frames. We drop them from this map as
|
|
* soon as they leave the stack (see slowPathOnLeaveFrame) and in
|
|
* removeDebuggee.
|
|
*
|
|
* We don't trace the keys of this map (the frames are on the stack and
|
|
* thus necessarily live), but we do trace the values. It's like a WeakMap
|
|
* that way, but since stack frames are not gc-things, the implementation
|
|
* has to be different.
|
|
*/
|
|
typedef HashMap<AbstractFramePtr,
|
|
RelocatablePtrNativeObject,
|
|
DefaultHasher<AbstractFramePtr>,
|
|
RuntimeAllocPolicy> FrameMap;
|
|
FrameMap frames;
|
|
|
|
/* An ephemeral map from JSScript* to Debugger.Script instances. */
|
|
typedef DebuggerWeakMap<JSScript*> ScriptWeakMap;
|
|
ScriptWeakMap scripts;
|
|
|
|
/* The map from debuggee source script objects to their Debugger.Source instances. */
|
|
typedef DebuggerWeakMap<JSObject*, true> SourceWeakMap;
|
|
SourceWeakMap sources;
|
|
|
|
/* The map from debuggee objects to their Debugger.Object instances. */
|
|
typedef DebuggerWeakMap<JSObject*> ObjectWeakMap;
|
|
ObjectWeakMap objects;
|
|
|
|
/* The map from debuggee Envs to Debugger.Environment instances. */
|
|
ObjectWeakMap environments;
|
|
|
|
/*
|
|
* Keep track of tracelogger last drained identifiers to know if there are
|
|
* lost events.
|
|
*/
|
|
#ifdef NIGHTLY_BUILD
|
|
uint32_t traceLoggerLastDrainedSize;
|
|
uint32_t traceLoggerLastDrainedIteration;
|
|
#endif
|
|
uint32_t traceLoggerScriptedCallsLastDrainedSize;
|
|
uint32_t traceLoggerScriptedCallsLastDrainedIteration;
|
|
|
|
class FrameRange;
|
|
class ScriptQuery;
|
|
class ObjectQuery;
|
|
|
|
bool addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> obj);
|
|
void removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global,
|
|
WeakGlobalObjectSet::Enum* debugEnum);
|
|
|
|
/*
|
|
* Cope with an error or exception in a debugger hook.
|
|
*
|
|
* If callHook is true, then call the uncaughtExceptionHook, if any. If, in
|
|
* addition, vp is given, then parse the value returned by
|
|
* uncaughtExceptionHook as a resumption value.
|
|
*
|
|
* If there is no uncaughtExceptionHook, or if it fails, report and clear
|
|
* the pending exception on ac.context and return JSTRAP_ERROR.
|
|
*
|
|
* This always calls ac.leave(); ac is a parameter because this method must
|
|
* do some things in the debugger compartment and some things in the
|
|
* debuggee compartment.
|
|
*/
|
|
JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment>& ac, bool callHook);
|
|
JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment>& ac, MutableHandleValue vp, bool callHook);
|
|
|
|
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 bool getHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which);
|
|
static bool setHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which);
|
|
|
|
static Debugger* fromThisValue(JSContext* cx, const CallArgs& ca, const char* fnname);
|
|
static bool getEnabled(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setEnabled(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getOnExceptionUnwind(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setOnExceptionUnwind(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getOnNewScript(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setOnNewScript(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getOnEnterFrame(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setOnEnterFrame(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getOnNewGlobalObject(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setOnNewGlobalObject(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getOnNewPromise(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setOnNewPromise(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getMemory(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool getOnIonCompilation(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool setOnIonCompilation(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;
|
|
|
|
// Whether the Debugger instance needs to observe coverage of any JavaScript
|
|
// execution.
|
|
IsObserving observesCoverage() const;
|
|
|
|
private:
|
|
static bool ensureExecutionObservabilityOfFrame(JSContext* cx, AbstractFramePtr frame);
|
|
static bool ensureExecutionObservabilityOfCompartment(JSContext* cx, JSCompartment* comp);
|
|
|
|
static bool hookObservesAllExecution(Hook which);
|
|
|
|
bool updateObservesAllExecutionOnDebuggees(JSContext* cx, IsObserving observing);
|
|
bool updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing);
|
|
void updateObservesAsmJSOnDebuggees(IsObserving observing);
|
|
|
|
JSObject* getHook(Hook hook) const;
|
|
bool hasAnyLiveHooks(JSRuntime* rt) 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, HandleObject obj, HandleSavedFrame frame,
|
|
double when, GlobalObject::DebuggerVector& dbgs);
|
|
static void slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise);
|
|
static void slowPathOnIonCompilation(JSContext* cx, Handle<ScriptVector> scripts,
|
|
LSprinter& graph);
|
|
|
|
template <typename HookIsEnabledFun /* bool (Debugger*) */,
|
|
typename FireHookFun /* JSTrapStatus (Debugger*) */>
|
|
static JSTrapStatus dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled,
|
|
FireHookFun fireHook);
|
|
|
|
JSTrapStatus fireDebuggerStatement(JSContext* cx, MutableHandleValue vp);
|
|
JSTrapStatus fireExceptionUnwind(JSContext* cx, MutableHandleValue vp);
|
|
JSTrapStatus fireEnterFrame(JSContext* cx, AbstractFramePtr frame, MutableHandleValue vp);
|
|
JSTrapStatus fireNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global, MutableHandleValue vp);
|
|
JSTrapStatus firePromiseHook(JSContext* cx, Hook hook, HandleObject promise, MutableHandleValue vp);
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/*
|
|
* Receive a "garbage collection" event from the engine. A GC cycle with the
|
|
* given data was recently completed.
|
|
*/
|
|
void fireOnGarbageCollectionHook(JSContext* cx,
|
|
const JS::dbg::GarbageCollectionEvent::Ptr& gcData);
|
|
|
|
/*
|
|
* Receive a "Ion compilation" event from the engine. An Ion compilation with
|
|
* the given summary just got linked.
|
|
*/
|
|
JSTrapStatus fireOnIonCompilationHook(JSContext* cx, Handle<ScriptVector> scripts,
|
|
LSprinter& graph);
|
|
|
|
/*
|
|
* Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy
|
|
* its data if we need to make a new Debugger.Frame.
|
|
*/
|
|
bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
|
|
const ScriptFrameIter* maybeIter, MutableHandleValue vp);
|
|
|
|
inline Breakpoint* firstBreakpoint() const;
|
|
|
|
static inline Debugger* fromOnNewGlobalObjectWatchersLink(JSCList* link);
|
|
|
|
static bool replaceFrameGuts(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to,
|
|
ScriptFrameIter& iter);
|
|
|
|
public:
|
|
Debugger(JSContext* cx, NativeObject* dbg);
|
|
~Debugger();
|
|
|
|
bool init(JSContext* cx);
|
|
inline const js::HeapPtrNativeObject& toJSObject() const;
|
|
inline js::HeapPtrNativeObject& toJSObjectRef();
|
|
static inline Debugger* fromJSObject(const JSObject* obj);
|
|
static Debugger* fromChildJSObject(JSObject* obj);
|
|
|
|
bool hasMemory() const;
|
|
DebuggerMemory& memory() const;
|
|
|
|
WeakGlobalObjectSet::Range allDebuggees() const { return debuggees.all(); }
|
|
|
|
/*********************************** Methods for interaction with the GC. */
|
|
|
|
/*
|
|
* A Debugger object is live if:
|
|
* * the Debugger JSObject is live (Debugger::trace handles this case); OR
|
|
* * it is in the middle of dispatching an event (the event dispatching
|
|
* code roots it in this case); OR
|
|
* * it is enabled, and it is debugging at least one live compartment,
|
|
* and at least one of the following is true:
|
|
* - it has a debugger hook installed
|
|
* - it has a breakpoint set on a live script
|
|
* - it has a watchpoint set on a live object.
|
|
*
|
|
* Debugger::markAllIteratively handles the last case. If it finds any
|
|
* Debugger objects that are definitely live but not yet marked, it marks
|
|
* them and returns true. If not, it returns false.
|
|
*/
|
|
static void markIncomingCrossCompartmentEdges(JSTracer* tracer);
|
|
static bool markAllIteratively(GCMarker* trc);
|
|
static void markAll(JSTracer* trc);
|
|
static void sweepAll(FreeOp* fop);
|
|
static void detachAllDebuggersFromGlobal(FreeOp* fop, GlobalObject* global);
|
|
static void findZoneEdges(JS::Zone* v, gc::ComponentFinder<JS::Zone>& finder);
|
|
|
|
/*
|
|
* 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, JSObject* obj, HandleSavedFrame frame,
|
|
double when);
|
|
static inline bool observesIonCompilation(JSContext* cx);
|
|
static inline void onIonCompilation(JSContext* cx, Handle<ScriptVector> scripts,
|
|
LSprinter& graph);
|
|
static JSTrapStatus onTrap(JSContext* cx, MutableHandleValue vp);
|
|
static JSTrapStatus onSingleStep(JSContext* cx, MutableHandleValue vp);
|
|
static bool handleBaselineOsr(JSContext* cx, InterpreterFrame* from, jit::BaselineFrame* to);
|
|
static bool handleIonBailout(JSContext* cx, jit::RematerializedFrame* from, jit::BaselineFrame* to);
|
|
static void handleUnrecoverableIonBailoutError(JSContext* cx, jit::RematerializedFrame* frame);
|
|
static void propagateForcedReturn(JSContext* cx, AbstractFramePtr frame, HandleValue rval);
|
|
static bool hasLiveHook(GlobalObject* global, Hook which);
|
|
static bool inFrameMaps(AbstractFramePtr frame);
|
|
|
|
/************************************* Functions for use by Debugger.cpp. */
|
|
|
|
inline bool observesEnterFrame() const;
|
|
inline bool observesNewScript() const;
|
|
inline bool observesNewGlobalObject() const;
|
|
inline bool observesGlobal(GlobalObject* global) const;
|
|
bool observesFrame(AbstractFramePtr frame) const;
|
|
bool observesFrame(const ScriptFrameIter& iter) const;
|
|
bool observesScript(JSScript* script) const;
|
|
|
|
/*
|
|
* If env is nullptr, call vp->setNull() and return true. Otherwise, find
|
|
* or create a Debugger.Environment object for the given Env. On success,
|
|
* store the Environment object in *vp and return true.
|
|
*/
|
|
bool wrapEnvironment(JSContext* cx, Handle<Env*> env, MutableHandleValue vp);
|
|
|
|
/*
|
|
* Like cx->compartment()->wrap(cx, vp), but for the debugger compartment.
|
|
*
|
|
* Preconditions: *vp is a value from a debuggee compartment; cx is in the
|
|
* debugger's compartment.
|
|
*
|
|
* If *vp is an object, this produces a (new or existing) Debugger.Object
|
|
* wrapper for it. Otherwise this is the same as JSCompartment::wrap.
|
|
*
|
|
* If *vp is a magic JS_OPTIMIZED_OUT value, this produces a plain object
|
|
* of the form { optimizedOut: true }.
|
|
*
|
|
* If *vp is a magic JS_OPTIMIZED_ARGUMENTS value signifying missing
|
|
* arguments, this produces a plain object of the form { missingArguments:
|
|
* true }.
|
|
*
|
|
* If *vp is a magic JS_UNINITIALIZED_LEXICAL value signifying an
|
|
* unaccessible uninitialized binding, this produces a plain object of the
|
|
* form { uninitialized: true }.
|
|
*/
|
|
bool wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp);
|
|
|
|
/*
|
|
* Unwrap a Debug.Object, without rewrapping it for any particular debuggee
|
|
* compartment.
|
|
*
|
|
* Preconditions: cx is in the debugger compartment. *vp is a value in that
|
|
* compartment. (*vp should be a "debuggee value", meaning it is the
|
|
* debugger's reflection of a value in the debuggee.)
|
|
*
|
|
* If *vp is a Debugger.Object, store the referent in *vp. Otherwise, if *vp
|
|
* is an object, throw a TypeError, because it is not a debuggee
|
|
* value. Otherwise *vp is a primitive, so leave it alone.
|
|
*
|
|
* When passing values from the debuggee to the debugger:
|
|
* enter debugger compartment;
|
|
* call wrapDebuggeeValue; // compartment- and debugger-wrapping
|
|
*
|
|
* When passing values from the debugger to the debuggee:
|
|
* call unwrapDebuggeeValue; // debugger-unwrapping
|
|
* enter debuggee compartment;
|
|
* call cx->compartment()->wrap; // compartment-rewrapping
|
|
*
|
|
* (Extreme nerd sidebar: Unwrapping happens in two steps because there are
|
|
* two different kinds of symmetry at work: regardless of which direction
|
|
* we're going, we want any exceptions to be created and thrown in the
|
|
* debugger compartment--mirror symmetry. But compartment wrapping always
|
|
* happens in the target compartment--rotational symmetry.)
|
|
*/
|
|
bool unwrapDebuggeeValue(JSContext* cx, MutableHandleValue vp);
|
|
bool unwrapDebuggeeObject(JSContext* cx, MutableHandleObject obj);
|
|
bool unwrapPropertyDescriptor(JSContext* cx, HandleObject obj,
|
|
MutableHandle<PropertyDescriptor> desc);
|
|
|
|
/*
|
|
* Store the Debugger.Frame object for frame in *vp.
|
|
*
|
|
* Use this if you have already access to a frame pointer without having
|
|
* to incur the cost of walking the stack.
|
|
*/
|
|
bool getScriptFrame(JSContext* cx, AbstractFramePtr frame, MutableHandleValue vp) {
|
|
return getScriptFrameWithIter(cx, frame, nullptr, vp);
|
|
}
|
|
|
|
/*
|
|
* Store the Debugger.Frame object for iter in *vp. Eagerly copies a
|
|
* ScriptFrameIter::Data.
|
|
*
|
|
* Use this if you had to make a ScriptFrameIter to get the required
|
|
* frame, in which case the cost of walking the stack has already been
|
|
* paid.
|
|
*/
|
|
bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter, MutableHandleValue vp) {
|
|
return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp);
|
|
}
|
|
|
|
/*
|
|
* Set |*status| and |*value| to a (JSTrapStatus, Value) pair reflecting a
|
|
* standard SpiderMonkey call state: a boolean success value |ok|, a return
|
|
* value |rv|, and a context |cx| that may or may not have an exception set.
|
|
* If an exception was pending on |cx|, it is cleared (and |ok| is asserted
|
|
* to be false).
|
|
*/
|
|
static void resultToCompletion(JSContext* cx, bool ok, const Value& rv,
|
|
JSTrapStatus* status, MutableHandleValue value);
|
|
|
|
/*
|
|
* Set |*result| to a JavaScript completion value corresponding to |status|
|
|
* and |value|. |value| should be the return value or exception value, not
|
|
* wrapped as a debuggee value. |cx| must be in the debugger compartment.
|
|
*/
|
|
bool newCompletionValue(JSContext* cx, JSTrapStatus status, Value value,
|
|
MutableHandleValue result);
|
|
|
|
/*
|
|
* Precondition: we are in the debuggee compartment (ac is entered) and ok
|
|
* is true if the operation in the debuggee compartment succeeded, false on
|
|
* error or exception.
|
|
*
|
|
* Postcondition: we are in the debugger compartment, having called
|
|
* ac.leave() even if an error occurred.
|
|
*
|
|
* On success, a completion value is in vp and ac.context does not have a
|
|
* pending exception. (This ordinarily returns true even if the ok argument
|
|
* is false.)
|
|
*/
|
|
bool receiveCompletionValue(mozilla::Maybe<AutoCompartment>& ac, bool ok,
|
|
HandleValue val,
|
|
MutableHandleValue vp);
|
|
|
|
/*
|
|
* Return the Debugger.Script object for |script|, or create a new one if
|
|
* needed. The context |cx| must be in the debugger compartment; |script|
|
|
* must be a script in a debuggee compartment.
|
|
*/
|
|
JSObject* wrapScript(JSContext* cx, HandleScript script);
|
|
|
|
/*
|
|
* Return the Debugger.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;
|
|
};
|
|
|
|
template<>
|
|
struct DefaultGCPolicy<Debugger::TenurePromotionsLogEntry> {
|
|
static void trace(JSTracer* trc, Debugger::TenurePromotionsLogEntry* e, const char*) {
|
|
Debugger::TenurePromotionsLogEntry::trace(e, trc);
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct DefaultGCPolicy<Debugger::AllocationsLogEntry> {
|
|
static void trace(JSTracer* trc, Debugger::AllocationsLogEntry* e, const char*) {
|
|
Debugger::AllocationsLogEntry::trace(e, trc);
|
|
}
|
|
};
|
|
|
|
class BreakpointSite {
|
|
friend class Breakpoint;
|
|
friend struct ::JSCompartment;
|
|
friend class ::JSScript;
|
|
friend class Debugger;
|
|
|
|
public:
|
|
JSScript* script;
|
|
jsbytecode * const pc;
|
|
|
|
private:
|
|
JSCList breakpoints; /* cyclic list of all js::Breakpoints at this instruction */
|
|
size_t enabledCount; /* number of breakpoints in the list that are enabled */
|
|
|
|
void recompile(FreeOp* fop);
|
|
|
|
public:
|
|
BreakpointSite(JSScript* script, jsbytecode* pc);
|
|
Breakpoint* firstBreakpoint() const;
|
|
bool hasBreakpoint(Breakpoint* bp);
|
|
|
|
void inc(FreeOp* fop);
|
|
void dec(FreeOp* fop);
|
|
void destroyIfEmpty(FreeOp* fop);
|
|
};
|
|
|
|
/*
|
|
* Each Breakpoint is a member of two linked lists: its debugger's list and its
|
|
* site's list.
|
|
*
|
|
* GC rules:
|
|
* - script is live, breakpoint exists, and debugger is enabled
|
|
* ==> debugger is live
|
|
* - script is live, breakpoint exists, and debugger is live
|
|
* ==> retain the breakpoint and the handler object is live
|
|
*
|
|
* Debugger::markAllIteratively implements these two rules. It uses
|
|
* Debugger::hasAnyLiveHooks to check for rule 1.
|
|
*
|
|
* Nothing else causes a breakpoint to be retained, so if its script or
|
|
* debugger is collected, the breakpoint is destroyed during GC sweep phase,
|
|
* even if the debugger compartment isn't being GC'd. This is implemented in
|
|
* Zone::sweepBreakpoints.
|
|
*/
|
|
class Breakpoint {
|
|
friend struct ::JSCompartment;
|
|
friend class Debugger;
|
|
|
|
public:
|
|
Debugger * const debugger;
|
|
BreakpointSite * const site;
|
|
private:
|
|
/* |handler| is marked unconditionally during minor GC. */
|
|
js::PreBarrieredObject handler;
|
|
JSCList debuggerLinks;
|
|
JSCList siteLinks;
|
|
|
|
public:
|
|
static Breakpoint* fromDebuggerLinks(JSCList* links);
|
|
static Breakpoint* fromSiteLinks(JSCList* links);
|
|
Breakpoint(Debugger* debugger, BreakpointSite* site, JSObject* handler);
|
|
void destroy(FreeOp* fop);
|
|
Breakpoint* nextInDebugger();
|
|
Breakpoint* nextInSite();
|
|
const PreBarrieredObject& getHandler() const { return handler; }
|
|
PreBarrieredObject& getHandlerRef() { return handler; }
|
|
};
|
|
|
|
Breakpoint*
|
|
Debugger::firstBreakpoint() const
|
|
{
|
|
if (JS_CLIST_IS_EMPTY(&breakpoints))
|
|
return nullptr;
|
|
return Breakpoint::fromDebuggerLinks(JS_NEXT_LINK(&breakpoints));
|
|
}
|
|
|
|
/* static */ Debugger*
|
|
Debugger::fromOnNewGlobalObjectWatchersLink(JSCList* link) {
|
|
char* p = reinterpret_cast<char*>(link);
|
|
return reinterpret_cast<Debugger*>(p - offsetof(Debugger, onNewGlobalObjectWatchersLink));
|
|
}
|
|
|
|
const js::HeapPtrNativeObject&
|
|
Debugger::toJSObject() const
|
|
{
|
|
MOZ_ASSERT(object);
|
|
return object;
|
|
}
|
|
|
|
js::HeapPtrNativeObject&
|
|
Debugger::toJSObjectRef()
|
|
{
|
|
MOZ_ASSERT(object);
|
|
return object;
|
|
}
|
|
|
|
bool
|
|
Debugger::observesEnterFrame() const
|
|
{
|
|
return enabled && getHook(OnEnterFrame);
|
|
}
|
|
|
|
bool
|
|
Debugger::observesNewScript() const
|
|
{
|
|
return enabled && getHook(OnNewScript);
|
|
}
|
|
|
|
bool
|
|
Debugger::observesNewGlobalObject() const
|
|
{
|
|
return enabled && getHook(OnNewGlobalObject);
|
|
}
|
|
|
|
bool
|
|
Debugger::observesGlobal(GlobalObject* global) const
|
|
{
|
|
ReadBarriered<GlobalObject*> debuggee(global);
|
|
return debuggees.has(debuggee);
|
|
}
|
|
|
|
/* static */ void
|
|
Debugger::onNewScript(JSContext* cx, HandleScript script)
|
|
{
|
|
// We early return in slowPathOnNewScript for self-hosted scripts, so we can
|
|
// ignore those in our assertion here.
|
|
MOZ_ASSERT_IF(!script->compartment()->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, JSObject* obj, HandleSavedFrame frame, double when)
|
|
{
|
|
GlobalObject::DebuggerVector* dbgs = cx->global()->getDebuggers();
|
|
if (!dbgs || dbgs->empty())
|
|
return true;
|
|
RootedObject hobj(cx, obj);
|
|
return Debugger::slowPathOnLogAllocationSite(cx, hobj, frame, when, *dbgs);
|
|
}
|
|
|
|
bool ReportObjectRequired(JSContext* cx);
|
|
|
|
} /* namespace js */
|
|
|
|
namespace JS {
|
|
|
|
template <>
|
|
struct DeletePolicy<js::Debugger> : public js::GCManagedDeletePolicy<js::Debugger>
|
|
{};
|
|
|
|
} /* namespace JS */
|
|
|
|
#endif /* vm_Debugger_h */
|