js/src/vm/Debugger.h

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:613bba616327
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 */

mercurial