| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #ifndef vm_Debugger_h |
| #define vm_Debugger_h |
| |
| #include "mozilla/LinkedList.h" |
| |
| #include "jsapi.h" |
| #include "jsclist.h" |
| #include "jscntxt.h" |
| #include "jscompartment.h" |
| #include "jsweakmap.h" |
| |
| #include "gc/Barrier.h" |
| #include "js/HashTable.h" |
| #include "vm/GlobalObject.h" |
| |
| namespace js { |
| |
| /* |
| * A weakmap that supports the keys being in different compartments to the |
| * values, although all values must be in the same compartment. |
| * |
| * The Key and Value classes must support the compartment() method. |
| * |
| * The purpose of this is to allow the garbage collector to easily find edges |
| * from debugee object compartments to debugger compartments when calculating |
| * the compartment groups. Note that these edges are the inverse of the edges |
| * stored in the cross compartment map. |
| * |
| * The current implementation results in all debuggee object compartments being |
| * swept in the same group as the debugger. This is a conservative approach, |
| * and compartments may be unnecessarily grouped, however it results in a |
| * simpler and faster implementation. |
| */ |
| template <class Key, class Value> |
| class DebuggerWeakMap : private WeakMap<Key, Value, DefaultHasher<Key> > |
| { |
| private: |
| typedef HashMap<JS::Zone *, |
| uintptr_t, |
| DefaultHasher<JS::Zone *>, |
| RuntimeAllocPolicy> CountMap; |
| |
| CountMap zoneCounts; |
| |
| public: |
| typedef WeakMap<Key, Value, DefaultHasher<Key> > Base; |
| explicit DebuggerWeakMap(JSContext *cx) |
| : Base(cx), zoneCounts(cx) { } |
| |
| public: |
| /* Expose those parts of HashMap public interface that are used by Debugger methods. */ |
| |
| 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; |
| |
| bool init(uint32_t len = 16) { |
| return Base::init(len) && zoneCounts.init(); |
| } |
| |
| void clearWithoutCallingDestructors() { |
| Base::clearWithoutCallingDestructors(); |
| } |
| |
| AddPtr lookupForAdd(const Lookup &l) const { |
| return Base::lookupForAdd(l); |
| } |
| |
| template<typename KeyInput, typename ValueInput> |
| bool relookupOrAdd(AddPtr &p, const KeyInput &k, const ValueInput &v) { |
| JS_ASSERT(v->compartment() == Base::compartment); |
| if (!incZoneCount(k->zone())) |
| return false; |
| bool ok = Base::relookupOrAdd(p, k, v); |
| if (!ok) |
| decZoneCount(k->zone()); |
| return ok; |
| } |
| |
| Range all() const { |
| return Base::all(); |
| } |
| |
| void remove(const Lookup &l) { |
| Base::remove(l); |
| decZoneCount(l->zone()); |
| } |
| |
| public: |
| /* Expose WeakMap public interface*/ |
| void trace(JSTracer *tracer) { |
| Base::trace(tracer); |
| } |
| |
| public: |
| void markKeys(JSTracer *tracer) { |
| for (Range r = all(); !r.empty(); r.popFront()) { |
| Key key = r.front().key; |
| gc::Mark(tracer, &key, "cross-compartment WeakMap key"); |
| JS_ASSERT(key == r.front().key); |
| } |
| } |
| |
| bool hasKeyInZone(JS::Zone *zone) { |
| CountMap::Ptr p = zoneCounts.lookup(zone); |
| JS_ASSERT_IF(p, p->value > 0); |
| return p; |
| } |
| |
| private: |
| /* Override sweep method to also update our edge cache. */ |
| void sweep() { |
| for (Enum e(*static_cast<Base *>(this)); !e.empty(); e.popFront()) { |
| Key k(e.front().key); |
| if (gc::IsAboutToBeFinalized(&k)) { |
| e.removeFront(); |
| decZoneCount(k->zone()); |
| } |
| } |
| 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); |
| JS_ASSERT(p); |
| JS_ASSERT(p->value > 0); |
| --p->value; |
| if (p->value == 0) |
| zoneCounts.remove(zone); |
| } |
| }; |
| |
| class Debugger : private mozilla::LinkedListElement<Debugger> |
| { |
| friend class Breakpoint; |
| friend class mozilla::LinkedListElement<Debugger>; |
| friend JSBool (::JS_DefineDebuggerObject)(JSContext *cx, JSObject *obj); |
| |
| public: |
| enum Hook { |
| OnDebuggerStatement, |
| OnExceptionUnwind, |
| OnNewScript, |
| OnEnterFrame, |
| OnNewGlobalObject, |
| 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_PROTO_STOP, |
| JSSLOT_DEBUG_HOOK_START = JSSLOT_DEBUG_PROTO_STOP, |
| JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount, |
| JSSLOT_DEBUG_COUNT = JSSLOT_DEBUG_HOOK_STOP |
| }; |
| |
| private: |
| HeapPtrObject object; /* The Debugger object. Strong reference. */ |
| GlobalObjectSet debuggees; /* Debuggee globals. Cross-compartment weak references. */ |
| js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */ |
| bool enabled; |
| JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */ |
| |
| /* |
| * 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, |
| RelocatablePtrObject, |
| DefaultHasher<AbstractFramePtr>, |
| RuntimeAllocPolicy> FrameMap; |
| FrameMap frames; |
| |
| /* An ephemeral map from JSScript* to Debugger.Script instances. */ |
| typedef DebuggerWeakMap<EncapsulatedPtrScript, RelocatablePtrObject> ScriptWeakMap; |
| ScriptWeakMap scripts; |
| |
| /* The map from debuggee source script objects to their Debugger.Source instances. */ |
| typedef DebuggerWeakMap<EncapsulatedPtrObject, RelocatablePtrObject> SourceWeakMap; |
| SourceWeakMap sources; |
| |
| /* The map from debuggee objects to their Debugger.Object instances. */ |
| typedef DebuggerWeakMap<EncapsulatedPtrObject, RelocatablePtrObject> ObjectWeakMap; |
| ObjectWeakMap objects; |
| |
| /* The map from debuggee Envs to Debugger.Environment instances. */ |
| ObjectWeakMap environments; |
| |
| class FrameRange; |
| class ScriptQuery; |
| |
| bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj); |
| bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj, |
| AutoDebugModeGC &dmgc); |
| void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global, |
| GlobalObjectSet::Enum *compartmentEnum, |
| GlobalObjectSet::Enum *debugEnum); |
| void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global, |
| AutoDebugModeGC &dmgc, |
| GlobalObjectSet::Enum *compartmentEnum, |
| GlobalObjectSet::Enum *debugEnum); |
| |
| /* |
| * Cope with an error or exception in a debugger hook. |
| * |
| * If callHook is true, then call the uncaughtExceptionHook, if any. If, in |
| * addition, vp is given, then parse the value returned by |
| * uncaughtExceptionHook as a resumption value. |
| * |
| * If there is no uncaughtExceptionHook, or if it fails, report and clear |
| * the pending exception on ac.context and return JSTRAP_ERROR. |
| * |
| * This always calls ac.leave(); ac is a parameter because this method must |
| * do some things in the debugger compartment and some things in the |
| * debuggee compartment. |
| */ |
| JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment> &ac, bool callHook); |
| JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment> &ac, MutableHandleValue vp, bool callHook); |
| |
| JSTrapStatus handleUncaughtExceptionHelper(mozilla::Maybe<AutoCompartment> &ac, |
| MutableHandleValue *vp, bool callHook); |
| |
| /* |
| * Handle the result of a hook that is expected to return a resumption |
| * value <https://wiki.mozilla.org/Debugger#Resumption_Values>. This is called |
| * when we return from a debugging hook to debuggee code. The interpreter wants |
| * a (JSTrapStatus, Value) pair telling it how to proceed. |
| * |
| * Precondition: ac is entered. We are in the debugger compartment. |
| * |
| * Postcondition: This called ac.leave(). See handleUncaughtException. |
| * |
| * If ok is false, the hook failed. If an exception is pending in |
| * ac.context(), return handleUncaughtException(ac, vp, callhook). |
| * Otherwise just return JSTRAP_ERROR. |
| * |
| * If ok is true, there must be no exception pending in ac.context(). rv may be: |
| * undefined - Return JSTRAP_CONTINUE to continue execution normally. |
| * {return: value} or {throw: value} - Call unwrapDebuggeeValue to |
| * unwrap value. Store the result in *vp and return JSTRAP_RETURN |
| * or JSTRAP_THROW. The interpreter will force the current frame to |
| * return or throw an exception. |
| * null - Return JSTRAP_ERROR to terminate the debuggee with an |
| * uncatchable error. |
| * anything else - Make a new TypeError the pending exception and |
| * return handleUncaughtException(ac, vp, callHook). |
| */ |
| JSTrapStatus parseResumptionValue(mozilla::Maybe<AutoCompartment> &ac, bool ok, const Value &rv, |
| MutableHandleValue vp, bool callHook = true); |
| |
| GlobalObject *unwrapDebuggeeArgument(JSContext *cx, const Value &v); |
| |
| static void traceObject(JSTracer *trc, JSObject *obj); |
| void trace(JSTracer *trc); |
| static void finalize(FreeOp *fop, JSObject *obj); |
| void markKeysInCompartment(JSTracer *tracer); |
| |
| static Class jsclass; |
| |
| static Debugger *fromThisValue(JSContext *cx, const CallArgs &ca, const char *fnname); |
| static JSBool getEnabled(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool setEnabled(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool getHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which); |
| static JSBool setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which); |
| static JSBool getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool getOnNewScript(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool setOnNewScript(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool setOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool addDebuggee(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool removeDebuggee(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool hasDebuggee(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool getDebuggees(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool getNewestFrame(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool findScripts(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool findAllGlobals(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool wrap(JSContext *cx, unsigned argc, Value *vp); |
| static JSBool construct(JSContext *cx, unsigned argc, Value *vp); |
| static const JSPropertySpec properties[]; |
| static const JSFunctionSpec methods[]; |
| |
| JSObject *getHook(Hook hook) const; |
| bool hasAnyLiveHooks() const; |
| |
| static JSTrapStatus slowPathOnEnterFrame(JSContext *cx, AbstractFramePtr frame, |
| MutableHandleValue vp); |
| static bool slowPathOnLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok); |
| static void slowPathOnNewScript(JSContext *cx, HandleScript script, |
| GlobalObject *compileAndGoGlobal); |
| static bool slowPathOnNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global); |
| static JSTrapStatus dispatchHook(JSContext *cx, MutableHandleValue vp, Hook which); |
| |
| JSTrapStatus fireDebuggerStatement(JSContext *cx, MutableHandleValue vp); |
| JSTrapStatus fireExceptionUnwind(JSContext *cx, MutableHandleValue vp); |
| JSTrapStatus fireEnterFrame(JSContext *cx, MutableHandleValue vp); |
| JSTrapStatus fireNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global, 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); |
| |
| inline Breakpoint *firstBreakpoint() const; |
| |
| static inline Debugger *fromOnNewGlobalObjectWatchersLink(JSCList *link); |
| |
| public: |
| Debugger(JSContext *cx, JSObject *dbg); |
| ~Debugger(); |
| |
| bool init(JSContext *cx); |
| inline const js::HeapPtrObject &toJSObject() const; |
| inline js::HeapPtrObject &toJSObjectRef(); |
| static inline Debugger *fromJSObject(JSObject *obj); |
| static Debugger *fromChildJSObject(JSObject *obj); |
| |
| /*********************************** 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 markCrossCompartmentDebuggerObjectReferents(JSTracer *tracer); |
| static bool markAllIteratively(GCMarker *trc); |
| static void markAll(JSTracer *trc); |
| static void sweepAll(FreeOp *fop); |
| static void detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global, |
| GlobalObjectSet::Enum *compartmentEnum); |
| static void findCompartmentEdges(JS::Zone *v, gc::ComponentFinder<JS::Zone> &finder); |
| |
| static inline JSTrapStatus onEnterFrame(JSContext *cx, AbstractFramePtr frame, |
| MutableHandleValue vp); |
| static inline bool onLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok); |
| static inline JSTrapStatus onDebuggerStatement(JSContext *cx, MutableHandleValue vp); |
| static inline JSTrapStatus onExceptionUnwind(JSContext *cx, MutableHandleValue vp); |
| static inline void onNewScript(JSContext *cx, HandleScript script, |
| GlobalObject *compileAndGoGlobal); |
| static inline bool onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global); |
| static JSTrapStatus onTrap(JSContext *cx, MutableHandleValue vp); |
| static JSTrapStatus onSingleStep(JSContext *cx, MutableHandleValue vp); |
| static bool handleBaselineOsr(JSContext *cx, StackFrame *from, jit::BaselineFrame *to); |
| |
| /************************************* 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 observesScript(JSScript *script) const; |
| |
| /* |
| * If env is NULL, 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. |
| */ |
| 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); |
| |
| /* Store the Debugger.Frame object for iter in *vp. */ |
| bool getScriptFrame(JSContext *cx, const ScriptFrameIter &iter, MutableHandleValue 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, Value 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 &) MOZ_DELETE; |
| Debugger & operator=(const Debugger &) MOZ_DELETE; |
| }; |
| |
| class BreakpointSite { |
| friend class Breakpoint; |
| friend struct ::JSCompartment; |
| friend class ::JSScript; |
| friend class Debugger; |
| |
| public: |
| JSScript *script; |
| jsbytecode * const pc; |
| |
| private: |
| JSCList breakpoints; /* cyclic list of all js::Breakpoints at this instruction */ |
| size_t enabledCount; /* number of breakpoints in the list that are enabled */ |
| JSTrapHandler trapHandler; /* jsdbgapi trap state */ |
| HeapValue trapClosure; |
| |
| void recompile(FreeOp *fop); |
| |
| public: |
| BreakpointSite(JSScript *script, jsbytecode *pc); |
| Breakpoint *firstBreakpoint() const; |
| bool hasBreakpoint(Breakpoint *bp); |
| bool hasTrap() const { return !!trapHandler; } |
| |
| void inc(FreeOp *fop); |
| void dec(FreeOp *fop); |
| void setTrap(FreeOp *fop, JSTrapHandler handler, const Value &closure); |
| void clearTrap(FreeOp *fop, JSTrapHandler *handlerp = NULL, Value *closurep = NULL); |
| void destroyIfEmpty(FreeOp *fop); |
| }; |
| |
| /* |
| * Each Breakpoint is a member of two linked lists: its debugger's list and its |
| * site's list. |
| * |
| * GC rules: |
| * - script is live, breakpoint exists, and debugger is enabled |
| * ==> debugger is live |
| * - script is live, breakpoint exists, and debugger is live |
| * ==> retain the breakpoint and the handler object is live |
| * |
| * Debugger::markAllIteratively implements these two rules. It uses |
| * Debugger::hasAnyLiveHooks to check for rule 1. |
| * |
| * Nothing else causes a breakpoint to be retained, so if its script or |
| * debugger is collected, the breakpoint is destroyed during GC sweep phase, |
| * even if the debugger compartment isn't being GC'd. This is implemented in |
| * JSCompartment::sweepBreakpoints. |
| */ |
| class Breakpoint { |
| friend struct ::JSCompartment; |
| friend class Debugger; |
| |
| public: |
| Debugger * const debugger; |
| BreakpointSite * const site; |
| private: |
| /* |handler| is marked unconditionally during minor GC. */ |
| js::EncapsulatedPtrObject 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 EncapsulatedPtrObject &getHandler() const { return handler; } |
| EncapsulatedPtrObject &getHandlerRef() { return handler; } |
| }; |
| |
| Breakpoint * |
| Debugger::firstBreakpoint() const |
| { |
| if (JS_CLIST_IS_EMPTY(&breakpoints)) |
| return NULL; |
| return Breakpoint::fromDebuggerLinks(JS_NEXT_LINK(&breakpoints)); |
| } |
| |
| Debugger * |
| Debugger::fromOnNewGlobalObjectWatchersLink(JSCList *link) { |
| char *p = reinterpret_cast<char *>(link); |
| return reinterpret_cast<Debugger *>(p - offsetof(Debugger, onNewGlobalObjectWatchersLink)); |
| } |
| |
| const js::HeapPtrObject & |
| Debugger::toJSObject() const |
| { |
| JS_ASSERT(object); |
| return object; |
| } |
| |
| js::HeapPtrObject & |
| Debugger::toJSObjectRef() |
| { |
| JS_ASSERT(object); |
| return object; |
| } |
| |
| bool |
| Debugger::observesEnterFrame() const |
| { |
| return enabled && getHook(OnEnterFrame); |
| } |
| |
| bool |
| Debugger::observesNewScript() const |
| { |
| return enabled && getHook(OnNewScript); |
| } |
| |
| bool |
| Debugger::observesNewGlobalObject() const |
| { |
| return enabled && getHook(OnNewGlobalObject); |
| } |
| |
| bool |
| Debugger::observesGlobal(GlobalObject *global) const |
| { |
| return debuggees.has(global); |
| } |
| |
| JSTrapStatus |
| Debugger::onEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) |
| { |
| if (cx->compartment()->getDebuggees().empty()) |
| return JSTRAP_CONTINUE; |
| return slowPathOnEnterFrame(cx, frame, vp); |
| } |
| |
| JSTrapStatus |
| Debugger::onDebuggerStatement(JSContext *cx, MutableHandleValue vp) |
| { |
| return cx->compartment()->getDebuggees().empty() |
| ? JSTRAP_CONTINUE |
| : dispatchHook(cx, vp, OnDebuggerStatement); |
| } |
| |
| JSTrapStatus |
| Debugger::onExceptionUnwind(JSContext *cx, MutableHandleValue vp) |
| { |
| return cx->compartment()->getDebuggees().empty() |
| ? JSTRAP_CONTINUE |
| : dispatchHook(cx, vp, OnExceptionUnwind); |
| } |
| |
| void |
| Debugger::onNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal) |
| { |
| JS_ASSERT_IF(script->compileAndGo, compileAndGoGlobal); |
| JS_ASSERT_IF(!script->compileAndGo, !compileAndGoGlobal); |
| if (!script->compartment()->getDebuggees().empty()) |
| slowPathOnNewScript(cx, script, compileAndGoGlobal); |
| } |
| |
| bool |
| Debugger::onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global) |
| { |
| if (JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers)) |
| return true; |
| return Debugger::slowPathOnNewGlobalObject(cx, global); |
| } |
| |
| extern JSBool |
| EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame, |
| StableCharPtr chars, unsigned length, const char *filename, unsigned lineno, |
| MutableHandleValue rval); |
| |
| } |
| |
| #endif /* vm_Debugger_h */ |