michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef vm_Debugger_h michael@0: #define vm_Debugger_h michael@0: michael@0: #include "mozilla/LinkedList.h" michael@0: michael@0: #include "jsclist.h" michael@0: #include "jscntxt.h" michael@0: #include "jscompartment.h" michael@0: #include "jsweakmap.h" michael@0: michael@0: #include "gc/Barrier.h" michael@0: #include "js/HashTable.h" michael@0: #include "vm/GlobalObject.h" michael@0: michael@0: namespace js { michael@0: michael@0: class Breakpoint; michael@0: michael@0: /* michael@0: * A weakmap that supports the keys being in different compartments to the michael@0: * values, although all values must be in the same compartment. michael@0: * michael@0: * The Key and Value classes must support the compartment() method. michael@0: * michael@0: * The purpose of this is to allow the garbage collector to easily find edges michael@0: * from debugee object compartments to debugger compartments when calculating michael@0: * the compartment groups. Note that these edges are the inverse of the edges michael@0: * stored in the cross compartment map. michael@0: * michael@0: * The current implementation results in all debuggee object compartments being michael@0: * swept in the same group as the debugger. This is a conservative approach, michael@0: * and compartments may be unnecessarily grouped, however it results in a michael@0: * simpler and faster implementation. michael@0: * michael@0: * If InvisibleKeysOk is true, then the map can have keys in invisible-to- michael@0: * debugger compartments. If it is false, we assert that such entries are never michael@0: * created. michael@0: */ michael@0: template michael@0: class DebuggerWeakMap : private WeakMap > michael@0: { michael@0: private: michael@0: typedef HashMap, michael@0: RuntimeAllocPolicy> CountMap; michael@0: michael@0: CountMap zoneCounts; michael@0: michael@0: public: michael@0: typedef WeakMap > Base; michael@0: explicit DebuggerWeakMap(JSContext *cx) michael@0: : Base(cx), zoneCounts(cx->runtime()) { } michael@0: michael@0: public: michael@0: /* Expose those parts of HashMap public interface that are used by Debugger methods. */ michael@0: michael@0: typedef typename Base::Entry Entry; michael@0: typedef typename Base::Ptr Ptr; michael@0: typedef typename Base::AddPtr AddPtr; michael@0: typedef typename Base::Range Range; michael@0: typedef typename Base::Enum Enum; michael@0: typedef typename Base::Lookup Lookup; michael@0: michael@0: /* Expose WeakMap public interface */ michael@0: michael@0: using Base::clearWithoutCallingDestructors; michael@0: using Base::lookupForAdd; michael@0: using Base::all; michael@0: using Base::trace; michael@0: michael@0: bool init(uint32_t len = 16) { michael@0: return Base::init(len) && zoneCounts.init(); michael@0: } michael@0: michael@0: template michael@0: bool relookupOrAdd(AddPtr &p, const KeyInput &k, const ValueInput &v) { michael@0: JS_ASSERT(v->compartment() == Base::compartment); michael@0: JS_ASSERT(!k->compartment()->options_.mergeable()); michael@0: JS_ASSERT_IF(!InvisibleKeysOk, !k->compartment()->options_.invisibleToDebugger()); michael@0: JS_ASSERT(!Base::has(k)); michael@0: if (!incZoneCount(k->zone())) michael@0: return false; michael@0: bool ok = Base::relookupOrAdd(p, k, v); michael@0: if (!ok) michael@0: decZoneCount(k->zone()); michael@0: return ok; michael@0: } michael@0: michael@0: void remove(const Lookup &l) { michael@0: JS_ASSERT(Base::has(l)); michael@0: Base::remove(l); michael@0: decZoneCount(l->zone()); michael@0: } michael@0: michael@0: public: michael@0: void markKeys(JSTracer *tracer) { michael@0: for (Enum e(*static_cast(this)); !e.empty(); e.popFront()) { michael@0: Key key = e.front().key(); michael@0: gc::Mark(tracer, &key, "Debugger WeakMap key"); michael@0: if (key != e.front().key()) michael@0: e.rekeyFront(key); michael@0: key.unsafeSet(nullptr); michael@0: } michael@0: } michael@0: michael@0: bool hasKeyInZone(JS::Zone *zone) { michael@0: CountMap::Ptr p = zoneCounts.lookup(zone); michael@0: JS_ASSERT_IF(p, p->value() > 0); michael@0: return p; michael@0: } michael@0: michael@0: private: michael@0: /* Override sweep method to also update our edge cache. */ michael@0: void sweep() { michael@0: for (Enum e(*static_cast(this)); !e.empty(); e.popFront()) { michael@0: Key k(e.front().key()); michael@0: if (gc::IsAboutToBeFinalized(&k)) { michael@0: e.removeFront(); michael@0: decZoneCount(k->zone()); michael@0: } michael@0: } michael@0: Base::assertEntriesNotAboutToBeFinalized(); michael@0: } michael@0: michael@0: bool incZoneCount(JS::Zone *zone) { michael@0: CountMap::Ptr p = zoneCounts.lookupWithDefault(zone, 0); michael@0: if (!p) michael@0: return false; michael@0: ++p->value(); michael@0: return true; michael@0: } michael@0: michael@0: void decZoneCount(JS::Zone *zone) { michael@0: CountMap::Ptr p = zoneCounts.lookup(zone); michael@0: JS_ASSERT(p); michael@0: JS_ASSERT(p->value() > 0); michael@0: --p->value(); michael@0: if (p->value() == 0) michael@0: zoneCounts.remove(zone); michael@0: } michael@0: }; michael@0: michael@0: /* michael@0: * Env is the type of what ES5 calls "lexical environments" (runtime michael@0: * activations of lexical scopes). This is currently just JSObject, and is michael@0: * implemented by Call, Block, With, and DeclEnv objects, among others--but michael@0: * environments and objects are really two different concepts. michael@0: */ michael@0: typedef JSObject Env; michael@0: michael@0: class Debugger : private mozilla::LinkedListElement michael@0: { michael@0: friend class Breakpoint; michael@0: friend class mozilla::LinkedListElement; michael@0: friend bool (::JS_DefineDebuggerObject)(JSContext *cx, JS::HandleObject obj); michael@0: michael@0: public: michael@0: enum Hook { michael@0: OnDebuggerStatement, michael@0: OnExceptionUnwind, michael@0: OnNewScript, michael@0: OnEnterFrame, michael@0: OnNewGlobalObject, michael@0: HookCount michael@0: }; michael@0: enum { michael@0: JSSLOT_DEBUG_PROTO_START, michael@0: JSSLOT_DEBUG_FRAME_PROTO = JSSLOT_DEBUG_PROTO_START, michael@0: JSSLOT_DEBUG_ENV_PROTO, michael@0: JSSLOT_DEBUG_OBJECT_PROTO, michael@0: JSSLOT_DEBUG_SCRIPT_PROTO, michael@0: JSSLOT_DEBUG_SOURCE_PROTO, michael@0: JSSLOT_DEBUG_MEMORY_PROTO, michael@0: JSSLOT_DEBUG_PROTO_STOP, michael@0: JSSLOT_DEBUG_HOOK_START = JSSLOT_DEBUG_PROTO_STOP, michael@0: JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount, michael@0: JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP, michael@0: JSSLOT_DEBUG_COUNT michael@0: }; michael@0: private: michael@0: HeapPtrObject object; /* The Debugger object. Strong reference. */ michael@0: GlobalObjectSet debuggees; /* Debuggee globals. Cross-compartment weak references. */ michael@0: js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */ michael@0: bool enabled; michael@0: JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */ michael@0: michael@0: /* michael@0: * If this Debugger is enabled, and has a onNewGlobalObject handler, then michael@0: * this link is inserted into the circular list headed by michael@0: * JSRuntime::onNewGlobalObjectWatchers. Otherwise, this is set to a michael@0: * singleton cycle. michael@0: */ michael@0: JSCList onNewGlobalObjectWatchersLink; michael@0: michael@0: /* michael@0: * Map from stack frames that are currently on the stack to Debugger.Frame michael@0: * instances. michael@0: * michael@0: * The keys are always live stack frames. We drop them from this map as michael@0: * soon as they leave the stack (see slowPathOnLeaveFrame) and in michael@0: * removeDebuggee. michael@0: * michael@0: * We don't trace the keys of this map (the frames are on the stack and michael@0: * thus necessarily live), but we do trace the values. It's like a WeakMap michael@0: * that way, but since stack frames are not gc-things, the implementation michael@0: * has to be different. michael@0: */ michael@0: typedef HashMap, michael@0: RuntimeAllocPolicy> FrameMap; michael@0: FrameMap frames; michael@0: michael@0: /* An ephemeral map from JSScript* to Debugger.Script instances. */ michael@0: typedef DebuggerWeakMap ScriptWeakMap; michael@0: ScriptWeakMap scripts; michael@0: michael@0: /* The map from debuggee source script objects to their Debugger.Source instances. */ michael@0: typedef DebuggerWeakMap SourceWeakMap; michael@0: SourceWeakMap sources; michael@0: michael@0: /* The map from debuggee objects to their Debugger.Object instances. */ michael@0: typedef DebuggerWeakMap ObjectWeakMap; michael@0: ObjectWeakMap objects; michael@0: michael@0: /* The map from debuggee Envs to Debugger.Environment instances. */ michael@0: ObjectWeakMap environments; michael@0: michael@0: class FrameRange; michael@0: class ScriptQuery; michael@0: michael@0: bool addDebuggeeGlobal(JSContext *cx, Handle obj); michael@0: bool addDebuggeeGlobal(JSContext *cx, Handle obj, michael@0: AutoDebugModeInvalidation &invalidate); michael@0: void cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global, michael@0: AutoDebugModeInvalidation &invalidate, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnu); michael@0: bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnum); michael@0: bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global, michael@0: AutoDebugModeInvalidation &invalidate, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnum); michael@0: void removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnum); michael@0: void removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global, michael@0: AutoDebugModeInvalidation &invalidate, michael@0: GlobalObjectSet::Enum *compartmentEnum, michael@0: GlobalObjectSet::Enum *debugEnum); michael@0: michael@0: /* michael@0: * Cope with an error or exception in a debugger hook. michael@0: * michael@0: * If callHook is true, then call the uncaughtExceptionHook, if any. If, in michael@0: * addition, vp is given, then parse the value returned by michael@0: * uncaughtExceptionHook as a resumption value. michael@0: * michael@0: * If there is no uncaughtExceptionHook, or if it fails, report and clear michael@0: * the pending exception on ac.context and return JSTRAP_ERROR. michael@0: * michael@0: * This always calls ac.leave(); ac is a parameter because this method must michael@0: * do some things in the debugger compartment and some things in the michael@0: * debuggee compartment. michael@0: */ michael@0: JSTrapStatus handleUncaughtException(mozilla::Maybe &ac, bool callHook); michael@0: JSTrapStatus handleUncaughtException(mozilla::Maybe &ac, MutableHandleValue vp, bool callHook); michael@0: michael@0: JSTrapStatus handleUncaughtExceptionHelper(mozilla::Maybe &ac, michael@0: MutableHandleValue *vp, bool callHook); michael@0: michael@0: /* michael@0: * Handle the result of a hook that is expected to return a resumption michael@0: * value . This is called michael@0: * when we return from a debugging hook to debuggee code. The interpreter wants michael@0: * a (JSTrapStatus, Value) pair telling it how to proceed. michael@0: * michael@0: * Precondition: ac is entered. We are in the debugger compartment. michael@0: * michael@0: * Postcondition: This called ac.leave(). See handleUncaughtException. michael@0: * michael@0: * If ok is false, the hook failed. If an exception is pending in michael@0: * ac.context(), return handleUncaughtException(ac, vp, callhook). michael@0: * Otherwise just return JSTRAP_ERROR. michael@0: * michael@0: * If ok is true, there must be no exception pending in ac.context(). rv may be: michael@0: * undefined - Return JSTRAP_CONTINUE to continue execution normally. michael@0: * {return: value} or {throw: value} - Call unwrapDebuggeeValue to michael@0: * unwrap value. Store the result in *vp and return JSTRAP_RETURN michael@0: * or JSTRAP_THROW. The interpreter will force the current frame to michael@0: * return or throw an exception. michael@0: * null - Return JSTRAP_ERROR to terminate the debuggee with an michael@0: * uncatchable error. michael@0: * anything else - Make a new TypeError the pending exception and michael@0: * return handleUncaughtException(ac, vp, callHook). michael@0: */ michael@0: JSTrapStatus parseResumptionValue(mozilla::Maybe &ac, bool ok, const Value &rv, michael@0: MutableHandleValue vp, bool callHook = true); michael@0: michael@0: GlobalObject *unwrapDebuggeeArgument(JSContext *cx, const Value &v); michael@0: michael@0: static void traceObject(JSTracer *trc, JSObject *obj); michael@0: void trace(JSTracer *trc); michael@0: static void finalize(FreeOp *fop, JSObject *obj); michael@0: void markKeysInCompartment(JSTracer *tracer); michael@0: michael@0: static const Class jsclass; michael@0: michael@0: static Debugger *fromThisValue(JSContext *cx, const CallArgs &ca, const char *fnname); michael@0: static bool getEnabled(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool setEnabled(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool getHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which); michael@0: static bool setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which); michael@0: static bool getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool getOnNewScript(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool setOnNewScript(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool setOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool getMemory(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool addDebuggee(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool removeDebuggee(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool hasDebuggee(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool getDebuggees(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool getNewestFrame(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool findScripts(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool findAllGlobals(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp); michael@0: static bool construct(JSContext *cx, unsigned argc, Value *vp); michael@0: static const JSPropertySpec properties[]; michael@0: static const JSFunctionSpec methods[]; michael@0: michael@0: JSObject *getHook(Hook hook) const; michael@0: bool hasAnyLiveHooks() const; michael@0: michael@0: static JSTrapStatus slowPathOnEnterFrame(JSContext *cx, AbstractFramePtr frame, michael@0: MutableHandleValue vp); michael@0: static bool slowPathOnLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok); michael@0: static void slowPathOnNewScript(JSContext *cx, HandleScript script, michael@0: GlobalObject *compileAndGoGlobal); michael@0: static void slowPathOnNewGlobalObject(JSContext *cx, Handle global); michael@0: static JSTrapStatus dispatchHook(JSContext *cx, MutableHandleValue vp, Hook which); michael@0: michael@0: JSTrapStatus fireDebuggerStatement(JSContext *cx, MutableHandleValue vp); michael@0: JSTrapStatus fireExceptionUnwind(JSContext *cx, MutableHandleValue vp); michael@0: JSTrapStatus fireEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp); michael@0: JSTrapStatus fireNewGlobalObject(JSContext *cx, Handle global, MutableHandleValue vp); michael@0: michael@0: /* michael@0: * Allocate and initialize a Debugger.Script instance whose referent is michael@0: * |script|. michael@0: */ michael@0: JSObject *newDebuggerScript(JSContext *cx, HandleScript script); michael@0: michael@0: /* michael@0: * Allocate and initialize a Debugger.Source instance whose referent is michael@0: * |source|. michael@0: */ michael@0: JSObject *newDebuggerSource(JSContext *cx, js::HandleScriptSource source); michael@0: michael@0: /* michael@0: * Receive a "new script" event from the engine. A new script was compiled michael@0: * or deserialized. michael@0: */ michael@0: void fireNewScript(JSContext *cx, HandleScript script); michael@0: michael@0: /* michael@0: * Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy michael@0: * its data if we need to make a new Debugger.Frame. michael@0: */ michael@0: bool getScriptFrameWithIter(JSContext *cx, AbstractFramePtr frame, michael@0: const ScriptFrameIter *maybeIter, MutableHandleValue vp); michael@0: michael@0: inline Breakpoint *firstBreakpoint() const; michael@0: michael@0: static inline Debugger *fromOnNewGlobalObjectWatchersLink(JSCList *link); michael@0: michael@0: static bool replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to, michael@0: ScriptFrameIter &iter); michael@0: michael@0: public: michael@0: Debugger(JSContext *cx, JSObject *dbg); michael@0: ~Debugger(); michael@0: michael@0: bool init(JSContext *cx); michael@0: inline const js::HeapPtrObject &toJSObject() const; michael@0: inline js::HeapPtrObject &toJSObjectRef(); michael@0: static inline Debugger *fromJSObject(JSObject *obj); michael@0: static Debugger *fromChildJSObject(JSObject *obj); michael@0: michael@0: /*********************************** Methods for interaction with the GC. */ michael@0: michael@0: /* michael@0: * A Debugger object is live if: michael@0: * * the Debugger JSObject is live (Debugger::trace handles this case); OR michael@0: * * it is in the middle of dispatching an event (the event dispatching michael@0: * code roots it in this case); OR michael@0: * * it is enabled, and it is debugging at least one live compartment, michael@0: * and at least one of the following is true: michael@0: * - it has a debugger hook installed michael@0: * - it has a breakpoint set on a live script michael@0: * - it has a watchpoint set on a live object. michael@0: * michael@0: * Debugger::markAllIteratively handles the last case. If it finds any michael@0: * Debugger objects that are definitely live but not yet marked, it marks michael@0: * them and returns true. If not, it returns false. michael@0: */ michael@0: static void markCrossCompartmentDebuggerObjectReferents(JSTracer *tracer); michael@0: static bool markAllIteratively(GCMarker *trc); michael@0: static void markAll(JSTracer *trc); michael@0: static void sweepAll(FreeOp *fop); michael@0: static void detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global, michael@0: GlobalObjectSet::Enum *compartmentEnum); michael@0: static void findCompartmentEdges(JS::Zone *v, gc::ComponentFinder &finder); michael@0: michael@0: static inline JSTrapStatus onEnterFrame(JSContext *cx, AbstractFramePtr frame, michael@0: MutableHandleValue vp); michael@0: static inline bool onLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok); michael@0: static inline JSTrapStatus onDebuggerStatement(JSContext *cx, MutableHandleValue vp); michael@0: static inline JSTrapStatus onExceptionUnwind(JSContext *cx, MutableHandleValue vp); michael@0: static inline void onNewScript(JSContext *cx, HandleScript script, michael@0: GlobalObject *compileAndGoGlobal); michael@0: static inline void onNewGlobalObject(JSContext *cx, Handle global); michael@0: static JSTrapStatus onTrap(JSContext *cx, MutableHandleValue vp); michael@0: static JSTrapStatus onSingleStep(JSContext *cx, MutableHandleValue vp); michael@0: static bool handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to); michael@0: static bool handleIonBailout(JSContext *cx, jit::RematerializedFrame *from, jit::BaselineFrame *to); michael@0: michael@0: /************************************* Functions for use by Debugger.cpp. */ michael@0: michael@0: inline bool observesEnterFrame() const; michael@0: inline bool observesNewScript() const; michael@0: inline bool observesNewGlobalObject() const; michael@0: inline bool observesGlobal(GlobalObject *global) const; michael@0: bool observesFrame(AbstractFramePtr frame) const; michael@0: bool observesFrame(const ScriptFrameIter &iter) const; michael@0: bool observesScript(JSScript *script) const; michael@0: michael@0: /* michael@0: * If env is nullptr, call vp->setNull() and return true. Otherwise, find michael@0: * or create a Debugger.Environment object for the given Env. On success, michael@0: * store the Environment object in *vp and return true. michael@0: */ michael@0: bool wrapEnvironment(JSContext *cx, Handle env, MutableHandleValue vp); michael@0: michael@0: /* michael@0: * Like cx->compartment()->wrap(cx, vp), but for the debugger compartment. michael@0: * michael@0: * Preconditions: *vp is a value from a debuggee compartment; cx is in the michael@0: * debugger's compartment. michael@0: * michael@0: * If *vp is an object, this produces a (new or existing) Debugger.Object michael@0: * wrapper for it. Otherwise this is the same as JSCompartment::wrap. michael@0: * michael@0: * If *vp is a magic JS_OPTIMIZED_OUT value, this produces a plain object michael@0: * of the form { optimizedOut: true }. michael@0: * michael@0: * If *vp is a magic JS_OPTIMIZED_ARGUMENTS value signifying missing michael@0: * arguments, this produces a plain object of the form { missingArguments: michael@0: * true }. michael@0: */ michael@0: bool wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp); michael@0: michael@0: /* michael@0: * Unwrap a Debug.Object, without rewrapping it for any particular debuggee michael@0: * compartment. michael@0: * michael@0: * Preconditions: cx is in the debugger compartment. *vp is a value in that michael@0: * compartment. (*vp should be a "debuggee value", meaning it is the michael@0: * debugger's reflection of a value in the debuggee.) michael@0: * michael@0: * If *vp is a Debugger.Object, store the referent in *vp. Otherwise, if *vp michael@0: * is an object, throw a TypeError, because it is not a debuggee michael@0: * value. Otherwise *vp is a primitive, so leave it alone. michael@0: * michael@0: * When passing values from the debuggee to the debugger: michael@0: * enter debugger compartment; michael@0: * call wrapDebuggeeValue; // compartment- and debugger-wrapping michael@0: * michael@0: * When passing values from the debugger to the debuggee: michael@0: * call unwrapDebuggeeValue; // debugger-unwrapping michael@0: * enter debuggee compartment; michael@0: * call cx->compartment()->wrap; // compartment-rewrapping michael@0: * michael@0: * (Extreme nerd sidebar: Unwrapping happens in two steps because there are michael@0: * two different kinds of symmetry at work: regardless of which direction michael@0: * we're going, we want any exceptions to be created and thrown in the michael@0: * debugger compartment--mirror symmetry. But compartment wrapping always michael@0: * happens in the target compartment--rotational symmetry.) michael@0: */ michael@0: bool unwrapDebuggeeValue(JSContext *cx, MutableHandleValue vp); michael@0: michael@0: /* michael@0: * Store the Debugger.Frame object for frame in *vp. michael@0: * michael@0: * Use this if you have already access to a frame pointer without having michael@0: * to incur the cost of walking the stack. michael@0: */ michael@0: bool getScriptFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) { michael@0: return getScriptFrameWithIter(cx, frame, nullptr, vp); michael@0: } michael@0: michael@0: /* michael@0: * Store the Debugger.Frame object for iter in *vp. Eagerly copies a michael@0: * ScriptFrameIter::Data. michael@0: * michael@0: * Use this if you had to make a ScriptFrameIter to get the required michael@0: * frame, in which case the cost of walking the stack has already been michael@0: * paid. michael@0: */ michael@0: bool getScriptFrame(JSContext *cx, const ScriptFrameIter &iter, MutableHandleValue vp) { michael@0: return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp); michael@0: } michael@0: michael@0: /* michael@0: * Set |*status| and |*value| to a (JSTrapStatus, Value) pair reflecting a michael@0: * standard SpiderMonkey call state: a boolean success value |ok|, a return michael@0: * value |rv|, and a context |cx| that may or may not have an exception set. michael@0: * If an exception was pending on |cx|, it is cleared (and |ok| is asserted michael@0: * to be false). michael@0: */ michael@0: static void resultToCompletion(JSContext *cx, bool ok, const Value &rv, michael@0: JSTrapStatus *status, MutableHandleValue value); michael@0: michael@0: /* michael@0: * Set |*result| to a JavaScript completion value corresponding to |status| michael@0: * and |value|. |value| should be the return value or exception value, not michael@0: * wrapped as a debuggee value. |cx| must be in the debugger compartment. michael@0: */ michael@0: bool newCompletionValue(JSContext *cx, JSTrapStatus status, Value value, michael@0: MutableHandleValue result); michael@0: michael@0: /* michael@0: * Precondition: we are in the debuggee compartment (ac is entered) and ok michael@0: * is true if the operation in the debuggee compartment succeeded, false on michael@0: * error or exception. michael@0: * michael@0: * Postcondition: we are in the debugger compartment, having called michael@0: * ac.leave() even if an error occurred. michael@0: * michael@0: * On success, a completion value is in vp and ac.context does not have a michael@0: * pending exception. (This ordinarily returns true even if the ok argument michael@0: * is false.) michael@0: */ michael@0: bool receiveCompletionValue(mozilla::Maybe &ac, bool ok, michael@0: HandleValue val, michael@0: MutableHandleValue vp); michael@0: michael@0: /* michael@0: * Return the Debugger.Script object for |script|, or create a new one if michael@0: * needed. The context |cx| must be in the debugger compartment; |script| michael@0: * must be a script in a debuggee compartment. michael@0: */ michael@0: JSObject *wrapScript(JSContext *cx, HandleScript script); michael@0: michael@0: /* michael@0: * Return the Debugger.Source object for |source|, or create a new one if michael@0: * needed. The context |cx| must be in the debugger compartment; |source| michael@0: * must be a script source object in a debuggee compartment. michael@0: */ michael@0: JSObject *wrapSource(JSContext *cx, js::HandleScriptSource source); michael@0: michael@0: private: michael@0: Debugger(const Debugger &) MOZ_DELETE; michael@0: Debugger & operator=(const Debugger &) MOZ_DELETE; michael@0: }; michael@0: michael@0: class BreakpointSite { michael@0: friend class Breakpoint; michael@0: friend struct ::JSCompartment; michael@0: friend class ::JSScript; michael@0: friend class Debugger; michael@0: michael@0: public: michael@0: JSScript *script; michael@0: jsbytecode * const pc; michael@0: michael@0: private: michael@0: JSCList breakpoints; /* cyclic list of all js::Breakpoints at this instruction */ michael@0: size_t enabledCount; /* number of breakpoints in the list that are enabled */ michael@0: JSTrapHandler trapHandler; /* trap state */ michael@0: HeapValue trapClosure; michael@0: michael@0: void recompile(FreeOp *fop); michael@0: michael@0: public: michael@0: BreakpointSite(JSScript *script, jsbytecode *pc); michael@0: Breakpoint *firstBreakpoint() const; michael@0: bool hasBreakpoint(Breakpoint *bp); michael@0: bool hasTrap() const { return !!trapHandler; } michael@0: michael@0: void inc(FreeOp *fop); michael@0: void dec(FreeOp *fop); michael@0: void setTrap(FreeOp *fop, JSTrapHandler handler, const Value &closure); michael@0: void clearTrap(FreeOp *fop, JSTrapHandler *handlerp = nullptr, Value *closurep = nullptr); michael@0: void destroyIfEmpty(FreeOp *fop); michael@0: }; michael@0: michael@0: /* michael@0: * Each Breakpoint is a member of two linked lists: its debugger's list and its michael@0: * site's list. michael@0: * michael@0: * GC rules: michael@0: * - script is live, breakpoint exists, and debugger is enabled michael@0: * ==> debugger is live michael@0: * - script is live, breakpoint exists, and debugger is live michael@0: * ==> retain the breakpoint and the handler object is live michael@0: * michael@0: * Debugger::markAllIteratively implements these two rules. It uses michael@0: * Debugger::hasAnyLiveHooks to check for rule 1. michael@0: * michael@0: * Nothing else causes a breakpoint to be retained, so if its script or michael@0: * debugger is collected, the breakpoint is destroyed during GC sweep phase, michael@0: * even if the debugger compartment isn't being GC'd. This is implemented in michael@0: * JSCompartment::sweepBreakpoints. michael@0: */ michael@0: class Breakpoint { michael@0: friend struct ::JSCompartment; michael@0: friend class Debugger; michael@0: michael@0: public: michael@0: Debugger * const debugger; michael@0: BreakpointSite * const site; michael@0: private: michael@0: /* |handler| is marked unconditionally during minor GC. */ michael@0: js::EncapsulatedPtrObject handler; michael@0: JSCList debuggerLinks; michael@0: JSCList siteLinks; michael@0: michael@0: public: michael@0: static Breakpoint *fromDebuggerLinks(JSCList *links); michael@0: static Breakpoint *fromSiteLinks(JSCList *links); michael@0: Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler); michael@0: void destroy(FreeOp *fop); michael@0: Breakpoint *nextInDebugger(); michael@0: Breakpoint *nextInSite(); michael@0: const EncapsulatedPtrObject &getHandler() const { return handler; } michael@0: EncapsulatedPtrObject &getHandlerRef() { return handler; } michael@0: }; michael@0: michael@0: Breakpoint * michael@0: Debugger::firstBreakpoint() const michael@0: { michael@0: if (JS_CLIST_IS_EMPTY(&breakpoints)) michael@0: return nullptr; michael@0: return Breakpoint::fromDebuggerLinks(JS_NEXT_LINK(&breakpoints)); michael@0: } michael@0: michael@0: Debugger * michael@0: Debugger::fromOnNewGlobalObjectWatchersLink(JSCList *link) { michael@0: char *p = reinterpret_cast(link); michael@0: return reinterpret_cast(p - offsetof(Debugger, onNewGlobalObjectWatchersLink)); michael@0: } michael@0: michael@0: const js::HeapPtrObject & michael@0: Debugger::toJSObject() const michael@0: { michael@0: JS_ASSERT(object); michael@0: return object; michael@0: } michael@0: michael@0: js::HeapPtrObject & michael@0: Debugger::toJSObjectRef() michael@0: { michael@0: JS_ASSERT(object); michael@0: return object; michael@0: } michael@0: michael@0: bool michael@0: Debugger::observesEnterFrame() const michael@0: { michael@0: return enabled && getHook(OnEnterFrame); michael@0: } michael@0: michael@0: bool michael@0: Debugger::observesNewScript() const michael@0: { michael@0: return enabled && getHook(OnNewScript); michael@0: } michael@0: michael@0: bool michael@0: Debugger::observesNewGlobalObject() const michael@0: { michael@0: return enabled && getHook(OnNewGlobalObject); michael@0: } michael@0: michael@0: bool michael@0: Debugger::observesGlobal(GlobalObject *global) const michael@0: { michael@0: return debuggees.has(global); michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::onEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) michael@0: { michael@0: if (cx->compartment()->getDebuggees().empty()) michael@0: return JSTRAP_CONTINUE; michael@0: return slowPathOnEnterFrame(cx, frame, vp); michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::onDebuggerStatement(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: return cx->compartment()->getDebuggees().empty() michael@0: ? JSTRAP_CONTINUE michael@0: : dispatchHook(cx, vp, OnDebuggerStatement); michael@0: } michael@0: michael@0: JSTrapStatus michael@0: Debugger::onExceptionUnwind(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: return cx->compartment()->getDebuggees().empty() michael@0: ? JSTRAP_CONTINUE michael@0: : dispatchHook(cx, vp, OnExceptionUnwind); michael@0: } michael@0: michael@0: void michael@0: Debugger::onNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal) michael@0: { michael@0: JS_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal); michael@0: JS_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal == &script->uninlinedGlobal()); michael@0: // We early return in slowPathOnNewScript for self-hosted scripts, so we can michael@0: // ignore those in our assertion here. michael@0: JS_ASSERT_IF(!script->compartment()->options().invisibleToDebugger() && michael@0: !script->selfHosted(), michael@0: script->compartment()->firedOnNewGlobalObject); michael@0: JS_ASSERT_IF(!script->compileAndGo(), !compileAndGoGlobal); michael@0: if (!script->compartment()->getDebuggees().empty()) michael@0: slowPathOnNewScript(cx, script, compileAndGoGlobal); michael@0: } michael@0: michael@0: void michael@0: Debugger::onNewGlobalObject(JSContext *cx, Handle global) michael@0: { michael@0: JS_ASSERT(!global->compartment()->firedOnNewGlobalObject); michael@0: #ifdef DEBUG michael@0: global->compartment()->firedOnNewGlobalObject = true; michael@0: #endif michael@0: if (!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers)) michael@0: Debugger::slowPathOnNewGlobalObject(cx, global); michael@0: } michael@0: michael@0: extern bool michael@0: EvaluateInEnv(JSContext *cx, Handle env, HandleValue thisv, AbstractFramePtr frame, michael@0: ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno, michael@0: MutableHandleValue rval); michael@0: michael@0: } michael@0: michael@0: #endif /* vm_Debugger_h */