|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #ifndef vm_Debugger_h |
|
8 #define vm_Debugger_h |
|
9 |
|
10 #include "mozilla/LinkedList.h" |
|
11 |
|
12 #include "jsclist.h" |
|
13 #include "jscntxt.h" |
|
14 #include "jscompartment.h" |
|
15 #include "jsweakmap.h" |
|
16 |
|
17 #include "gc/Barrier.h" |
|
18 #include "js/HashTable.h" |
|
19 #include "vm/GlobalObject.h" |
|
20 |
|
21 namespace js { |
|
22 |
|
23 class Breakpoint; |
|
24 |
|
25 /* |
|
26 * A weakmap that supports the keys being in different compartments to the |
|
27 * values, although all values must be in the same compartment. |
|
28 * |
|
29 * The Key and Value classes must support the compartment() method. |
|
30 * |
|
31 * The purpose of this is to allow the garbage collector to easily find edges |
|
32 * from debugee object compartments to debugger compartments when calculating |
|
33 * the compartment groups. Note that these edges are the inverse of the edges |
|
34 * stored in the cross compartment map. |
|
35 * |
|
36 * The current implementation results in all debuggee object compartments being |
|
37 * swept in the same group as the debugger. This is a conservative approach, |
|
38 * and compartments may be unnecessarily grouped, however it results in a |
|
39 * simpler and faster implementation. |
|
40 * |
|
41 * If InvisibleKeysOk is true, then the map can have keys in invisible-to- |
|
42 * debugger compartments. If it is false, we assert that such entries are never |
|
43 * created. |
|
44 */ |
|
45 template <class Key, class Value, bool InvisibleKeysOk=false> |
|
46 class DebuggerWeakMap : private WeakMap<Key, Value, DefaultHasher<Key> > |
|
47 { |
|
48 private: |
|
49 typedef HashMap<JS::Zone *, |
|
50 uintptr_t, |
|
51 DefaultHasher<JS::Zone *>, |
|
52 RuntimeAllocPolicy> CountMap; |
|
53 |
|
54 CountMap zoneCounts; |
|
55 |
|
56 public: |
|
57 typedef WeakMap<Key, Value, DefaultHasher<Key> > Base; |
|
58 explicit DebuggerWeakMap(JSContext *cx) |
|
59 : Base(cx), zoneCounts(cx->runtime()) { } |
|
60 |
|
61 public: |
|
62 /* Expose those parts of HashMap public interface that are used by Debugger methods. */ |
|
63 |
|
64 typedef typename Base::Entry Entry; |
|
65 typedef typename Base::Ptr Ptr; |
|
66 typedef typename Base::AddPtr AddPtr; |
|
67 typedef typename Base::Range Range; |
|
68 typedef typename Base::Enum Enum; |
|
69 typedef typename Base::Lookup Lookup; |
|
70 |
|
71 /* Expose WeakMap public interface */ |
|
72 |
|
73 using Base::clearWithoutCallingDestructors; |
|
74 using Base::lookupForAdd; |
|
75 using Base::all; |
|
76 using Base::trace; |
|
77 |
|
78 bool init(uint32_t len = 16) { |
|
79 return Base::init(len) && zoneCounts.init(); |
|
80 } |
|
81 |
|
82 template<typename KeyInput, typename ValueInput> |
|
83 bool relookupOrAdd(AddPtr &p, const KeyInput &k, const ValueInput &v) { |
|
84 JS_ASSERT(v->compartment() == Base::compartment); |
|
85 JS_ASSERT(!k->compartment()->options_.mergeable()); |
|
86 JS_ASSERT_IF(!InvisibleKeysOk, !k->compartment()->options_.invisibleToDebugger()); |
|
87 JS_ASSERT(!Base::has(k)); |
|
88 if (!incZoneCount(k->zone())) |
|
89 return false; |
|
90 bool ok = Base::relookupOrAdd(p, k, v); |
|
91 if (!ok) |
|
92 decZoneCount(k->zone()); |
|
93 return ok; |
|
94 } |
|
95 |
|
96 void remove(const Lookup &l) { |
|
97 JS_ASSERT(Base::has(l)); |
|
98 Base::remove(l); |
|
99 decZoneCount(l->zone()); |
|
100 } |
|
101 |
|
102 public: |
|
103 void markKeys(JSTracer *tracer) { |
|
104 for (Enum e(*static_cast<Base *>(this)); !e.empty(); e.popFront()) { |
|
105 Key key = e.front().key(); |
|
106 gc::Mark(tracer, &key, "Debugger WeakMap key"); |
|
107 if (key != e.front().key()) |
|
108 e.rekeyFront(key); |
|
109 key.unsafeSet(nullptr); |
|
110 } |
|
111 } |
|
112 |
|
113 bool hasKeyInZone(JS::Zone *zone) { |
|
114 CountMap::Ptr p = zoneCounts.lookup(zone); |
|
115 JS_ASSERT_IF(p, p->value() > 0); |
|
116 return p; |
|
117 } |
|
118 |
|
119 private: |
|
120 /* Override sweep method to also update our edge cache. */ |
|
121 void sweep() { |
|
122 for (Enum e(*static_cast<Base *>(this)); !e.empty(); e.popFront()) { |
|
123 Key k(e.front().key()); |
|
124 if (gc::IsAboutToBeFinalized(&k)) { |
|
125 e.removeFront(); |
|
126 decZoneCount(k->zone()); |
|
127 } |
|
128 } |
|
129 Base::assertEntriesNotAboutToBeFinalized(); |
|
130 } |
|
131 |
|
132 bool incZoneCount(JS::Zone *zone) { |
|
133 CountMap::Ptr p = zoneCounts.lookupWithDefault(zone, 0); |
|
134 if (!p) |
|
135 return false; |
|
136 ++p->value(); |
|
137 return true; |
|
138 } |
|
139 |
|
140 void decZoneCount(JS::Zone *zone) { |
|
141 CountMap::Ptr p = zoneCounts.lookup(zone); |
|
142 JS_ASSERT(p); |
|
143 JS_ASSERT(p->value() > 0); |
|
144 --p->value(); |
|
145 if (p->value() == 0) |
|
146 zoneCounts.remove(zone); |
|
147 } |
|
148 }; |
|
149 |
|
150 /* |
|
151 * Env is the type of what ES5 calls "lexical environments" (runtime |
|
152 * activations of lexical scopes). This is currently just JSObject, and is |
|
153 * implemented by Call, Block, With, and DeclEnv objects, among others--but |
|
154 * environments and objects are really two different concepts. |
|
155 */ |
|
156 typedef JSObject Env; |
|
157 |
|
158 class Debugger : private mozilla::LinkedListElement<Debugger> |
|
159 { |
|
160 friend class Breakpoint; |
|
161 friend class mozilla::LinkedListElement<Debugger>; |
|
162 friend bool (::JS_DefineDebuggerObject)(JSContext *cx, JS::HandleObject obj); |
|
163 |
|
164 public: |
|
165 enum Hook { |
|
166 OnDebuggerStatement, |
|
167 OnExceptionUnwind, |
|
168 OnNewScript, |
|
169 OnEnterFrame, |
|
170 OnNewGlobalObject, |
|
171 HookCount |
|
172 }; |
|
173 enum { |
|
174 JSSLOT_DEBUG_PROTO_START, |
|
175 JSSLOT_DEBUG_FRAME_PROTO = JSSLOT_DEBUG_PROTO_START, |
|
176 JSSLOT_DEBUG_ENV_PROTO, |
|
177 JSSLOT_DEBUG_OBJECT_PROTO, |
|
178 JSSLOT_DEBUG_SCRIPT_PROTO, |
|
179 JSSLOT_DEBUG_SOURCE_PROTO, |
|
180 JSSLOT_DEBUG_MEMORY_PROTO, |
|
181 JSSLOT_DEBUG_PROTO_STOP, |
|
182 JSSLOT_DEBUG_HOOK_START = JSSLOT_DEBUG_PROTO_STOP, |
|
183 JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount, |
|
184 JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP, |
|
185 JSSLOT_DEBUG_COUNT |
|
186 }; |
|
187 private: |
|
188 HeapPtrObject object; /* The Debugger object. Strong reference. */ |
|
189 GlobalObjectSet debuggees; /* Debuggee globals. Cross-compartment weak references. */ |
|
190 js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */ |
|
191 bool enabled; |
|
192 JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */ |
|
193 |
|
194 /* |
|
195 * If this Debugger is enabled, and has a onNewGlobalObject handler, then |
|
196 * this link is inserted into the circular list headed by |
|
197 * JSRuntime::onNewGlobalObjectWatchers. Otherwise, this is set to a |
|
198 * singleton cycle. |
|
199 */ |
|
200 JSCList onNewGlobalObjectWatchersLink; |
|
201 |
|
202 /* |
|
203 * Map from stack frames that are currently on the stack to Debugger.Frame |
|
204 * instances. |
|
205 * |
|
206 * The keys are always live stack frames. We drop them from this map as |
|
207 * soon as they leave the stack (see slowPathOnLeaveFrame) and in |
|
208 * removeDebuggee. |
|
209 * |
|
210 * We don't trace the keys of this map (the frames are on the stack and |
|
211 * thus necessarily live), but we do trace the values. It's like a WeakMap |
|
212 * that way, but since stack frames are not gc-things, the implementation |
|
213 * has to be different. |
|
214 */ |
|
215 typedef HashMap<AbstractFramePtr, |
|
216 RelocatablePtrObject, |
|
217 DefaultHasher<AbstractFramePtr>, |
|
218 RuntimeAllocPolicy> FrameMap; |
|
219 FrameMap frames; |
|
220 |
|
221 /* An ephemeral map from JSScript* to Debugger.Script instances. */ |
|
222 typedef DebuggerWeakMap<EncapsulatedPtrScript, RelocatablePtrObject> ScriptWeakMap; |
|
223 ScriptWeakMap scripts; |
|
224 |
|
225 /* The map from debuggee source script objects to their Debugger.Source instances. */ |
|
226 typedef DebuggerWeakMap<EncapsulatedPtrObject, RelocatablePtrObject, true> SourceWeakMap; |
|
227 SourceWeakMap sources; |
|
228 |
|
229 /* The map from debuggee objects to their Debugger.Object instances. */ |
|
230 typedef DebuggerWeakMap<EncapsulatedPtrObject, RelocatablePtrObject> ObjectWeakMap; |
|
231 ObjectWeakMap objects; |
|
232 |
|
233 /* The map from debuggee Envs to Debugger.Environment instances. */ |
|
234 ObjectWeakMap environments; |
|
235 |
|
236 class FrameRange; |
|
237 class ScriptQuery; |
|
238 |
|
239 bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj); |
|
240 bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj, |
|
241 AutoDebugModeInvalidation &invalidate); |
|
242 void cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global, |
|
243 AutoDebugModeInvalidation &invalidate, |
|
244 GlobalObjectSet::Enum *compartmentEnum, |
|
245 GlobalObjectSet::Enum *debugEnu); |
|
246 bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global, |
|
247 GlobalObjectSet::Enum *compartmentEnum, |
|
248 GlobalObjectSet::Enum *debugEnum); |
|
249 bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global, |
|
250 AutoDebugModeInvalidation &invalidate, |
|
251 GlobalObjectSet::Enum *compartmentEnum, |
|
252 GlobalObjectSet::Enum *debugEnum); |
|
253 void removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global, |
|
254 GlobalObjectSet::Enum *compartmentEnum, |
|
255 GlobalObjectSet::Enum *debugEnum); |
|
256 void removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global, |
|
257 AutoDebugModeInvalidation &invalidate, |
|
258 GlobalObjectSet::Enum *compartmentEnum, |
|
259 GlobalObjectSet::Enum *debugEnum); |
|
260 |
|
261 /* |
|
262 * Cope with an error or exception in a debugger hook. |
|
263 * |
|
264 * If callHook is true, then call the uncaughtExceptionHook, if any. If, in |
|
265 * addition, vp is given, then parse the value returned by |
|
266 * uncaughtExceptionHook as a resumption value. |
|
267 * |
|
268 * If there is no uncaughtExceptionHook, or if it fails, report and clear |
|
269 * the pending exception on ac.context and return JSTRAP_ERROR. |
|
270 * |
|
271 * This always calls ac.leave(); ac is a parameter because this method must |
|
272 * do some things in the debugger compartment and some things in the |
|
273 * debuggee compartment. |
|
274 */ |
|
275 JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment> &ac, bool callHook); |
|
276 JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment> &ac, MutableHandleValue vp, bool callHook); |
|
277 |
|
278 JSTrapStatus handleUncaughtExceptionHelper(mozilla::Maybe<AutoCompartment> &ac, |
|
279 MutableHandleValue *vp, bool callHook); |
|
280 |
|
281 /* |
|
282 * Handle the result of a hook that is expected to return a resumption |
|
283 * value <https://wiki.mozilla.org/Debugger#Resumption_Values>. This is called |
|
284 * when we return from a debugging hook to debuggee code. The interpreter wants |
|
285 * a (JSTrapStatus, Value) pair telling it how to proceed. |
|
286 * |
|
287 * Precondition: ac is entered. We are in the debugger compartment. |
|
288 * |
|
289 * Postcondition: This called ac.leave(). See handleUncaughtException. |
|
290 * |
|
291 * If ok is false, the hook failed. If an exception is pending in |
|
292 * ac.context(), return handleUncaughtException(ac, vp, callhook). |
|
293 * Otherwise just return JSTRAP_ERROR. |
|
294 * |
|
295 * If ok is true, there must be no exception pending in ac.context(). rv may be: |
|
296 * undefined - Return JSTRAP_CONTINUE to continue execution normally. |
|
297 * {return: value} or {throw: value} - Call unwrapDebuggeeValue to |
|
298 * unwrap value. Store the result in *vp and return JSTRAP_RETURN |
|
299 * or JSTRAP_THROW. The interpreter will force the current frame to |
|
300 * return or throw an exception. |
|
301 * null - Return JSTRAP_ERROR to terminate the debuggee with an |
|
302 * uncatchable error. |
|
303 * anything else - Make a new TypeError the pending exception and |
|
304 * return handleUncaughtException(ac, vp, callHook). |
|
305 */ |
|
306 JSTrapStatus parseResumptionValue(mozilla::Maybe<AutoCompartment> &ac, bool ok, const Value &rv, |
|
307 MutableHandleValue vp, bool callHook = true); |
|
308 |
|
309 GlobalObject *unwrapDebuggeeArgument(JSContext *cx, const Value &v); |
|
310 |
|
311 static void traceObject(JSTracer *trc, JSObject *obj); |
|
312 void trace(JSTracer *trc); |
|
313 static void finalize(FreeOp *fop, JSObject *obj); |
|
314 void markKeysInCompartment(JSTracer *tracer); |
|
315 |
|
316 static const Class jsclass; |
|
317 |
|
318 static Debugger *fromThisValue(JSContext *cx, const CallArgs &ca, const char *fnname); |
|
319 static bool getEnabled(JSContext *cx, unsigned argc, Value *vp); |
|
320 static bool setEnabled(JSContext *cx, unsigned argc, Value *vp); |
|
321 static bool getHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which); |
|
322 static bool setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which); |
|
323 static bool getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp); |
|
324 static bool setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp); |
|
325 static bool getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp); |
|
326 static bool setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp); |
|
327 static bool getOnNewScript(JSContext *cx, unsigned argc, Value *vp); |
|
328 static bool setOnNewScript(JSContext *cx, unsigned argc, Value *vp); |
|
329 static bool getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp); |
|
330 static bool setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp); |
|
331 static bool getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp); |
|
332 static bool setOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp); |
|
333 static bool getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp); |
|
334 static bool setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp); |
|
335 static bool getMemory(JSContext *cx, unsigned argc, Value *vp); |
|
336 static bool addDebuggee(JSContext *cx, unsigned argc, Value *vp); |
|
337 static bool addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp); |
|
338 static bool removeDebuggee(JSContext *cx, unsigned argc, Value *vp); |
|
339 static bool removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp); |
|
340 static bool hasDebuggee(JSContext *cx, unsigned argc, Value *vp); |
|
341 static bool getDebuggees(JSContext *cx, unsigned argc, Value *vp); |
|
342 static bool getNewestFrame(JSContext *cx, unsigned argc, Value *vp); |
|
343 static bool clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp); |
|
344 static bool findScripts(JSContext *cx, unsigned argc, Value *vp); |
|
345 static bool findAllGlobals(JSContext *cx, unsigned argc, Value *vp); |
|
346 static bool makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp); |
|
347 static bool construct(JSContext *cx, unsigned argc, Value *vp); |
|
348 static const JSPropertySpec properties[]; |
|
349 static const JSFunctionSpec methods[]; |
|
350 |
|
351 JSObject *getHook(Hook hook) const; |
|
352 bool hasAnyLiveHooks() const; |
|
353 |
|
354 static JSTrapStatus slowPathOnEnterFrame(JSContext *cx, AbstractFramePtr frame, |
|
355 MutableHandleValue vp); |
|
356 static bool slowPathOnLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok); |
|
357 static void slowPathOnNewScript(JSContext *cx, HandleScript script, |
|
358 GlobalObject *compileAndGoGlobal); |
|
359 static void slowPathOnNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global); |
|
360 static JSTrapStatus dispatchHook(JSContext *cx, MutableHandleValue vp, Hook which); |
|
361 |
|
362 JSTrapStatus fireDebuggerStatement(JSContext *cx, MutableHandleValue vp); |
|
363 JSTrapStatus fireExceptionUnwind(JSContext *cx, MutableHandleValue vp); |
|
364 JSTrapStatus fireEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp); |
|
365 JSTrapStatus fireNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global, MutableHandleValue vp); |
|
366 |
|
367 /* |
|
368 * Allocate and initialize a Debugger.Script instance whose referent is |
|
369 * |script|. |
|
370 */ |
|
371 JSObject *newDebuggerScript(JSContext *cx, HandleScript script); |
|
372 |
|
373 /* |
|
374 * Allocate and initialize a Debugger.Source instance whose referent is |
|
375 * |source|. |
|
376 */ |
|
377 JSObject *newDebuggerSource(JSContext *cx, js::HandleScriptSource source); |
|
378 |
|
379 /* |
|
380 * Receive a "new script" event from the engine. A new script was compiled |
|
381 * or deserialized. |
|
382 */ |
|
383 void fireNewScript(JSContext *cx, HandleScript script); |
|
384 |
|
385 /* |
|
386 * Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy |
|
387 * its data if we need to make a new Debugger.Frame. |
|
388 */ |
|
389 bool getScriptFrameWithIter(JSContext *cx, AbstractFramePtr frame, |
|
390 const ScriptFrameIter *maybeIter, MutableHandleValue vp); |
|
391 |
|
392 inline Breakpoint *firstBreakpoint() const; |
|
393 |
|
394 static inline Debugger *fromOnNewGlobalObjectWatchersLink(JSCList *link); |
|
395 |
|
396 static bool replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to, |
|
397 ScriptFrameIter &iter); |
|
398 |
|
399 public: |
|
400 Debugger(JSContext *cx, JSObject *dbg); |
|
401 ~Debugger(); |
|
402 |
|
403 bool init(JSContext *cx); |
|
404 inline const js::HeapPtrObject &toJSObject() const; |
|
405 inline js::HeapPtrObject &toJSObjectRef(); |
|
406 static inline Debugger *fromJSObject(JSObject *obj); |
|
407 static Debugger *fromChildJSObject(JSObject *obj); |
|
408 |
|
409 /*********************************** Methods for interaction with the GC. */ |
|
410 |
|
411 /* |
|
412 * A Debugger object is live if: |
|
413 * * the Debugger JSObject is live (Debugger::trace handles this case); OR |
|
414 * * it is in the middle of dispatching an event (the event dispatching |
|
415 * code roots it in this case); OR |
|
416 * * it is enabled, and it is debugging at least one live compartment, |
|
417 * and at least one of the following is true: |
|
418 * - it has a debugger hook installed |
|
419 * - it has a breakpoint set on a live script |
|
420 * - it has a watchpoint set on a live object. |
|
421 * |
|
422 * Debugger::markAllIteratively handles the last case. If it finds any |
|
423 * Debugger objects that are definitely live but not yet marked, it marks |
|
424 * them and returns true. If not, it returns false. |
|
425 */ |
|
426 static void markCrossCompartmentDebuggerObjectReferents(JSTracer *tracer); |
|
427 static bool markAllIteratively(GCMarker *trc); |
|
428 static void markAll(JSTracer *trc); |
|
429 static void sweepAll(FreeOp *fop); |
|
430 static void detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global, |
|
431 GlobalObjectSet::Enum *compartmentEnum); |
|
432 static void findCompartmentEdges(JS::Zone *v, gc::ComponentFinder<JS::Zone> &finder); |
|
433 |
|
434 static inline JSTrapStatus onEnterFrame(JSContext *cx, AbstractFramePtr frame, |
|
435 MutableHandleValue vp); |
|
436 static inline bool onLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok); |
|
437 static inline JSTrapStatus onDebuggerStatement(JSContext *cx, MutableHandleValue vp); |
|
438 static inline JSTrapStatus onExceptionUnwind(JSContext *cx, MutableHandleValue vp); |
|
439 static inline void onNewScript(JSContext *cx, HandleScript script, |
|
440 GlobalObject *compileAndGoGlobal); |
|
441 static inline void onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global); |
|
442 static JSTrapStatus onTrap(JSContext *cx, MutableHandleValue vp); |
|
443 static JSTrapStatus onSingleStep(JSContext *cx, MutableHandleValue vp); |
|
444 static bool handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to); |
|
445 static bool handleIonBailout(JSContext *cx, jit::RematerializedFrame *from, jit::BaselineFrame *to); |
|
446 |
|
447 /************************************* Functions for use by Debugger.cpp. */ |
|
448 |
|
449 inline bool observesEnterFrame() const; |
|
450 inline bool observesNewScript() const; |
|
451 inline bool observesNewGlobalObject() const; |
|
452 inline bool observesGlobal(GlobalObject *global) const; |
|
453 bool observesFrame(AbstractFramePtr frame) const; |
|
454 bool observesFrame(const ScriptFrameIter &iter) const; |
|
455 bool observesScript(JSScript *script) const; |
|
456 |
|
457 /* |
|
458 * If env is nullptr, call vp->setNull() and return true. Otherwise, find |
|
459 * or create a Debugger.Environment object for the given Env. On success, |
|
460 * store the Environment object in *vp and return true. |
|
461 */ |
|
462 bool wrapEnvironment(JSContext *cx, Handle<Env*> env, MutableHandleValue vp); |
|
463 |
|
464 /* |
|
465 * Like cx->compartment()->wrap(cx, vp), but for the debugger compartment. |
|
466 * |
|
467 * Preconditions: *vp is a value from a debuggee compartment; cx is in the |
|
468 * debugger's compartment. |
|
469 * |
|
470 * If *vp is an object, this produces a (new or existing) Debugger.Object |
|
471 * wrapper for it. Otherwise this is the same as JSCompartment::wrap. |
|
472 * |
|
473 * If *vp is a magic JS_OPTIMIZED_OUT value, this produces a plain object |
|
474 * of the form { optimizedOut: true }. |
|
475 * |
|
476 * If *vp is a magic JS_OPTIMIZED_ARGUMENTS value signifying missing |
|
477 * arguments, this produces a plain object of the form { missingArguments: |
|
478 * true }. |
|
479 */ |
|
480 bool wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp); |
|
481 |
|
482 /* |
|
483 * Unwrap a Debug.Object, without rewrapping it for any particular debuggee |
|
484 * compartment. |
|
485 * |
|
486 * Preconditions: cx is in the debugger compartment. *vp is a value in that |
|
487 * compartment. (*vp should be a "debuggee value", meaning it is the |
|
488 * debugger's reflection of a value in the debuggee.) |
|
489 * |
|
490 * If *vp is a Debugger.Object, store the referent in *vp. Otherwise, if *vp |
|
491 * is an object, throw a TypeError, because it is not a debuggee |
|
492 * value. Otherwise *vp is a primitive, so leave it alone. |
|
493 * |
|
494 * When passing values from the debuggee to the debugger: |
|
495 * enter debugger compartment; |
|
496 * call wrapDebuggeeValue; // compartment- and debugger-wrapping |
|
497 * |
|
498 * When passing values from the debugger to the debuggee: |
|
499 * call unwrapDebuggeeValue; // debugger-unwrapping |
|
500 * enter debuggee compartment; |
|
501 * call cx->compartment()->wrap; // compartment-rewrapping |
|
502 * |
|
503 * (Extreme nerd sidebar: Unwrapping happens in two steps because there are |
|
504 * two different kinds of symmetry at work: regardless of which direction |
|
505 * we're going, we want any exceptions to be created and thrown in the |
|
506 * debugger compartment--mirror symmetry. But compartment wrapping always |
|
507 * happens in the target compartment--rotational symmetry.) |
|
508 */ |
|
509 bool unwrapDebuggeeValue(JSContext *cx, MutableHandleValue vp); |
|
510 |
|
511 /* |
|
512 * Store the Debugger.Frame object for frame in *vp. |
|
513 * |
|
514 * Use this if you have already access to a frame pointer without having |
|
515 * to incur the cost of walking the stack. |
|
516 */ |
|
517 bool getScriptFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) { |
|
518 return getScriptFrameWithIter(cx, frame, nullptr, vp); |
|
519 } |
|
520 |
|
521 /* |
|
522 * Store the Debugger.Frame object for iter in *vp. Eagerly copies a |
|
523 * ScriptFrameIter::Data. |
|
524 * |
|
525 * Use this if you had to make a ScriptFrameIter to get the required |
|
526 * frame, in which case the cost of walking the stack has already been |
|
527 * paid. |
|
528 */ |
|
529 bool getScriptFrame(JSContext *cx, const ScriptFrameIter &iter, MutableHandleValue vp) { |
|
530 return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp); |
|
531 } |
|
532 |
|
533 /* |
|
534 * Set |*status| and |*value| to a (JSTrapStatus, Value) pair reflecting a |
|
535 * standard SpiderMonkey call state: a boolean success value |ok|, a return |
|
536 * value |rv|, and a context |cx| that may or may not have an exception set. |
|
537 * If an exception was pending on |cx|, it is cleared (and |ok| is asserted |
|
538 * to be false). |
|
539 */ |
|
540 static void resultToCompletion(JSContext *cx, bool ok, const Value &rv, |
|
541 JSTrapStatus *status, MutableHandleValue value); |
|
542 |
|
543 /* |
|
544 * Set |*result| to a JavaScript completion value corresponding to |status| |
|
545 * and |value|. |value| should be the return value or exception value, not |
|
546 * wrapped as a debuggee value. |cx| must be in the debugger compartment. |
|
547 */ |
|
548 bool newCompletionValue(JSContext *cx, JSTrapStatus status, Value value, |
|
549 MutableHandleValue result); |
|
550 |
|
551 /* |
|
552 * Precondition: we are in the debuggee compartment (ac is entered) and ok |
|
553 * is true if the operation in the debuggee compartment succeeded, false on |
|
554 * error or exception. |
|
555 * |
|
556 * Postcondition: we are in the debugger compartment, having called |
|
557 * ac.leave() even if an error occurred. |
|
558 * |
|
559 * On success, a completion value is in vp and ac.context does not have a |
|
560 * pending exception. (This ordinarily returns true even if the ok argument |
|
561 * is false.) |
|
562 */ |
|
563 bool receiveCompletionValue(mozilla::Maybe<AutoCompartment> &ac, bool ok, |
|
564 HandleValue val, |
|
565 MutableHandleValue vp); |
|
566 |
|
567 /* |
|
568 * Return the Debugger.Script object for |script|, or create a new one if |
|
569 * needed. The context |cx| must be in the debugger compartment; |script| |
|
570 * must be a script in a debuggee compartment. |
|
571 */ |
|
572 JSObject *wrapScript(JSContext *cx, HandleScript script); |
|
573 |
|
574 /* |
|
575 * Return the Debugger.Source object for |source|, or create a new one if |
|
576 * needed. The context |cx| must be in the debugger compartment; |source| |
|
577 * must be a script source object in a debuggee compartment. |
|
578 */ |
|
579 JSObject *wrapSource(JSContext *cx, js::HandleScriptSource source); |
|
580 |
|
581 private: |
|
582 Debugger(const Debugger &) MOZ_DELETE; |
|
583 Debugger & operator=(const Debugger &) MOZ_DELETE; |
|
584 }; |
|
585 |
|
586 class BreakpointSite { |
|
587 friend class Breakpoint; |
|
588 friend struct ::JSCompartment; |
|
589 friend class ::JSScript; |
|
590 friend class Debugger; |
|
591 |
|
592 public: |
|
593 JSScript *script; |
|
594 jsbytecode * const pc; |
|
595 |
|
596 private: |
|
597 JSCList breakpoints; /* cyclic list of all js::Breakpoints at this instruction */ |
|
598 size_t enabledCount; /* number of breakpoints in the list that are enabled */ |
|
599 JSTrapHandler trapHandler; /* trap state */ |
|
600 HeapValue trapClosure; |
|
601 |
|
602 void recompile(FreeOp *fop); |
|
603 |
|
604 public: |
|
605 BreakpointSite(JSScript *script, jsbytecode *pc); |
|
606 Breakpoint *firstBreakpoint() const; |
|
607 bool hasBreakpoint(Breakpoint *bp); |
|
608 bool hasTrap() const { return !!trapHandler; } |
|
609 |
|
610 void inc(FreeOp *fop); |
|
611 void dec(FreeOp *fop); |
|
612 void setTrap(FreeOp *fop, JSTrapHandler handler, const Value &closure); |
|
613 void clearTrap(FreeOp *fop, JSTrapHandler *handlerp = nullptr, Value *closurep = nullptr); |
|
614 void destroyIfEmpty(FreeOp *fop); |
|
615 }; |
|
616 |
|
617 /* |
|
618 * Each Breakpoint is a member of two linked lists: its debugger's list and its |
|
619 * site's list. |
|
620 * |
|
621 * GC rules: |
|
622 * - script is live, breakpoint exists, and debugger is enabled |
|
623 * ==> debugger is live |
|
624 * - script is live, breakpoint exists, and debugger is live |
|
625 * ==> retain the breakpoint and the handler object is live |
|
626 * |
|
627 * Debugger::markAllIteratively implements these two rules. It uses |
|
628 * Debugger::hasAnyLiveHooks to check for rule 1. |
|
629 * |
|
630 * Nothing else causes a breakpoint to be retained, so if its script or |
|
631 * debugger is collected, the breakpoint is destroyed during GC sweep phase, |
|
632 * even if the debugger compartment isn't being GC'd. This is implemented in |
|
633 * JSCompartment::sweepBreakpoints. |
|
634 */ |
|
635 class Breakpoint { |
|
636 friend struct ::JSCompartment; |
|
637 friend class Debugger; |
|
638 |
|
639 public: |
|
640 Debugger * const debugger; |
|
641 BreakpointSite * const site; |
|
642 private: |
|
643 /* |handler| is marked unconditionally during minor GC. */ |
|
644 js::EncapsulatedPtrObject handler; |
|
645 JSCList debuggerLinks; |
|
646 JSCList siteLinks; |
|
647 |
|
648 public: |
|
649 static Breakpoint *fromDebuggerLinks(JSCList *links); |
|
650 static Breakpoint *fromSiteLinks(JSCList *links); |
|
651 Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler); |
|
652 void destroy(FreeOp *fop); |
|
653 Breakpoint *nextInDebugger(); |
|
654 Breakpoint *nextInSite(); |
|
655 const EncapsulatedPtrObject &getHandler() const { return handler; } |
|
656 EncapsulatedPtrObject &getHandlerRef() { return handler; } |
|
657 }; |
|
658 |
|
659 Breakpoint * |
|
660 Debugger::firstBreakpoint() const |
|
661 { |
|
662 if (JS_CLIST_IS_EMPTY(&breakpoints)) |
|
663 return nullptr; |
|
664 return Breakpoint::fromDebuggerLinks(JS_NEXT_LINK(&breakpoints)); |
|
665 } |
|
666 |
|
667 Debugger * |
|
668 Debugger::fromOnNewGlobalObjectWatchersLink(JSCList *link) { |
|
669 char *p = reinterpret_cast<char *>(link); |
|
670 return reinterpret_cast<Debugger *>(p - offsetof(Debugger, onNewGlobalObjectWatchersLink)); |
|
671 } |
|
672 |
|
673 const js::HeapPtrObject & |
|
674 Debugger::toJSObject() const |
|
675 { |
|
676 JS_ASSERT(object); |
|
677 return object; |
|
678 } |
|
679 |
|
680 js::HeapPtrObject & |
|
681 Debugger::toJSObjectRef() |
|
682 { |
|
683 JS_ASSERT(object); |
|
684 return object; |
|
685 } |
|
686 |
|
687 bool |
|
688 Debugger::observesEnterFrame() const |
|
689 { |
|
690 return enabled && getHook(OnEnterFrame); |
|
691 } |
|
692 |
|
693 bool |
|
694 Debugger::observesNewScript() const |
|
695 { |
|
696 return enabled && getHook(OnNewScript); |
|
697 } |
|
698 |
|
699 bool |
|
700 Debugger::observesNewGlobalObject() const |
|
701 { |
|
702 return enabled && getHook(OnNewGlobalObject); |
|
703 } |
|
704 |
|
705 bool |
|
706 Debugger::observesGlobal(GlobalObject *global) const |
|
707 { |
|
708 return debuggees.has(global); |
|
709 } |
|
710 |
|
711 JSTrapStatus |
|
712 Debugger::onEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) |
|
713 { |
|
714 if (cx->compartment()->getDebuggees().empty()) |
|
715 return JSTRAP_CONTINUE; |
|
716 return slowPathOnEnterFrame(cx, frame, vp); |
|
717 } |
|
718 |
|
719 JSTrapStatus |
|
720 Debugger::onDebuggerStatement(JSContext *cx, MutableHandleValue vp) |
|
721 { |
|
722 return cx->compartment()->getDebuggees().empty() |
|
723 ? JSTRAP_CONTINUE |
|
724 : dispatchHook(cx, vp, OnDebuggerStatement); |
|
725 } |
|
726 |
|
727 JSTrapStatus |
|
728 Debugger::onExceptionUnwind(JSContext *cx, MutableHandleValue vp) |
|
729 { |
|
730 return cx->compartment()->getDebuggees().empty() |
|
731 ? JSTRAP_CONTINUE |
|
732 : dispatchHook(cx, vp, OnExceptionUnwind); |
|
733 } |
|
734 |
|
735 void |
|
736 Debugger::onNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal) |
|
737 { |
|
738 JS_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal); |
|
739 JS_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal == &script->uninlinedGlobal()); |
|
740 // We early return in slowPathOnNewScript for self-hosted scripts, so we can |
|
741 // ignore those in our assertion here. |
|
742 JS_ASSERT_IF(!script->compartment()->options().invisibleToDebugger() && |
|
743 !script->selfHosted(), |
|
744 script->compartment()->firedOnNewGlobalObject); |
|
745 JS_ASSERT_IF(!script->compileAndGo(), !compileAndGoGlobal); |
|
746 if (!script->compartment()->getDebuggees().empty()) |
|
747 slowPathOnNewScript(cx, script, compileAndGoGlobal); |
|
748 } |
|
749 |
|
750 void |
|
751 Debugger::onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global) |
|
752 { |
|
753 JS_ASSERT(!global->compartment()->firedOnNewGlobalObject); |
|
754 #ifdef DEBUG |
|
755 global->compartment()->firedOnNewGlobalObject = true; |
|
756 #endif |
|
757 if (!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers)) |
|
758 Debugger::slowPathOnNewGlobalObject(cx, global); |
|
759 } |
|
760 |
|
761 extern bool |
|
762 EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame, |
|
763 ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno, |
|
764 MutableHandleValue rval); |
|
765 |
|
766 } |
|
767 |
|
768 #endif /* vm_Debugger_h */ |