|
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 #include "vm/Debugger-inl.h" |
|
8 |
|
9 #include "jscntxt.h" |
|
10 #include "jscompartment.h" |
|
11 #include "jshashutil.h" |
|
12 #include "jsnum.h" |
|
13 #include "jsobj.h" |
|
14 #include "jswrapper.h" |
|
15 #include "frontend/BytecodeCompiler.h" |
|
16 #include "gc/Marking.h" |
|
17 #include "jit/BaselineJIT.h" |
|
18 #include "js/Vector.h" |
|
19 #include "vm/ArgumentsObject.h" |
|
20 #include "vm/DebuggerMemory.h" |
|
21 #include "vm/WrapperObject.h" |
|
22 #include "jsgcinlines.h" |
|
23 #include "jsobjinlines.h" |
|
24 #include "jsopcodeinlines.h" |
|
25 #include "jsscriptinlines.h" |
|
26 #include "vm/ObjectImpl-inl.h" |
|
27 #include "vm/Stack-inl.h" |
|
28 |
|
29 using namespace js; |
|
30 |
|
31 using js::frontend::IsIdentifier; |
|
32 using mozilla::ArrayLength; |
|
33 using mozilla::Maybe; |
|
34 |
|
35 |
|
36 /*** Forward declarations ************************************************************************/ |
|
37 |
|
38 extern const Class DebuggerFrame_class; |
|
39 |
|
40 enum { |
|
41 JSSLOT_DEBUGFRAME_OWNER, |
|
42 JSSLOT_DEBUGFRAME_ARGUMENTS, |
|
43 JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, |
|
44 JSSLOT_DEBUGFRAME_ONPOP_HANDLER, |
|
45 JSSLOT_DEBUGFRAME_COUNT |
|
46 }; |
|
47 |
|
48 extern const Class DebuggerArguments_class; |
|
49 |
|
50 enum { |
|
51 JSSLOT_DEBUGARGUMENTS_FRAME, |
|
52 JSSLOT_DEBUGARGUMENTS_COUNT |
|
53 }; |
|
54 |
|
55 extern const Class DebuggerEnv_class; |
|
56 |
|
57 enum { |
|
58 JSSLOT_DEBUGENV_OWNER, |
|
59 JSSLOT_DEBUGENV_COUNT |
|
60 }; |
|
61 |
|
62 extern const Class DebuggerObject_class; |
|
63 |
|
64 enum { |
|
65 JSSLOT_DEBUGOBJECT_OWNER, |
|
66 JSSLOT_DEBUGOBJECT_COUNT |
|
67 }; |
|
68 |
|
69 extern const Class DebuggerScript_class; |
|
70 |
|
71 enum { |
|
72 JSSLOT_DEBUGSCRIPT_OWNER, |
|
73 JSSLOT_DEBUGSCRIPT_COUNT |
|
74 }; |
|
75 |
|
76 extern const Class DebuggerSource_class; |
|
77 |
|
78 enum { |
|
79 JSSLOT_DEBUGSOURCE_OWNER, |
|
80 JSSLOT_DEBUGSOURCE_COUNT |
|
81 }; |
|
82 |
|
83 |
|
84 /*** Utils ***************************************************************************************/ |
|
85 |
|
86 static bool |
|
87 ReportMoreArgsNeeded(JSContext *cx, const char *name, unsigned required) |
|
88 { |
|
89 JS_ASSERT(required > 0); |
|
90 JS_ASSERT(required <= 10); |
|
91 char s[2]; |
|
92 s[0] = '0' + (required - 1); |
|
93 s[1] = '\0'; |
|
94 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
95 name, s, required == 2 ? "" : "s"); |
|
96 return false; |
|
97 } |
|
98 |
|
99 static inline bool |
|
100 EnsureFunctionHasScript(JSContext *cx, HandleFunction fun) |
|
101 { |
|
102 if (fun->isInterpretedLazy()) { |
|
103 AutoCompartment ac(cx, fun); |
|
104 return !!fun->getOrCreateScript(cx); |
|
105 } |
|
106 return true; |
|
107 } |
|
108 |
|
109 static inline JSScript * |
|
110 GetOrCreateFunctionScript(JSContext *cx, HandleFunction fun) |
|
111 { |
|
112 MOZ_ASSERT(fun->isInterpreted()); |
|
113 if (!EnsureFunctionHasScript(cx, fun)) |
|
114 return nullptr; |
|
115 return fun->nonLazyScript(); |
|
116 } |
|
117 |
|
118 #define REQUIRE_ARGC(name, n) \ |
|
119 JS_BEGIN_MACRO \ |
|
120 if (argc < (n)) \ |
|
121 return ReportMoreArgsNeeded(cx, name, n); \ |
|
122 JS_END_MACRO |
|
123 |
|
124 static bool |
|
125 ReportObjectRequired(JSContext *cx) |
|
126 { |
|
127 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); |
|
128 return false; |
|
129 } |
|
130 |
|
131 static bool |
|
132 ValueToIdentifier(JSContext *cx, HandleValue v, MutableHandleId id) |
|
133 { |
|
134 if (!ValueToId<CanGC>(cx, v, id)) |
|
135 return false; |
|
136 if (!JSID_IS_ATOM(id) || !IsIdentifier(JSID_TO_ATOM(id))) { |
|
137 RootedValue val(cx, v); |
|
138 js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, |
|
139 JSDVG_SEARCH_STACK, val, js::NullPtr(), "not an identifier", |
|
140 nullptr); |
|
141 return false; |
|
142 } |
|
143 return true; |
|
144 } |
|
145 |
|
146 /* |
|
147 * A range of all the Debugger.Frame objects for a particular AbstractFramePtr. |
|
148 * |
|
149 * FIXME This checks only current debuggers, so it relies on a hack in |
|
150 * Debugger::removeDebuggeeGlobal to make sure only current debuggers |
|
151 * have Frame objects with .live === true. |
|
152 */ |
|
153 class Debugger::FrameRange |
|
154 { |
|
155 AbstractFramePtr frame; |
|
156 |
|
157 /* The debuggers in |fp|'s compartment, or nullptr if there are none. */ |
|
158 GlobalObject::DebuggerVector *debuggers; |
|
159 |
|
160 /* |
|
161 * The index of the front Debugger.Frame's debugger in debuggers. |
|
162 * nextDebugger < debuggerCount if and only if the range is not empty. |
|
163 */ |
|
164 size_t debuggerCount, nextDebugger; |
|
165 |
|
166 /* |
|
167 * If the range is not empty, this is front Debugger.Frame's entry in its |
|
168 * debugger's frame table. |
|
169 */ |
|
170 FrameMap::Ptr entry; |
|
171 |
|
172 public: |
|
173 /* |
|
174 * Return a range containing all Debugger.Frame instances referring to |
|
175 * |fp|. |global| is |fp|'s global object; if nullptr or omitted, we |
|
176 * compute it ourselves from |fp|. |
|
177 * |
|
178 * We keep an index into the compartment's debugger list, and a |
|
179 * FrameMap::Ptr into the current debugger's frame map. Thus, if the set of |
|
180 * debuggers in |fp|'s compartment changes, this range becomes invalid. |
|
181 * Similarly, if stack frames are added to or removed from frontDebugger(), |
|
182 * then the range's front is invalid until popFront is called. |
|
183 */ |
|
184 FrameRange(AbstractFramePtr frame, GlobalObject *global = nullptr) |
|
185 : frame(frame) |
|
186 { |
|
187 nextDebugger = 0; |
|
188 |
|
189 /* Find our global, if we were not given one. */ |
|
190 if (!global) |
|
191 global = &frame.script()->global(); |
|
192 |
|
193 /* The frame and global must match. */ |
|
194 JS_ASSERT(&frame.script()->global() == global); |
|
195 |
|
196 /* Find the list of debuggers we'll iterate over. There may be none. */ |
|
197 debuggers = global->getDebuggers(); |
|
198 if (debuggers) { |
|
199 debuggerCount = debuggers->length(); |
|
200 findNext(); |
|
201 } else { |
|
202 debuggerCount = 0; |
|
203 } |
|
204 } |
|
205 |
|
206 bool empty() const { |
|
207 return nextDebugger >= debuggerCount; |
|
208 } |
|
209 |
|
210 JSObject *frontFrame() const { |
|
211 JS_ASSERT(!empty()); |
|
212 return entry->value(); |
|
213 } |
|
214 |
|
215 Debugger *frontDebugger() const { |
|
216 JS_ASSERT(!empty()); |
|
217 return (*debuggers)[nextDebugger]; |
|
218 } |
|
219 |
|
220 /* |
|
221 * Delete the front frame from its Debugger's frame map. After this call, |
|
222 * the range's front is invalid until popFront is called. |
|
223 */ |
|
224 void removeFrontFrame() const { |
|
225 JS_ASSERT(!empty()); |
|
226 frontDebugger()->frames.remove(entry); |
|
227 } |
|
228 |
|
229 void popFront() { |
|
230 JS_ASSERT(!empty()); |
|
231 nextDebugger++; |
|
232 findNext(); |
|
233 } |
|
234 |
|
235 private: |
|
236 /* |
|
237 * Either make this range refer to the first appropriate Debugger.Frame at |
|
238 * or after nextDebugger, or make it empty. |
|
239 */ |
|
240 void findNext() { |
|
241 while (!empty()) { |
|
242 Debugger *dbg = (*debuggers)[nextDebugger]; |
|
243 entry = dbg->frames.lookup(frame); |
|
244 if (entry) |
|
245 break; |
|
246 nextDebugger++; |
|
247 } |
|
248 } |
|
249 }; |
|
250 |
|
251 /*** Breakpoints *********************************************************************************/ |
|
252 |
|
253 BreakpointSite::BreakpointSite(JSScript *script, jsbytecode *pc) |
|
254 : script(script), pc(pc), enabledCount(0), |
|
255 trapHandler(nullptr), trapClosure(UndefinedValue()) |
|
256 { |
|
257 JS_ASSERT(!script->hasBreakpointsAt(pc)); |
|
258 JS_INIT_CLIST(&breakpoints); |
|
259 } |
|
260 |
|
261 void |
|
262 BreakpointSite::recompile(FreeOp *fop) |
|
263 { |
|
264 #ifdef JS_ION |
|
265 if (script->hasBaselineScript()) |
|
266 script->baselineScript()->toggleDebugTraps(script, pc); |
|
267 #endif |
|
268 } |
|
269 |
|
270 void |
|
271 BreakpointSite::inc(FreeOp *fop) |
|
272 { |
|
273 enabledCount++; |
|
274 if (enabledCount == 1 && !trapHandler) |
|
275 recompile(fop); |
|
276 } |
|
277 |
|
278 void |
|
279 BreakpointSite::dec(FreeOp *fop) |
|
280 { |
|
281 JS_ASSERT(enabledCount > 0); |
|
282 enabledCount--; |
|
283 if (enabledCount == 0 && !trapHandler) |
|
284 recompile(fop); |
|
285 } |
|
286 |
|
287 void |
|
288 BreakpointSite::setTrap(FreeOp *fop, JSTrapHandler handler, const Value &closure) |
|
289 { |
|
290 trapHandler = handler; |
|
291 trapClosure = closure; |
|
292 |
|
293 if (enabledCount == 0) |
|
294 recompile(fop); |
|
295 } |
|
296 |
|
297 void |
|
298 BreakpointSite::clearTrap(FreeOp *fop, JSTrapHandler *handlerp, Value *closurep) |
|
299 { |
|
300 if (handlerp) |
|
301 *handlerp = trapHandler; |
|
302 if (closurep) |
|
303 *closurep = trapClosure; |
|
304 |
|
305 trapHandler = nullptr; |
|
306 trapClosure = UndefinedValue(); |
|
307 if (enabledCount == 0) { |
|
308 if (!fop->runtime()->isHeapBusy()) { |
|
309 /* If the GC is running then the script is being destroyed. */ |
|
310 recompile(fop); |
|
311 } |
|
312 destroyIfEmpty(fop); |
|
313 } |
|
314 } |
|
315 |
|
316 void |
|
317 BreakpointSite::destroyIfEmpty(FreeOp *fop) |
|
318 { |
|
319 if (JS_CLIST_IS_EMPTY(&breakpoints) && !trapHandler) |
|
320 script->destroyBreakpointSite(fop, pc); |
|
321 } |
|
322 |
|
323 Breakpoint * |
|
324 BreakpointSite::firstBreakpoint() const |
|
325 { |
|
326 if (JS_CLIST_IS_EMPTY(&breakpoints)) |
|
327 return nullptr; |
|
328 return Breakpoint::fromSiteLinks(JS_NEXT_LINK(&breakpoints)); |
|
329 } |
|
330 |
|
331 bool |
|
332 BreakpointSite::hasBreakpoint(Breakpoint *bp) |
|
333 { |
|
334 for (Breakpoint *p = firstBreakpoint(); p; p = p->nextInSite()) |
|
335 if (p == bp) |
|
336 return true; |
|
337 return false; |
|
338 } |
|
339 |
|
340 Breakpoint::Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler) |
|
341 : debugger(debugger), site(site), handler(handler) |
|
342 { |
|
343 JS_ASSERT(handler->compartment() == debugger->object->compartment()); |
|
344 JS_APPEND_LINK(&debuggerLinks, &debugger->breakpoints); |
|
345 JS_APPEND_LINK(&siteLinks, &site->breakpoints); |
|
346 } |
|
347 |
|
348 Breakpoint * |
|
349 Breakpoint::fromDebuggerLinks(JSCList *links) |
|
350 { |
|
351 return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, debuggerLinks)); |
|
352 } |
|
353 |
|
354 Breakpoint * |
|
355 Breakpoint::fromSiteLinks(JSCList *links) |
|
356 { |
|
357 return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, siteLinks)); |
|
358 } |
|
359 |
|
360 void |
|
361 Breakpoint::destroy(FreeOp *fop) |
|
362 { |
|
363 if (debugger->enabled) |
|
364 site->dec(fop); |
|
365 JS_REMOVE_LINK(&debuggerLinks); |
|
366 JS_REMOVE_LINK(&siteLinks); |
|
367 site->destroyIfEmpty(fop); |
|
368 fop->delete_(this); |
|
369 } |
|
370 |
|
371 Breakpoint * |
|
372 Breakpoint::nextInDebugger() |
|
373 { |
|
374 JSCList *link = JS_NEXT_LINK(&debuggerLinks); |
|
375 return (link == &debugger->breakpoints) ? nullptr : fromDebuggerLinks(link); |
|
376 } |
|
377 |
|
378 Breakpoint * |
|
379 Breakpoint::nextInSite() |
|
380 { |
|
381 JSCList *link = JS_NEXT_LINK(&siteLinks); |
|
382 return (link == &site->breakpoints) ? nullptr : fromSiteLinks(link); |
|
383 } |
|
384 |
|
385 /*** Debugger hook dispatch **********************************************************************/ |
|
386 |
|
387 Debugger::Debugger(JSContext *cx, JSObject *dbg) |
|
388 : object(dbg), uncaughtExceptionHook(nullptr), enabled(true), |
|
389 frames(cx->runtime()), scripts(cx), sources(cx), objects(cx), environments(cx) |
|
390 { |
|
391 assertSameCompartment(cx, dbg); |
|
392 |
|
393 cx->runtime()->debuggerList.insertBack(this); |
|
394 JS_INIT_CLIST(&breakpoints); |
|
395 JS_INIT_CLIST(&onNewGlobalObjectWatchersLink); |
|
396 } |
|
397 |
|
398 Debugger::~Debugger() |
|
399 { |
|
400 JS_ASSERT_IF(debuggees.initialized(), debuggees.empty()); |
|
401 |
|
402 /* |
|
403 * Since the inactive state for this link is a singleton cycle, it's always |
|
404 * safe to apply JS_REMOVE_LINK to it, regardless of whether we're in the list or not. |
|
405 * |
|
406 * We don't have to worry about locking here since Debugger is not |
|
407 * background finalized. |
|
408 */ |
|
409 JS_REMOVE_LINK(&onNewGlobalObjectWatchersLink); |
|
410 } |
|
411 |
|
412 bool |
|
413 Debugger::init(JSContext *cx) |
|
414 { |
|
415 bool ok = debuggees.init() && |
|
416 frames.init() && |
|
417 scripts.init() && |
|
418 sources.init() && |
|
419 objects.init() && |
|
420 environments.init(); |
|
421 if (!ok) |
|
422 js_ReportOutOfMemory(cx); |
|
423 return ok; |
|
424 } |
|
425 |
|
426 Debugger * |
|
427 Debugger::fromJSObject(JSObject *obj) |
|
428 { |
|
429 JS_ASSERT(js::GetObjectClass(obj) == &jsclass); |
|
430 return (Debugger *) obj->getPrivate(); |
|
431 } |
|
432 |
|
433 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSCRIPT_OWNER)); |
|
434 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSOURCE_OWNER)); |
|
435 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGOBJECT_OWNER)); |
|
436 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGENV_OWNER)); |
|
437 |
|
438 Debugger * |
|
439 Debugger::fromChildJSObject(JSObject *obj) |
|
440 { |
|
441 JS_ASSERT(obj->getClass() == &DebuggerFrame_class || |
|
442 obj->getClass() == &DebuggerScript_class || |
|
443 obj->getClass() == &DebuggerSource_class || |
|
444 obj->getClass() == &DebuggerObject_class || |
|
445 obj->getClass() == &DebuggerEnv_class); |
|
446 JSObject *dbgobj = &obj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER).toObject(); |
|
447 return fromJSObject(dbgobj); |
|
448 } |
|
449 |
|
450 bool |
|
451 Debugger::getScriptFrameWithIter(JSContext *cx, AbstractFramePtr frame, |
|
452 const ScriptFrameIter *maybeIter, MutableHandleValue vp) |
|
453 { |
|
454 MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == frame); |
|
455 |
|
456 FrameMap::AddPtr p = frames.lookupForAdd(frame); |
|
457 if (!p) { |
|
458 /* Create and populate the Debugger.Frame object. */ |
|
459 JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject(); |
|
460 JSObject *frameobj = |
|
461 NewObjectWithGivenProto(cx, &DebuggerFrame_class, proto, nullptr); |
|
462 if (!frameobj) |
|
463 return false; |
|
464 |
|
465 // Eagerly copy ScriptFrameIter data if we've already walked the |
|
466 // stack. |
|
467 if (maybeIter) { |
|
468 AbstractFramePtr data = maybeIter->copyDataAsAbstractFramePtr(); |
|
469 if (!data) |
|
470 return false; |
|
471 frameobj->setPrivate(data.raw()); |
|
472 } else { |
|
473 frameobj->setPrivate(frame.raw()); |
|
474 } |
|
475 |
|
476 frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*object)); |
|
477 |
|
478 if (!frames.add(p, frame, frameobj)) { |
|
479 js_ReportOutOfMemory(cx); |
|
480 return false; |
|
481 } |
|
482 } |
|
483 vp.setObject(*p->value()); |
|
484 return true; |
|
485 } |
|
486 |
|
487 JSObject * |
|
488 Debugger::getHook(Hook hook) const |
|
489 { |
|
490 JS_ASSERT(hook >= 0 && hook < HookCount); |
|
491 const Value &v = object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + hook); |
|
492 return v.isUndefined() ? nullptr : &v.toObject(); |
|
493 } |
|
494 |
|
495 bool |
|
496 Debugger::hasAnyLiveHooks() const |
|
497 { |
|
498 if (!enabled) |
|
499 return false; |
|
500 |
|
501 if (getHook(OnDebuggerStatement) || |
|
502 getHook(OnExceptionUnwind) || |
|
503 getHook(OnNewScript) || |
|
504 getHook(OnEnterFrame)) |
|
505 { |
|
506 return true; |
|
507 } |
|
508 |
|
509 /* If any breakpoints are in live scripts, return true. */ |
|
510 for (Breakpoint *bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) { |
|
511 if (IsScriptMarked(&bp->site->script)) |
|
512 return true; |
|
513 } |
|
514 |
|
515 for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) { |
|
516 JSObject *frameObj = r.front().value(); |
|
517 if (!frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() || |
|
518 !frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined()) |
|
519 return true; |
|
520 } |
|
521 |
|
522 return false; |
|
523 } |
|
524 |
|
525 JSTrapStatus |
|
526 Debugger::slowPathOnEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) |
|
527 { |
|
528 /* Build the list of recipients. */ |
|
529 AutoValueVector triggered(cx); |
|
530 Handle<GlobalObject*> global = cx->global(); |
|
531 |
|
532 if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) { |
|
533 for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) { |
|
534 Debugger *dbg = *p; |
|
535 if (dbg->observesFrame(frame) && dbg->observesEnterFrame() && |
|
536 !triggered.append(ObjectValue(*dbg->toJSObject()))) |
|
537 { |
|
538 return JSTRAP_ERROR; |
|
539 } |
|
540 } |
|
541 } |
|
542 |
|
543 /* Deliver the event, checking again as in dispatchHook. */ |
|
544 for (Value *p = triggered.begin(); p != triggered.end(); p++) { |
|
545 Debugger *dbg = Debugger::fromJSObject(&p->toObject()); |
|
546 if (dbg->debuggees.has(global) && dbg->observesEnterFrame()) { |
|
547 JSTrapStatus status = dbg->fireEnterFrame(cx, frame, vp); |
|
548 if (status != JSTRAP_CONTINUE) |
|
549 return status; |
|
550 } |
|
551 } |
|
552 |
|
553 return JSTRAP_CONTINUE; |
|
554 } |
|
555 |
|
556 static void |
|
557 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp *fop, AbstractFramePtr frame, |
|
558 JSObject *frameobj); |
|
559 |
|
560 static void |
|
561 DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj); |
|
562 |
|
563 /* |
|
564 * Handle leaving a frame with debuggers watching. |frameOk| indicates whether |
|
565 * the frame is exiting normally or abruptly. Set |cx|'s exception and/or |
|
566 * |cx->fp()|'s return value, and return a new success value. |
|
567 */ |
|
568 bool |
|
569 Debugger::slowPathOnLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool frameOk) |
|
570 { |
|
571 Handle<GlobalObject*> global = cx->global(); |
|
572 |
|
573 /* Save the frame's completion value. */ |
|
574 JSTrapStatus status; |
|
575 RootedValue value(cx); |
|
576 Debugger::resultToCompletion(cx, frameOk, frame.returnValue(), &status, &value); |
|
577 |
|
578 /* Build a list of the recipients. */ |
|
579 AutoObjectVector frames(cx); |
|
580 for (FrameRange r(frame, global); !r.empty(); r.popFront()) { |
|
581 if (!frames.append(r.frontFrame())) { |
|
582 cx->clearPendingException(); |
|
583 return false; |
|
584 } |
|
585 } |
|
586 |
|
587 /* For each Debugger.Frame, fire its onPop handler, if any. */ |
|
588 for (JSObject **p = frames.begin(); p != frames.end(); p++) { |
|
589 RootedObject frameobj(cx, *p); |
|
590 Debugger *dbg = Debugger::fromChildJSObject(frameobj); |
|
591 |
|
592 if (dbg->enabled && |
|
593 !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined()) { |
|
594 RootedValue handler(cx, frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER)); |
|
595 |
|
596 Maybe<AutoCompartment> ac; |
|
597 ac.construct(cx, dbg->object); |
|
598 |
|
599 RootedValue completion(cx); |
|
600 if (!dbg->newCompletionValue(cx, status, value, &completion)) { |
|
601 status = dbg->handleUncaughtException(ac, false); |
|
602 break; |
|
603 } |
|
604 |
|
605 /* Call the onPop handler. */ |
|
606 RootedValue rval(cx); |
|
607 bool hookOk = Invoke(cx, ObjectValue(*frameobj), handler, 1, completion.address(), |
|
608 &rval); |
|
609 RootedValue nextValue(cx); |
|
610 JSTrapStatus nextStatus = dbg->parseResumptionValue(ac, hookOk, rval, &nextValue); |
|
611 |
|
612 /* |
|
613 * At this point, we are back in the debuggee compartment, and any error has |
|
614 * been wrapped up as a completion value. |
|
615 */ |
|
616 JS_ASSERT(cx->compartment() == global->compartment()); |
|
617 JS_ASSERT(!cx->isExceptionPending()); |
|
618 |
|
619 /* JSTRAP_CONTINUE means "make no change". */ |
|
620 if (nextStatus != JSTRAP_CONTINUE) { |
|
621 status = nextStatus; |
|
622 value = nextValue; |
|
623 } |
|
624 } |
|
625 } |
|
626 |
|
627 /* |
|
628 * Clean up all Debugger.Frame instances. Use a fresh FrameRange, as one |
|
629 * debugger's onPop handler could have caused another debugger to create its |
|
630 * own Debugger.Frame instance. |
|
631 */ |
|
632 for (FrameRange r(frame, global); !r.empty(); r.popFront()) { |
|
633 RootedObject frameobj(cx, r.frontFrame()); |
|
634 Debugger *dbg = r.frontDebugger(); |
|
635 JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj)); |
|
636 |
|
637 FreeOp *fop = cx->runtime()->defaultFreeOp(); |
|
638 DebuggerFrame_freeScriptFrameIterData(fop, frameobj); |
|
639 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj); |
|
640 |
|
641 dbg->frames.remove(frame); |
|
642 } |
|
643 |
|
644 /* |
|
645 * If this is an eval frame, then from the debugger's perspective the |
|
646 * script is about to be destroyed. Remove any breakpoints in it. |
|
647 */ |
|
648 if (frame.isEvalFrame()) { |
|
649 RootedScript script(cx, frame.script()); |
|
650 script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), nullptr, nullptr); |
|
651 } |
|
652 |
|
653 /* Establish (status, value) as our resumption value. */ |
|
654 switch (status) { |
|
655 case JSTRAP_RETURN: |
|
656 frame.setReturnValue(value); |
|
657 return true; |
|
658 |
|
659 case JSTRAP_THROW: |
|
660 cx->setPendingException(value); |
|
661 return false; |
|
662 |
|
663 case JSTRAP_ERROR: |
|
664 JS_ASSERT(!cx->isExceptionPending()); |
|
665 return false; |
|
666 |
|
667 default: |
|
668 MOZ_ASSUME_UNREACHABLE("bad final trap status"); |
|
669 } |
|
670 } |
|
671 |
|
672 bool |
|
673 Debugger::wrapEnvironment(JSContext *cx, Handle<Env*> env, MutableHandleValue rval) |
|
674 { |
|
675 if (!env) { |
|
676 rval.setNull(); |
|
677 return true; |
|
678 } |
|
679 |
|
680 /* |
|
681 * DebuggerEnv should only wrap a debug scope chain obtained (transitively) |
|
682 * from GetDebugScopeFor(Frame|Function). |
|
683 */ |
|
684 JS_ASSERT(!env->is<ScopeObject>()); |
|
685 |
|
686 JSObject *envobj; |
|
687 DependentAddPtr<ObjectWeakMap> p(cx, environments, env); |
|
688 if (p) { |
|
689 envobj = p->value(); |
|
690 } else { |
|
691 /* Create a new Debugger.Environment for env. */ |
|
692 JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_ENV_PROTO).toObject(); |
|
693 envobj = NewObjectWithGivenProto(cx, &DebuggerEnv_class, proto, nullptr, TenuredObject); |
|
694 if (!envobj) |
|
695 return false; |
|
696 envobj->setPrivateGCThing(env); |
|
697 envobj->setReservedSlot(JSSLOT_DEBUGENV_OWNER, ObjectValue(*object)); |
|
698 if (!p.add(cx, environments, env, envobj)) { |
|
699 js_ReportOutOfMemory(cx); |
|
700 return false; |
|
701 } |
|
702 |
|
703 CrossCompartmentKey key(CrossCompartmentKey::DebuggerEnvironment, object, env); |
|
704 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) { |
|
705 environments.remove(env); |
|
706 js_ReportOutOfMemory(cx); |
|
707 return false; |
|
708 } |
|
709 } |
|
710 rval.setObject(*envobj); |
|
711 return true; |
|
712 } |
|
713 |
|
714 bool |
|
715 Debugger::wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp) |
|
716 { |
|
717 assertSameCompartment(cx, object.get()); |
|
718 |
|
719 if (vp.isObject()) { |
|
720 RootedObject obj(cx, &vp.toObject()); |
|
721 |
|
722 if (obj->is<JSFunction>()) { |
|
723 RootedFunction fun(cx, &obj->as<JSFunction>()); |
|
724 if (!EnsureFunctionHasScript(cx, fun)) |
|
725 return false; |
|
726 } |
|
727 |
|
728 DependentAddPtr<ObjectWeakMap> p(cx, objects, obj); |
|
729 if (p) { |
|
730 vp.setObject(*p->value()); |
|
731 } else { |
|
732 /* Create a new Debugger.Object for obj. */ |
|
733 JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO).toObject(); |
|
734 JSObject *dobj = |
|
735 NewObjectWithGivenProto(cx, &DebuggerObject_class, proto, nullptr, TenuredObject); |
|
736 if (!dobj) |
|
737 return false; |
|
738 dobj->setPrivateGCThing(obj); |
|
739 dobj->setReservedSlot(JSSLOT_DEBUGOBJECT_OWNER, ObjectValue(*object)); |
|
740 |
|
741 if (!p.add(cx, objects, obj, dobj)) { |
|
742 js_ReportOutOfMemory(cx); |
|
743 return false; |
|
744 } |
|
745 |
|
746 if (obj->compartment() != object->compartment()) { |
|
747 CrossCompartmentKey key(CrossCompartmentKey::DebuggerObject, object, obj); |
|
748 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) { |
|
749 objects.remove(obj); |
|
750 js_ReportOutOfMemory(cx); |
|
751 return false; |
|
752 } |
|
753 } |
|
754 |
|
755 vp.setObject(*dobj); |
|
756 } |
|
757 } else if (vp.isMagic()) { |
|
758 RootedObject optObj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); |
|
759 if (!optObj) |
|
760 return false; |
|
761 |
|
762 // We handle two sentinel values: missing arguments (overloading |
|
763 // JS_OPTIMIZED_ARGUMENTS) and optimized out slots (JS_OPTIMIZED_OUT). |
|
764 // Other magic values should not have escaped. |
|
765 PropertyName *name; |
|
766 if (vp.whyMagic() == JS_OPTIMIZED_ARGUMENTS) { |
|
767 name = cx->names().missingArguments; |
|
768 } else { |
|
769 MOZ_ASSERT(vp.whyMagic() == JS_OPTIMIZED_OUT); |
|
770 name = cx->names().optimizedOut; |
|
771 } |
|
772 |
|
773 RootedValue trueVal(cx, BooleanValue(true)); |
|
774 if (!JSObject::defineProperty(cx, optObj, name, trueVal)) |
|
775 return false; |
|
776 |
|
777 vp.setObject(*optObj); |
|
778 } else if (!cx->compartment()->wrap(cx, vp)) { |
|
779 vp.setUndefined(); |
|
780 return false; |
|
781 } |
|
782 |
|
783 return true; |
|
784 } |
|
785 |
|
786 bool |
|
787 Debugger::unwrapDebuggeeValue(JSContext *cx, MutableHandleValue vp) |
|
788 { |
|
789 assertSameCompartment(cx, object.get(), vp); |
|
790 if (vp.isObject()) { |
|
791 JSObject *dobj = &vp.toObject(); |
|
792 if (dobj->getClass() != &DebuggerObject_class) { |
|
793 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, |
|
794 "Debugger", "Debugger.Object", dobj->getClass()->name); |
|
795 return false; |
|
796 } |
|
797 |
|
798 Value owner = dobj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER); |
|
799 if (owner.isUndefined() || &owner.toObject() != object) { |
|
800 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
801 owner.isUndefined() |
|
802 ? JSMSG_DEBUG_OBJECT_PROTO |
|
803 : JSMSG_DEBUG_OBJECT_WRONG_OWNER); |
|
804 return false; |
|
805 } |
|
806 |
|
807 vp.setObject(*static_cast<JSObject*>(dobj->getPrivate())); |
|
808 } |
|
809 return true; |
|
810 } |
|
811 |
|
812 JSTrapStatus |
|
813 Debugger::handleUncaughtExceptionHelper(Maybe<AutoCompartment> &ac, |
|
814 MutableHandleValue *vp, bool callHook) |
|
815 { |
|
816 JSContext *cx = ac.ref().context()->asJSContext(); |
|
817 if (cx->isExceptionPending()) { |
|
818 if (callHook && uncaughtExceptionHook) { |
|
819 RootedValue exc(cx); |
|
820 if (!cx->getPendingException(&exc)) |
|
821 return JSTRAP_ERROR; |
|
822 cx->clearPendingException(); |
|
823 RootedValue fval(cx, ObjectValue(*uncaughtExceptionHook)); |
|
824 RootedValue rv(cx); |
|
825 if (Invoke(cx, ObjectValue(*object), fval, 1, exc.address(), &rv)) |
|
826 return vp ? parseResumptionValue(ac, true, rv, *vp, false) : JSTRAP_CONTINUE; |
|
827 } |
|
828 |
|
829 if (cx->isExceptionPending()) { |
|
830 JS_ReportPendingException(cx); |
|
831 cx->clearPendingException(); |
|
832 } |
|
833 } |
|
834 ac.destroy(); |
|
835 return JSTRAP_ERROR; |
|
836 } |
|
837 |
|
838 JSTrapStatus |
|
839 Debugger::handleUncaughtException(Maybe<AutoCompartment> &ac, MutableHandleValue vp, bool callHook) |
|
840 { |
|
841 return handleUncaughtExceptionHelper(ac, &vp, callHook); |
|
842 } |
|
843 |
|
844 JSTrapStatus |
|
845 Debugger::handleUncaughtException(Maybe<AutoCompartment> &ac, bool callHook) |
|
846 { |
|
847 return handleUncaughtExceptionHelper(ac, nullptr, callHook); |
|
848 } |
|
849 |
|
850 void |
|
851 Debugger::resultToCompletion(JSContext *cx, bool ok, const Value &rv, |
|
852 JSTrapStatus *status, MutableHandleValue value) |
|
853 { |
|
854 JS_ASSERT_IF(ok, !cx->isExceptionPending()); |
|
855 |
|
856 if (ok) { |
|
857 *status = JSTRAP_RETURN; |
|
858 value.set(rv); |
|
859 } else if (cx->isExceptionPending()) { |
|
860 *status = JSTRAP_THROW; |
|
861 if (!cx->getPendingException(value)) |
|
862 *status = JSTRAP_ERROR; |
|
863 cx->clearPendingException(); |
|
864 } else { |
|
865 *status = JSTRAP_ERROR; |
|
866 value.setUndefined(); |
|
867 } |
|
868 } |
|
869 |
|
870 bool |
|
871 Debugger::newCompletionValue(JSContext *cx, JSTrapStatus status, Value value_, |
|
872 MutableHandleValue result) |
|
873 { |
|
874 /* |
|
875 * We must be in the debugger's compartment, since that's where we want |
|
876 * to construct the completion value. |
|
877 */ |
|
878 assertSameCompartment(cx, object.get()); |
|
879 |
|
880 RootedId key(cx); |
|
881 RootedValue value(cx, value_); |
|
882 |
|
883 switch (status) { |
|
884 case JSTRAP_RETURN: |
|
885 key = NameToId(cx->names().return_); |
|
886 break; |
|
887 |
|
888 case JSTRAP_THROW: |
|
889 key = NameToId(cx->names().throw_); |
|
890 break; |
|
891 |
|
892 case JSTRAP_ERROR: |
|
893 result.setNull(); |
|
894 return true; |
|
895 |
|
896 default: |
|
897 MOZ_ASSUME_UNREACHABLE("bad status passed to Debugger::newCompletionValue"); |
|
898 } |
|
899 |
|
900 /* Common tail for JSTRAP_RETURN and JSTRAP_THROW. */ |
|
901 RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); |
|
902 if (!obj || |
|
903 !wrapDebuggeeValue(cx, &value) || |
|
904 !DefineNativeProperty(cx, obj, key, value, JS_PropertyStub, JS_StrictPropertyStub, |
|
905 JSPROP_ENUMERATE)) |
|
906 { |
|
907 return false; |
|
908 } |
|
909 |
|
910 result.setObject(*obj); |
|
911 return true; |
|
912 } |
|
913 |
|
914 bool |
|
915 Debugger::receiveCompletionValue(Maybe<AutoCompartment> &ac, bool ok, |
|
916 HandleValue val, |
|
917 MutableHandleValue vp) |
|
918 { |
|
919 JSContext *cx = ac.ref().context()->asJSContext(); |
|
920 |
|
921 JSTrapStatus status; |
|
922 RootedValue value(cx); |
|
923 resultToCompletion(cx, ok, val, &status, &value); |
|
924 ac.destroy(); |
|
925 return newCompletionValue(cx, status, value, vp); |
|
926 } |
|
927 |
|
928 JSTrapStatus |
|
929 Debugger::parseResumptionValue(Maybe<AutoCompartment> &ac, bool ok, const Value &rv, MutableHandleValue vp, |
|
930 bool callHook) |
|
931 { |
|
932 vp.setUndefined(); |
|
933 if (!ok) |
|
934 return handleUncaughtException(ac, vp, callHook); |
|
935 if (rv.isUndefined()) { |
|
936 ac.destroy(); |
|
937 return JSTRAP_CONTINUE; |
|
938 } |
|
939 if (rv.isNull()) { |
|
940 ac.destroy(); |
|
941 return JSTRAP_ERROR; |
|
942 } |
|
943 |
|
944 /* Check that rv is {return: val} or {throw: val}. */ |
|
945 JSContext *cx = ac.ref().context()->asJSContext(); |
|
946 Rooted<JSObject*> obj(cx); |
|
947 RootedShape shape(cx); |
|
948 RootedId returnId(cx, NameToId(cx->names().return_)); |
|
949 RootedId throwId(cx, NameToId(cx->names().throw_)); |
|
950 bool okResumption = rv.isObject(); |
|
951 if (okResumption) { |
|
952 obj = &rv.toObject(); |
|
953 okResumption = obj->is<JSObject>(); |
|
954 } |
|
955 if (okResumption) { |
|
956 shape = obj->lastProperty(); |
|
957 okResumption = shape->previous() && |
|
958 !shape->previous()->previous() && |
|
959 (shape->propid() == returnId || shape->propid() == throwId) && |
|
960 shape->isDataDescriptor(); |
|
961 } |
|
962 if (!okResumption) { |
|
963 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_RESUMPTION); |
|
964 return handleUncaughtException(ac, vp, callHook); |
|
965 } |
|
966 |
|
967 RootedValue v(cx, vp.get()); |
|
968 if (!NativeGet(cx, obj, obj, shape, &v) || !unwrapDebuggeeValue(cx, &v)) |
|
969 return handleUncaughtException(ac, &v, callHook); |
|
970 |
|
971 ac.destroy(); |
|
972 if (!cx->compartment()->wrap(cx, &v)) { |
|
973 vp.setUndefined(); |
|
974 return JSTRAP_ERROR; |
|
975 } |
|
976 vp.set(v); |
|
977 |
|
978 return shape->propid() == returnId ? JSTRAP_RETURN : JSTRAP_THROW; |
|
979 } |
|
980 |
|
981 static bool |
|
982 CallMethodIfPresent(JSContext *cx, HandleObject obj, const char *name, int argc, Value *argv, |
|
983 MutableHandleValue rval) |
|
984 { |
|
985 rval.setUndefined(); |
|
986 JSAtom *atom = Atomize(cx, name, strlen(name)); |
|
987 if (!atom) |
|
988 return false; |
|
989 |
|
990 RootedId id(cx, AtomToId(atom)); |
|
991 RootedValue fval(cx); |
|
992 return JSObject::getGeneric(cx, obj, obj, id, &fval) && |
|
993 (!js_IsCallable(fval) || Invoke(cx, ObjectValue(*obj), fval, argc, argv, rval)); |
|
994 } |
|
995 |
|
996 JSTrapStatus |
|
997 Debugger::fireDebuggerStatement(JSContext *cx, MutableHandleValue vp) |
|
998 { |
|
999 RootedObject hook(cx, getHook(OnDebuggerStatement)); |
|
1000 JS_ASSERT(hook); |
|
1001 JS_ASSERT(hook->isCallable()); |
|
1002 |
|
1003 Maybe<AutoCompartment> ac; |
|
1004 ac.construct(cx, object); |
|
1005 |
|
1006 ScriptFrameIter iter(cx); |
|
1007 |
|
1008 RootedValue scriptFrame(cx); |
|
1009 if (!getScriptFrame(cx, iter, &scriptFrame)) |
|
1010 return handleUncaughtException(ac, false); |
|
1011 |
|
1012 RootedValue rv(cx); |
|
1013 bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv); |
|
1014 return parseResumptionValue(ac, ok, rv, vp); |
|
1015 } |
|
1016 |
|
1017 JSTrapStatus |
|
1018 Debugger::fireExceptionUnwind(JSContext *cx, MutableHandleValue vp) |
|
1019 { |
|
1020 RootedObject hook(cx, getHook(OnExceptionUnwind)); |
|
1021 JS_ASSERT(hook); |
|
1022 JS_ASSERT(hook->isCallable()); |
|
1023 |
|
1024 RootedValue exc(cx); |
|
1025 if (!cx->getPendingException(&exc)) |
|
1026 return JSTRAP_ERROR; |
|
1027 cx->clearPendingException(); |
|
1028 |
|
1029 Maybe<AutoCompartment> ac; |
|
1030 ac.construct(cx, object); |
|
1031 |
|
1032 JS::AutoValueArray<2> argv(cx); |
|
1033 argv[0].setUndefined(); |
|
1034 argv[1].set(exc); |
|
1035 |
|
1036 ScriptFrameIter iter(cx); |
|
1037 if (!getScriptFrame(cx, iter, argv[0]) || !wrapDebuggeeValue(cx, argv[1])) |
|
1038 return handleUncaughtException(ac, false); |
|
1039 |
|
1040 RootedValue rv(cx); |
|
1041 bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 2, argv.begin(), &rv); |
|
1042 JSTrapStatus st = parseResumptionValue(ac, ok, rv, vp); |
|
1043 if (st == JSTRAP_CONTINUE) |
|
1044 cx->setPendingException(exc); |
|
1045 return st; |
|
1046 } |
|
1047 |
|
1048 JSTrapStatus |
|
1049 Debugger::fireEnterFrame(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp) |
|
1050 { |
|
1051 RootedObject hook(cx, getHook(OnEnterFrame)); |
|
1052 JS_ASSERT(hook); |
|
1053 JS_ASSERT(hook->isCallable()); |
|
1054 |
|
1055 Maybe<AutoCompartment> ac; |
|
1056 ac.construct(cx, object); |
|
1057 |
|
1058 RootedValue scriptFrame(cx); |
|
1059 if (!getScriptFrame(cx, frame, &scriptFrame)) |
|
1060 return handleUncaughtException(ac, false); |
|
1061 |
|
1062 RootedValue rv(cx); |
|
1063 bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv); |
|
1064 return parseResumptionValue(ac, ok, rv, vp); |
|
1065 } |
|
1066 |
|
1067 void |
|
1068 Debugger::fireNewScript(JSContext *cx, HandleScript script) |
|
1069 { |
|
1070 RootedObject hook(cx, getHook(OnNewScript)); |
|
1071 JS_ASSERT(hook); |
|
1072 JS_ASSERT(hook->isCallable()); |
|
1073 |
|
1074 Maybe<AutoCompartment> ac; |
|
1075 ac.construct(cx, object); |
|
1076 |
|
1077 JSObject *dsobj = wrapScript(cx, script); |
|
1078 if (!dsobj) { |
|
1079 handleUncaughtException(ac, false); |
|
1080 return; |
|
1081 } |
|
1082 |
|
1083 RootedValue scriptObject(cx, ObjectValue(*dsobj)); |
|
1084 RootedValue rv(cx); |
|
1085 if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptObject.address(), &rv)) |
|
1086 handleUncaughtException(ac, true); |
|
1087 } |
|
1088 |
|
1089 JSTrapStatus |
|
1090 Debugger::dispatchHook(JSContext *cx, MutableHandleValue vp, Hook which) |
|
1091 { |
|
1092 JS_ASSERT(which == OnDebuggerStatement || which == OnExceptionUnwind); |
|
1093 |
|
1094 /* |
|
1095 * Determine which debuggers will receive this event, and in what order. |
|
1096 * Make a copy of the list, since the original is mutable and we will be |
|
1097 * calling into arbitrary JS. |
|
1098 * |
|
1099 * Note: In the general case, 'triggered' contains references to objects in |
|
1100 * different compartments--every compartment *except* this one. |
|
1101 */ |
|
1102 AutoValueVector triggered(cx); |
|
1103 Handle<GlobalObject*> global = cx->global(); |
|
1104 if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) { |
|
1105 for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) { |
|
1106 Debugger *dbg = *p; |
|
1107 if (dbg->enabled && dbg->getHook(which)) { |
|
1108 if (!triggered.append(ObjectValue(*dbg->toJSObject()))) |
|
1109 return JSTRAP_ERROR; |
|
1110 } |
|
1111 } |
|
1112 } |
|
1113 |
|
1114 /* |
|
1115 * Deliver the event to each debugger, checking again to make sure it |
|
1116 * should still be delivered. |
|
1117 */ |
|
1118 for (Value *p = triggered.begin(); p != triggered.end(); p++) { |
|
1119 Debugger *dbg = Debugger::fromJSObject(&p->toObject()); |
|
1120 if (dbg->debuggees.has(global) && dbg->enabled && dbg->getHook(which)) { |
|
1121 JSTrapStatus st = (which == OnDebuggerStatement) |
|
1122 ? dbg->fireDebuggerStatement(cx, vp) |
|
1123 : dbg->fireExceptionUnwind(cx, vp); |
|
1124 if (st != JSTRAP_CONTINUE) |
|
1125 return st; |
|
1126 } |
|
1127 } |
|
1128 return JSTRAP_CONTINUE; |
|
1129 } |
|
1130 |
|
1131 static bool |
|
1132 AddNewScriptRecipients(GlobalObject::DebuggerVector *src, HandleScript script, |
|
1133 AutoValueVector *dest) |
|
1134 { |
|
1135 bool wasEmpty = dest->length() == 0; |
|
1136 for (Debugger **p = src->begin(); p != src->end(); p++) { |
|
1137 Debugger *dbg = *p; |
|
1138 Value v = ObjectValue(*dbg->toJSObject()); |
|
1139 if (dbg->observesScript(script) && dbg->observesNewScript() && |
|
1140 (wasEmpty || Find(dest->begin(), dest->end(), v) == dest->end()) && |
|
1141 !dest->append(v)) |
|
1142 { |
|
1143 return false; |
|
1144 } |
|
1145 } |
|
1146 return true; |
|
1147 } |
|
1148 |
|
1149 void |
|
1150 Debugger::slowPathOnNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal_) |
|
1151 { |
|
1152 Rooted<GlobalObject*> compileAndGoGlobal(cx, compileAndGoGlobal_); |
|
1153 |
|
1154 JS_ASSERT(script->compileAndGo() == !!compileAndGoGlobal); |
|
1155 |
|
1156 /* |
|
1157 * Build the list of recipients. For compile-and-go scripts, this is the |
|
1158 * same as the generic Debugger::dispatchHook code, but non-compile-and-go |
|
1159 * scripts are not tied to particular globals. We deliver them to every |
|
1160 * debugger observing any global in the script's compartment. |
|
1161 */ |
|
1162 AutoValueVector triggered(cx); |
|
1163 if (script->compileAndGo()) { |
|
1164 if (GlobalObject::DebuggerVector *debuggers = compileAndGoGlobal->getDebuggers()) { |
|
1165 if (!AddNewScriptRecipients(debuggers, script, &triggered)) |
|
1166 return; |
|
1167 } |
|
1168 } else { |
|
1169 GlobalObjectSet &debuggees = script->compartment()->getDebuggees(); |
|
1170 for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) { |
|
1171 if (!AddNewScriptRecipients(r.front()->getDebuggers(), script, &triggered)) |
|
1172 return; |
|
1173 } |
|
1174 } |
|
1175 |
|
1176 /* |
|
1177 * Deliver the event to each debugger, checking again as in |
|
1178 * Debugger::dispatchHook. |
|
1179 */ |
|
1180 for (Value *p = triggered.begin(); p != triggered.end(); p++) { |
|
1181 Debugger *dbg = Debugger::fromJSObject(&p->toObject()); |
|
1182 if ((!compileAndGoGlobal || dbg->debuggees.has(compileAndGoGlobal)) && |
|
1183 dbg->enabled && dbg->getHook(OnNewScript)) { |
|
1184 dbg->fireNewScript(cx, script); |
|
1185 } |
|
1186 } |
|
1187 } |
|
1188 |
|
1189 JSTrapStatus |
|
1190 Debugger::onTrap(JSContext *cx, MutableHandleValue vp) |
|
1191 { |
|
1192 MOZ_ASSERT(cx->compartment()->debugMode()); |
|
1193 |
|
1194 ScriptFrameIter iter(cx); |
|
1195 RootedScript script(cx, iter.script()); |
|
1196 Rooted<GlobalObject*> scriptGlobal(cx, &script->global()); |
|
1197 jsbytecode *pc = iter.pc(); |
|
1198 BreakpointSite *site = script->getBreakpointSite(pc); |
|
1199 JSOp op = JSOp(*pc); |
|
1200 |
|
1201 /* Build list of breakpoint handlers. */ |
|
1202 Vector<Breakpoint *> triggered(cx); |
|
1203 for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) { |
|
1204 if (!triggered.append(bp)) |
|
1205 return JSTRAP_ERROR; |
|
1206 } |
|
1207 |
|
1208 for (Breakpoint **p = triggered.begin(); p != triggered.end(); p++) { |
|
1209 Breakpoint *bp = *p; |
|
1210 |
|
1211 /* Handlers can clear breakpoints. Check that bp still exists. */ |
|
1212 if (!site || !site->hasBreakpoint(bp)) |
|
1213 continue; |
|
1214 |
|
1215 |
|
1216 /* |
|
1217 * There are two reasons we have to check whether dbg is enabled and |
|
1218 * debugging scriptGlobal. |
|
1219 * |
|
1220 * One is just that one breakpoint handler can disable other Debuggers |
|
1221 * or remove debuggees. |
|
1222 * |
|
1223 * The other has to do with non-compile-and-go scripts, which have no |
|
1224 * specific global--until they are executed. Only now do we know which |
|
1225 * global the script is running against. |
|
1226 */ |
|
1227 Debugger *dbg = bp->debugger; |
|
1228 if (dbg->enabled && dbg->debuggees.lookup(scriptGlobal)) { |
|
1229 Maybe<AutoCompartment> ac; |
|
1230 ac.construct(cx, dbg->object); |
|
1231 |
|
1232 RootedValue scriptFrame(cx); |
|
1233 if (!dbg->getScriptFrame(cx, iter, &scriptFrame)) |
|
1234 return dbg->handleUncaughtException(ac, false); |
|
1235 RootedValue rv(cx); |
|
1236 Rooted<JSObject*> handler(cx, bp->handler); |
|
1237 bool ok = CallMethodIfPresent(cx, handler, "hit", 1, scriptFrame.address(), &rv); |
|
1238 JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rv, vp, true); |
|
1239 if (st != JSTRAP_CONTINUE) |
|
1240 return st; |
|
1241 |
|
1242 /* Calling JS code invalidates site. Reload it. */ |
|
1243 site = script->getBreakpointSite(pc); |
|
1244 } |
|
1245 } |
|
1246 |
|
1247 if (site && site->trapHandler) { |
|
1248 JSTrapStatus st = site->trapHandler(cx, script, pc, vp.address(), site->trapClosure); |
|
1249 if (st != JSTRAP_CONTINUE) |
|
1250 return st; |
|
1251 } |
|
1252 |
|
1253 /* By convention, return the true op to the interpreter in vp. */ |
|
1254 vp.setInt32(op); |
|
1255 return JSTRAP_CONTINUE; |
|
1256 } |
|
1257 |
|
1258 JSTrapStatus |
|
1259 Debugger::onSingleStep(JSContext *cx, MutableHandleValue vp) |
|
1260 { |
|
1261 ScriptFrameIter iter(cx); |
|
1262 |
|
1263 /* |
|
1264 * We may be stepping over a JSOP_EXCEPTION, that pushes the context's |
|
1265 * pending exception for a 'catch' clause to handle. Don't let the |
|
1266 * onStep handlers mess with that (other than by returning a resumption |
|
1267 * value). |
|
1268 */ |
|
1269 RootedValue exception(cx, UndefinedValue()); |
|
1270 bool exceptionPending = cx->isExceptionPending(); |
|
1271 if (exceptionPending) { |
|
1272 if (!cx->getPendingException(&exception)) |
|
1273 return JSTRAP_ERROR; |
|
1274 cx->clearPendingException(); |
|
1275 } |
|
1276 |
|
1277 /* |
|
1278 * Build list of Debugger.Frame instances referring to this frame with |
|
1279 * onStep handlers. |
|
1280 */ |
|
1281 AutoObjectVector frames(cx); |
|
1282 for (FrameRange r(iter.abstractFramePtr()); !r.empty(); r.popFront()) { |
|
1283 JSObject *frame = r.frontFrame(); |
|
1284 if (!frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() && |
|
1285 !frames.append(frame)) |
|
1286 { |
|
1287 return JSTRAP_ERROR; |
|
1288 } |
|
1289 } |
|
1290 |
|
1291 #ifdef DEBUG |
|
1292 /* |
|
1293 * Validate the single-step count on this frame's script, to ensure that |
|
1294 * we're not receiving traps we didn't ask for. Even when frames is |
|
1295 * non-empty (and thus we know this trap was requested), do the check |
|
1296 * anyway, to make sure the count has the correct non-zero value. |
|
1297 * |
|
1298 * The converse --- ensuring that we do receive traps when we should --- can |
|
1299 * be done with unit tests. |
|
1300 */ |
|
1301 { |
|
1302 uint32_t stepperCount = 0; |
|
1303 JSScript *trappingScript = iter.script(); |
|
1304 GlobalObject *global = cx->global(); |
|
1305 if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) { |
|
1306 for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) { |
|
1307 Debugger *dbg = *p; |
|
1308 for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) { |
|
1309 AbstractFramePtr frame = r.front().key(); |
|
1310 JSObject *frameobj = r.front().value(); |
|
1311 if (frame.script() == trappingScript && |
|
1312 !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined()) |
|
1313 { |
|
1314 stepperCount++; |
|
1315 } |
|
1316 } |
|
1317 } |
|
1318 } |
|
1319 if (trappingScript->compileAndGo()) |
|
1320 JS_ASSERT(stepperCount == trappingScript->stepModeCount()); |
|
1321 else |
|
1322 JS_ASSERT(stepperCount <= trappingScript->stepModeCount()); |
|
1323 } |
|
1324 #endif |
|
1325 |
|
1326 /* Preserve the debuggee's iterValue while handlers run. */ |
|
1327 class PreserveIterValue { |
|
1328 JSContext *cx; |
|
1329 RootedValue savedIterValue; |
|
1330 |
|
1331 public: |
|
1332 PreserveIterValue(JSContext *cx) : cx(cx), savedIterValue(cx, cx->iterValue) { |
|
1333 cx->iterValue.setMagic(JS_NO_ITER_VALUE); |
|
1334 } |
|
1335 ~PreserveIterValue() { |
|
1336 cx->iterValue = savedIterValue; |
|
1337 } |
|
1338 }; |
|
1339 PreserveIterValue piv(cx); |
|
1340 |
|
1341 /* Call all the onStep handlers we found. */ |
|
1342 for (JSObject **p = frames.begin(); p != frames.end(); p++) { |
|
1343 RootedObject frame(cx, *p); |
|
1344 Debugger *dbg = Debugger::fromChildJSObject(frame); |
|
1345 |
|
1346 Maybe<AutoCompartment> ac; |
|
1347 ac.construct(cx, dbg->object); |
|
1348 |
|
1349 const Value &handler = frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER); |
|
1350 RootedValue rval(cx); |
|
1351 bool ok = Invoke(cx, ObjectValue(*frame), handler, 0, nullptr, &rval); |
|
1352 JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rval, vp); |
|
1353 if (st != JSTRAP_CONTINUE) |
|
1354 return st; |
|
1355 } |
|
1356 |
|
1357 vp.setUndefined(); |
|
1358 if (exceptionPending) |
|
1359 cx->setPendingException(exception); |
|
1360 return JSTRAP_CONTINUE; |
|
1361 } |
|
1362 |
|
1363 JSTrapStatus |
|
1364 Debugger::fireNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global, MutableHandleValue vp) |
|
1365 { |
|
1366 RootedObject hook(cx, getHook(OnNewGlobalObject)); |
|
1367 JS_ASSERT(hook); |
|
1368 JS_ASSERT(hook->isCallable()); |
|
1369 |
|
1370 Maybe<AutoCompartment> ac; |
|
1371 ac.construct(cx, object); |
|
1372 |
|
1373 RootedValue wrappedGlobal(cx, ObjectValue(*global)); |
|
1374 if (!wrapDebuggeeValue(cx, &wrappedGlobal)) |
|
1375 return handleUncaughtException(ac, false); |
|
1376 |
|
1377 RootedValue rv(cx); |
|
1378 |
|
1379 // onNewGlobalObject is infallible, and thus is only allowed to return |
|
1380 // undefined as a resumption value. If it returns anything else, we throw. |
|
1381 // And if that happens, or if the hook itself throws, we invoke the |
|
1382 // uncaughtExceptionHook so that we never leave an exception pending on the |
|
1383 // cx. This allows JS_NewGlobalObject to avoid handling failures from debugger |
|
1384 // hooks. |
|
1385 bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, wrappedGlobal.address(), &rv); |
|
1386 if (ok && !rv.isUndefined()) { |
|
1387 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
1388 JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED); |
|
1389 ok = false; |
|
1390 } |
|
1391 // NB: Even though we don't care about what goes into it, we have to pass vp |
|
1392 // to handleUncaughtException so that it parses resumption values from the |
|
1393 // uncaughtExceptionHook and tells the caller whether we should execute the |
|
1394 // rest of the onNewGlobalObject hooks or not. |
|
1395 JSTrapStatus status = ok ? JSTRAP_CONTINUE |
|
1396 : handleUncaughtException(ac, vp, true); |
|
1397 JS_ASSERT(!cx->isExceptionPending()); |
|
1398 return status; |
|
1399 } |
|
1400 |
|
1401 void |
|
1402 Debugger::slowPathOnNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global) |
|
1403 { |
|
1404 JS_ASSERT(!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers)); |
|
1405 if (global->compartment()->options().invisibleToDebugger()) |
|
1406 return; |
|
1407 |
|
1408 /* |
|
1409 * Make a copy of the runtime's onNewGlobalObjectWatchers before running the |
|
1410 * handlers. Since one Debugger's handler can disable another's, the list |
|
1411 * can be mutated while we're walking it. |
|
1412 */ |
|
1413 AutoObjectVector watchers(cx); |
|
1414 for (JSCList *link = JS_LIST_HEAD(&cx->runtime()->onNewGlobalObjectWatchers); |
|
1415 link != &cx->runtime()->onNewGlobalObjectWatchers; |
|
1416 link = JS_NEXT_LINK(link)) { |
|
1417 Debugger *dbg = fromOnNewGlobalObjectWatchersLink(link); |
|
1418 JS_ASSERT(dbg->observesNewGlobalObject()); |
|
1419 if (!watchers.append(dbg->object)) |
|
1420 return; |
|
1421 } |
|
1422 |
|
1423 JSTrapStatus status = JSTRAP_CONTINUE; |
|
1424 RootedValue value(cx); |
|
1425 |
|
1426 for (size_t i = 0; i < watchers.length(); i++) { |
|
1427 Debugger *dbg = fromJSObject(watchers[i]); |
|
1428 |
|
1429 // We disallow resumption values from onNewGlobalObject hooks, because we |
|
1430 // want the debugger hooks for global object creation to be infallible. |
|
1431 // But if an onNewGlobalObject hook throws, and the uncaughtExceptionHook |
|
1432 // decides to raise an error, we want to at least avoid invoking the rest |
|
1433 // of the onNewGlobalObject handlers in the list (not for any super |
|
1434 // compelling reason, just because it seems like the right thing to do). |
|
1435 // So we ignore whatever comes out in |value|, but break out of the loop |
|
1436 // if a non-success trap status is returned. |
|
1437 if (dbg->observesNewGlobalObject()) { |
|
1438 status = dbg->fireNewGlobalObject(cx, global, &value); |
|
1439 if (status != JSTRAP_CONTINUE && status != JSTRAP_RETURN) |
|
1440 break; |
|
1441 } |
|
1442 } |
|
1443 JS_ASSERT(!cx->isExceptionPending()); |
|
1444 } |
|
1445 |
|
1446 |
|
1447 /*** Debugger JSObjects **************************************************************************/ |
|
1448 |
|
1449 void |
|
1450 Debugger::markKeysInCompartment(JSTracer *trc) |
|
1451 { |
|
1452 /* |
|
1453 * WeakMap::Range is deliberately private, to discourage C++ code from |
|
1454 * enumerating WeakMap keys. However in this case we need access, so we |
|
1455 * make a base-class reference. Range is public in HashMap. |
|
1456 */ |
|
1457 objects.markKeys(trc); |
|
1458 environments.markKeys(trc); |
|
1459 scripts.markKeys(trc); |
|
1460 sources.markKeys(trc); |
|
1461 } |
|
1462 |
|
1463 /* |
|
1464 * Ordinarily, WeakMap keys and values are marked because at some point it was |
|
1465 * discovered that the WeakMap was live; that is, some object containing the |
|
1466 * WeakMap was marked during mark phase. |
|
1467 * |
|
1468 * However, during compartment GC, we have to do something about |
|
1469 * cross-compartment WeakMaps in non-GC'd compartments. If their keys and values |
|
1470 * might need to be marked, we have to do it manually. |
|
1471 * |
|
1472 * Each Debugger object keeps found cross-compartment WeakMaps: objects, scripts, |
|
1473 * script source objects, and environments. They have the nice property that all |
|
1474 * their values are in the same compartment as the Debugger object, so we only |
|
1475 * need to mark the keys. We must simply mark all keys that are in a compartment |
|
1476 * being GC'd. |
|
1477 * |
|
1478 * We must scan all Debugger objects regardless of whether they *currently* |
|
1479 * have any debuggees in a compartment being GC'd, because the WeakMap |
|
1480 * entries persist even when debuggees are removed. |
|
1481 * |
|
1482 * This happens during the initial mark phase, not iterative marking, because |
|
1483 * all the edges being reported here are strong references. |
|
1484 */ |
|
1485 void |
|
1486 Debugger::markCrossCompartmentDebuggerObjectReferents(JSTracer *trc) |
|
1487 { |
|
1488 JSRuntime *rt = trc->runtime(); |
|
1489 |
|
1490 /* |
|
1491 * Mark all objects in comp that are referents of Debugger.Objects in other |
|
1492 * compartments. |
|
1493 */ |
|
1494 for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) { |
|
1495 if (!dbg->object->zone()->isCollecting()) |
|
1496 dbg->markKeysInCompartment(trc); |
|
1497 } |
|
1498 } |
|
1499 |
|
1500 /* |
|
1501 * This method has two tasks: |
|
1502 * 1. Mark Debugger objects that are unreachable except for debugger hooks that |
|
1503 * may yet be called. |
|
1504 * 2. Mark breakpoint handlers. |
|
1505 * |
|
1506 * This happens during the iterative part of the GC mark phase. This method |
|
1507 * returns true if it has to mark anything; GC calls it repeatedly until it |
|
1508 * returns false. |
|
1509 */ |
|
1510 bool |
|
1511 Debugger::markAllIteratively(GCMarker *trc) |
|
1512 { |
|
1513 bool markedAny = false; |
|
1514 |
|
1515 /* |
|
1516 * Find all Debugger objects in danger of GC. This code is a little |
|
1517 * convoluted since the easiest way to find them is via their debuggees. |
|
1518 */ |
|
1519 JSRuntime *rt = trc->runtime(); |
|
1520 for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { |
|
1521 GlobalObjectSet &debuggees = c->getDebuggees(); |
|
1522 for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { |
|
1523 GlobalObject *global = e.front(); |
|
1524 if (!IsObjectMarked(&global)) |
|
1525 continue; |
|
1526 else if (global != e.front()) |
|
1527 e.rekeyFront(global); |
|
1528 |
|
1529 /* |
|
1530 * Every debuggee has at least one debugger, so in this case |
|
1531 * getDebuggers can't return nullptr. |
|
1532 */ |
|
1533 const GlobalObject::DebuggerVector *debuggers = global->getDebuggers(); |
|
1534 JS_ASSERT(debuggers); |
|
1535 for (Debugger * const *p = debuggers->begin(); p != debuggers->end(); p++) { |
|
1536 Debugger *dbg = *p; |
|
1537 |
|
1538 /* |
|
1539 * dbg is a Debugger with at least one debuggee. Check three things: |
|
1540 * - dbg is actually in a compartment that is being marked |
|
1541 * - it isn't already marked |
|
1542 * - it actually has hooks that might be called |
|
1543 */ |
|
1544 HeapPtrObject &dbgobj = dbg->toJSObjectRef(); |
|
1545 if (!dbgobj->zone()->isGCMarking()) |
|
1546 continue; |
|
1547 |
|
1548 bool dbgMarked = IsObjectMarked(&dbgobj); |
|
1549 if (!dbgMarked && dbg->hasAnyLiveHooks()) { |
|
1550 /* |
|
1551 * obj could be reachable only via its live, enabled |
|
1552 * debugger hooks, which may yet be called. |
|
1553 */ |
|
1554 MarkObject(trc, &dbgobj, "enabled Debugger"); |
|
1555 markedAny = true; |
|
1556 dbgMarked = true; |
|
1557 } |
|
1558 |
|
1559 if (dbgMarked) { |
|
1560 /* Search for breakpoints to mark. */ |
|
1561 for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) { |
|
1562 if (IsScriptMarked(&bp->site->script)) { |
|
1563 /* |
|
1564 * The debugger and the script are both live. |
|
1565 * Therefore the breakpoint handler is live. |
|
1566 */ |
|
1567 if (!IsObjectMarked(&bp->getHandlerRef())) { |
|
1568 MarkObject(trc, &bp->getHandlerRef(), "breakpoint handler"); |
|
1569 markedAny = true; |
|
1570 } |
|
1571 } |
|
1572 } |
|
1573 } |
|
1574 } |
|
1575 } |
|
1576 } |
|
1577 return markedAny; |
|
1578 } |
|
1579 |
|
1580 /* |
|
1581 * Mark all debugger-owned GC things unconditionally. This is used by the minor |
|
1582 * GC: the minor GC cannot apply the weak constraints of the full GC because it |
|
1583 * visits only part of the heap. |
|
1584 */ |
|
1585 void |
|
1586 Debugger::markAll(JSTracer *trc) |
|
1587 { |
|
1588 JSRuntime *rt = trc->runtime(); |
|
1589 for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) { |
|
1590 GlobalObjectSet &debuggees = dbg->debuggees; |
|
1591 for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { |
|
1592 GlobalObject *global = e.front(); |
|
1593 |
|
1594 MarkObjectUnbarriered(trc, &global, "Global Object"); |
|
1595 if (global != e.front()) |
|
1596 e.rekeyFront(global); |
|
1597 } |
|
1598 |
|
1599 HeapPtrObject &dbgobj = dbg->toJSObjectRef(); |
|
1600 MarkObject(trc, &dbgobj, "Debugger Object"); |
|
1601 |
|
1602 dbg->scripts.trace(trc); |
|
1603 dbg->sources.trace(trc); |
|
1604 dbg->objects.trace(trc); |
|
1605 dbg->environments.trace(trc); |
|
1606 |
|
1607 for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) { |
|
1608 MarkScriptUnbarriered(trc, &bp->site->script, "breakpoint script"); |
|
1609 MarkObject(trc, &bp->getHandlerRef(), "breakpoint handler"); |
|
1610 } |
|
1611 } |
|
1612 } |
|
1613 |
|
1614 void |
|
1615 Debugger::traceObject(JSTracer *trc, JSObject *obj) |
|
1616 { |
|
1617 if (Debugger *dbg = Debugger::fromJSObject(obj)) |
|
1618 dbg->trace(trc); |
|
1619 } |
|
1620 |
|
1621 void |
|
1622 Debugger::trace(JSTracer *trc) |
|
1623 { |
|
1624 if (uncaughtExceptionHook) |
|
1625 MarkObject(trc, &uncaughtExceptionHook, "hooks"); |
|
1626 |
|
1627 /* |
|
1628 * Mark Debugger.Frame objects. These are all reachable from JS, because the |
|
1629 * corresponding JS frames are still on the stack. |
|
1630 * |
|
1631 * (Once we support generator frames properly, we will need |
|
1632 * weakly-referenced Debugger.Frame objects as well, for suspended generator |
|
1633 * frames.) |
|
1634 */ |
|
1635 for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) { |
|
1636 RelocatablePtrObject &frameobj = r.front().value(); |
|
1637 JS_ASSERT(frameobj->getPrivate()); |
|
1638 MarkObject(trc, &frameobj, "live Debugger.Frame"); |
|
1639 } |
|
1640 |
|
1641 /* Trace the weak map from JSScript instances to Debugger.Script objects. */ |
|
1642 scripts.trace(trc); |
|
1643 |
|
1644 /* Trace the referent ->Debugger.Source weak map */ |
|
1645 sources.trace(trc); |
|
1646 |
|
1647 /* Trace the referent -> Debugger.Object weak map. */ |
|
1648 objects.trace(trc); |
|
1649 |
|
1650 /* Trace the referent -> Debugger.Environment weak map. */ |
|
1651 environments.trace(trc); |
|
1652 } |
|
1653 |
|
1654 void |
|
1655 Debugger::sweepAll(FreeOp *fop) |
|
1656 { |
|
1657 JSRuntime *rt = fop->runtime(); |
|
1658 |
|
1659 for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) { |
|
1660 if (IsObjectAboutToBeFinalized(&dbg->object)) { |
|
1661 /* |
|
1662 * dbg is being GC'd. Detach it from its debuggees. The debuggee |
|
1663 * might be GC'd too. Since detaching requires access to both |
|
1664 * objects, this must be done before finalize time. |
|
1665 */ |
|
1666 for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) { |
|
1667 // We can't recompile on-stack scripts here, and we |
|
1668 // can only toggle debug mode to off, so we use an |
|
1669 // infallible variant of removeDebuggeeGlobal. |
|
1670 dbg->removeDebuggeeGlobalUnderGC(fop, e.front(), nullptr, &e); |
|
1671 } |
|
1672 } |
|
1673 } |
|
1674 |
|
1675 for (gc::GCCompartmentGroupIter comp(rt); !comp.done(); comp.next()) { |
|
1676 /* For each debuggee being GC'd, detach it from all its debuggers. */ |
|
1677 GlobalObjectSet &debuggees = comp->getDebuggees(); |
|
1678 for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { |
|
1679 GlobalObject *global = e.front(); |
|
1680 if (IsObjectAboutToBeFinalized(&global)) { |
|
1681 // See infallibility note above. |
|
1682 detachAllDebuggersFromGlobal(fop, global, &e); |
|
1683 } |
|
1684 else if (global != e.front()) |
|
1685 e.rekeyFront(global); |
|
1686 } |
|
1687 } |
|
1688 } |
|
1689 |
|
1690 void |
|
1691 Debugger::detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global, |
|
1692 GlobalObjectSet::Enum *compartmentEnum) |
|
1693 { |
|
1694 const GlobalObject::DebuggerVector *debuggers = global->getDebuggers(); |
|
1695 JS_ASSERT(!debuggers->empty()); |
|
1696 while (!debuggers->empty()) |
|
1697 debuggers->back()->removeDebuggeeGlobalUnderGC(fop, global, compartmentEnum, nullptr); |
|
1698 } |
|
1699 |
|
1700 /* static */ void |
|
1701 Debugger::findCompartmentEdges(Zone *zone, js::gc::ComponentFinder<Zone> &finder) |
|
1702 { |
|
1703 /* |
|
1704 * For debugger cross compartment wrappers, add edges in the opposite |
|
1705 * direction to those already added by JSCompartment::findOutgoingEdges. |
|
1706 * This ensure that debuggers and their debuggees are finalized in the same |
|
1707 * group. |
|
1708 */ |
|
1709 for (Debugger *dbg = zone->runtimeFromMainThread()->debuggerList.getFirst(); |
|
1710 dbg; |
|
1711 dbg = dbg->getNext()) |
|
1712 { |
|
1713 Zone *w = dbg->object->zone(); |
|
1714 if (w == zone || !w->isGCMarking()) |
|
1715 continue; |
|
1716 if (dbg->scripts.hasKeyInZone(zone) || |
|
1717 dbg->sources.hasKeyInZone(zone) || |
|
1718 dbg->objects.hasKeyInZone(zone) || |
|
1719 dbg->environments.hasKeyInZone(zone)) |
|
1720 { |
|
1721 finder.addEdgeTo(w); |
|
1722 } |
|
1723 } |
|
1724 } |
|
1725 |
|
1726 void |
|
1727 Debugger::finalize(FreeOp *fop, JSObject *obj) |
|
1728 { |
|
1729 Debugger *dbg = fromJSObject(obj); |
|
1730 if (!dbg) |
|
1731 return; |
|
1732 fop->delete_(dbg); |
|
1733 } |
|
1734 |
|
1735 const Class Debugger::jsclass = { |
|
1736 "Debugger", |
|
1737 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | |
|
1738 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT), |
|
1739 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
|
1740 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Debugger::finalize, |
|
1741 nullptr, /* call */ |
|
1742 nullptr, /* hasInstance */ |
|
1743 nullptr, /* construct */ |
|
1744 Debugger::traceObject |
|
1745 }; |
|
1746 |
|
1747 Debugger * |
|
1748 Debugger::fromThisValue(JSContext *cx, const CallArgs &args, const char *fnname) |
|
1749 { |
|
1750 if (!args.thisv().isObject()) { |
|
1751 ReportObjectRequired(cx); |
|
1752 return nullptr; |
|
1753 } |
|
1754 JSObject *thisobj = &args.thisv().toObject(); |
|
1755 if (thisobj->getClass() != &Debugger::jsclass) { |
|
1756 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
1757 "Debugger", fnname, thisobj->getClass()->name); |
|
1758 return nullptr; |
|
1759 } |
|
1760 |
|
1761 /* |
|
1762 * Forbid Debugger.prototype, which is of the Debugger JSClass but isn't |
|
1763 * really a Debugger object. The prototype object is distinguished by |
|
1764 * having a nullptr private value. |
|
1765 */ |
|
1766 Debugger *dbg = fromJSObject(thisobj); |
|
1767 if (!dbg) { |
|
1768 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
1769 "Debugger", fnname, "prototype object"); |
|
1770 } |
|
1771 return dbg; |
|
1772 } |
|
1773 |
|
1774 #define THIS_DEBUGGER(cx, argc, vp, fnname, args, dbg) \ |
|
1775 CallArgs args = CallArgsFromVp(argc, vp); \ |
|
1776 Debugger *dbg = Debugger::fromThisValue(cx, args, fnname); \ |
|
1777 if (!dbg) \ |
|
1778 return false |
|
1779 |
|
1780 bool |
|
1781 Debugger::getEnabled(JSContext *cx, unsigned argc, Value *vp) |
|
1782 { |
|
1783 THIS_DEBUGGER(cx, argc, vp, "get enabled", args, dbg); |
|
1784 args.rval().setBoolean(dbg->enabled); |
|
1785 return true; |
|
1786 } |
|
1787 |
|
1788 bool |
|
1789 Debugger::setEnabled(JSContext *cx, unsigned argc, Value *vp) |
|
1790 { |
|
1791 REQUIRE_ARGC("Debugger.set enabled", 1); |
|
1792 THIS_DEBUGGER(cx, argc, vp, "set enabled", args, dbg); |
|
1793 bool enabled = ToBoolean(args[0]); |
|
1794 |
|
1795 if (enabled != dbg->enabled) { |
|
1796 for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) { |
|
1797 if (enabled) |
|
1798 bp->site->inc(cx->runtime()->defaultFreeOp()); |
|
1799 else |
|
1800 bp->site->dec(cx->runtime()->defaultFreeOp()); |
|
1801 } |
|
1802 |
|
1803 /* |
|
1804 * Add or remove ourselves from the runtime's list of Debuggers |
|
1805 * that care about new globals. |
|
1806 */ |
|
1807 if (dbg->getHook(OnNewGlobalObject)) { |
|
1808 if (enabled) { |
|
1809 /* If we were not enabled, the link should be a singleton list. */ |
|
1810 JS_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink)); |
|
1811 JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink, |
|
1812 &cx->runtime()->onNewGlobalObjectWatchers); |
|
1813 } else { |
|
1814 /* If we were enabled, the link should be inserted in the list. */ |
|
1815 JS_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink)); |
|
1816 JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink); |
|
1817 } |
|
1818 } |
|
1819 } |
|
1820 |
|
1821 dbg->enabled = enabled; |
|
1822 args.rval().setUndefined(); |
|
1823 return true; |
|
1824 } |
|
1825 |
|
1826 bool |
|
1827 Debugger::getHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which) |
|
1828 { |
|
1829 JS_ASSERT(which >= 0 && which < HookCount); |
|
1830 THIS_DEBUGGER(cx, argc, vp, "getHook", args, dbg); |
|
1831 args.rval().set(dbg->object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + which)); |
|
1832 return true; |
|
1833 } |
|
1834 |
|
1835 bool |
|
1836 Debugger::setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which) |
|
1837 { |
|
1838 JS_ASSERT(which >= 0 && which < HookCount); |
|
1839 REQUIRE_ARGC("Debugger.setHook", 1); |
|
1840 THIS_DEBUGGER(cx, argc, vp, "setHook", args, dbg); |
|
1841 if (args[0].isObject()) { |
|
1842 if (!args[0].toObject().isCallable()) |
|
1843 return ReportIsNotFunction(cx, args[0], args.length() - 1); |
|
1844 } else if (!args[0].isUndefined()) { |
|
1845 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED); |
|
1846 return false; |
|
1847 } |
|
1848 dbg->object->setReservedSlot(JSSLOT_DEBUG_HOOK_START + which, args[0]); |
|
1849 args.rval().setUndefined(); |
|
1850 return true; |
|
1851 } |
|
1852 |
|
1853 bool |
|
1854 Debugger::getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp) |
|
1855 { |
|
1856 return getHookImpl(cx, argc, vp, OnDebuggerStatement); |
|
1857 } |
|
1858 |
|
1859 bool |
|
1860 Debugger::setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp) |
|
1861 { |
|
1862 return setHookImpl(cx, argc, vp, OnDebuggerStatement); |
|
1863 } |
|
1864 |
|
1865 bool |
|
1866 Debugger::getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp) |
|
1867 { |
|
1868 return getHookImpl(cx, argc, vp, OnExceptionUnwind); |
|
1869 } |
|
1870 |
|
1871 bool |
|
1872 Debugger::setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp) |
|
1873 { |
|
1874 return setHookImpl(cx, argc, vp, OnExceptionUnwind); |
|
1875 } |
|
1876 |
|
1877 bool |
|
1878 Debugger::getOnNewScript(JSContext *cx, unsigned argc, Value *vp) |
|
1879 { |
|
1880 return getHookImpl(cx, argc, vp, OnNewScript); |
|
1881 } |
|
1882 |
|
1883 bool |
|
1884 Debugger::setOnNewScript(JSContext *cx, unsigned argc, Value *vp) |
|
1885 { |
|
1886 return setHookImpl(cx, argc, vp, OnNewScript); |
|
1887 } |
|
1888 |
|
1889 bool |
|
1890 Debugger::getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp) |
|
1891 { |
|
1892 return getHookImpl(cx, argc, vp, OnEnterFrame); |
|
1893 } |
|
1894 |
|
1895 bool |
|
1896 Debugger::setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp) |
|
1897 { |
|
1898 return setHookImpl(cx, argc, vp, OnEnterFrame); |
|
1899 } |
|
1900 |
|
1901 bool |
|
1902 Debugger::getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp) |
|
1903 { |
|
1904 return getHookImpl(cx, argc, vp, OnNewGlobalObject); |
|
1905 } |
|
1906 |
|
1907 bool |
|
1908 Debugger::setOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp) |
|
1909 { |
|
1910 THIS_DEBUGGER(cx, argc, vp, "setOnNewGlobalObject", args, dbg); |
|
1911 RootedObject oldHook(cx, dbg->getHook(OnNewGlobalObject)); |
|
1912 |
|
1913 if (!setHookImpl(cx, argc, vp, OnNewGlobalObject)) |
|
1914 return false; |
|
1915 |
|
1916 /* |
|
1917 * Add or remove ourselves from the runtime's list of Debuggers that |
|
1918 * care about new globals. |
|
1919 */ |
|
1920 if (dbg->enabled) { |
|
1921 JSObject *newHook = dbg->getHook(OnNewGlobalObject); |
|
1922 if (!oldHook && newHook) { |
|
1923 /* If we didn't have a hook, the link should be a singleton list. */ |
|
1924 JS_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink)); |
|
1925 JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink, |
|
1926 &cx->runtime()->onNewGlobalObjectWatchers); |
|
1927 } else if (oldHook && !newHook) { |
|
1928 /* If we did have a hook, the link should be inserted in the list. */ |
|
1929 JS_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink)); |
|
1930 JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink); |
|
1931 } |
|
1932 } |
|
1933 |
|
1934 return true; |
|
1935 } |
|
1936 |
|
1937 bool |
|
1938 Debugger::getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp) |
|
1939 { |
|
1940 THIS_DEBUGGER(cx, argc, vp, "get uncaughtExceptionHook", args, dbg); |
|
1941 args.rval().setObjectOrNull(dbg->uncaughtExceptionHook); |
|
1942 return true; |
|
1943 } |
|
1944 |
|
1945 bool |
|
1946 Debugger::setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp) |
|
1947 { |
|
1948 REQUIRE_ARGC("Debugger.set uncaughtExceptionHook", 1); |
|
1949 THIS_DEBUGGER(cx, argc, vp, "set uncaughtExceptionHook", args, dbg); |
|
1950 if (!args[0].isNull() && (!args[0].isObject() || !args[0].toObject().isCallable())) { |
|
1951 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ASSIGN_FUNCTION_OR_NULL, |
|
1952 "uncaughtExceptionHook"); |
|
1953 return false; |
|
1954 } |
|
1955 dbg->uncaughtExceptionHook = args[0].toObjectOrNull(); |
|
1956 args.rval().setUndefined(); |
|
1957 return true; |
|
1958 } |
|
1959 bool |
|
1960 Debugger::getMemory(JSContext *cx, unsigned argc, Value *vp) |
|
1961 { |
|
1962 THIS_DEBUGGER(cx, argc, vp, "get memory", args, dbg); |
|
1963 args.rval().set(dbg->object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE)); |
|
1964 return true; |
|
1965 } |
|
1966 |
|
1967 GlobalObject * |
|
1968 Debugger::unwrapDebuggeeArgument(JSContext *cx, const Value &v) |
|
1969 { |
|
1970 if (!v.isObject()) { |
|
1971 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
|
1972 "argument", "not a global object"); |
|
1973 return nullptr; |
|
1974 } |
|
1975 |
|
1976 RootedObject obj(cx, &v.toObject()); |
|
1977 |
|
1978 /* If it's a Debugger.Object belonging to this debugger, dereference that. */ |
|
1979 if (obj->getClass() == &DebuggerObject_class) { |
|
1980 RootedValue rv(cx, v); |
|
1981 if (!unwrapDebuggeeValue(cx, &rv)) |
|
1982 return nullptr; |
|
1983 obj = &rv.toObject(); |
|
1984 } |
|
1985 |
|
1986 /* If we have a cross-compartment wrapper, dereference as far as is secure. */ |
|
1987 obj = CheckedUnwrap(obj); |
|
1988 if (!obj) { |
|
1989 JS_ReportError(cx, "Permission denied to access object"); |
|
1990 return nullptr; |
|
1991 } |
|
1992 |
|
1993 /* If that produced an outer window, innerize it. */ |
|
1994 obj = GetInnerObject(cx, obj); |
|
1995 if (!obj) |
|
1996 return nullptr; |
|
1997 |
|
1998 /* If that didn't produce a global object, it's an error. */ |
|
1999 if (!obj->is<GlobalObject>()) { |
|
2000 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
|
2001 "argument", "not a global object"); |
|
2002 return nullptr; |
|
2003 } |
|
2004 |
|
2005 return &obj->as<GlobalObject>(); |
|
2006 } |
|
2007 |
|
2008 bool |
|
2009 Debugger::addDebuggee(JSContext *cx, unsigned argc, Value *vp) |
|
2010 { |
|
2011 REQUIRE_ARGC("Debugger.addDebuggee", 1); |
|
2012 THIS_DEBUGGER(cx, argc, vp, "addDebuggee", args, dbg); |
|
2013 Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); |
|
2014 if (!global) |
|
2015 return false; |
|
2016 |
|
2017 if (!dbg->addDebuggeeGlobal(cx, global)) |
|
2018 return false; |
|
2019 |
|
2020 RootedValue v(cx, ObjectValue(*global)); |
|
2021 if (!dbg->wrapDebuggeeValue(cx, &v)) |
|
2022 return false; |
|
2023 args.rval().set(v); |
|
2024 return true; |
|
2025 } |
|
2026 |
|
2027 bool |
|
2028 Debugger::addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp) |
|
2029 { |
|
2030 THIS_DEBUGGER(cx, argc, vp, "addAllGlobalsAsDebuggees", args, dbg); |
|
2031 for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) { |
|
2032 // Invalidate a zone at a time to avoid doing a zone-wide CellIter |
|
2033 // per compartment. |
|
2034 AutoDebugModeInvalidation invalidate(zone); |
|
2035 |
|
2036 for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) { |
|
2037 if (c == dbg->object->compartment() || c->options().invisibleToDebugger()) |
|
2038 continue; |
|
2039 c->zone()->scheduledForDestruction = false; |
|
2040 GlobalObject *global = c->maybeGlobal(); |
|
2041 if (global) { |
|
2042 Rooted<GlobalObject*> rg(cx, global); |
|
2043 if (!dbg->addDebuggeeGlobal(cx, rg, invalidate)) |
|
2044 return false; |
|
2045 } |
|
2046 } |
|
2047 } |
|
2048 |
|
2049 args.rval().setUndefined(); |
|
2050 return true; |
|
2051 } |
|
2052 |
|
2053 bool |
|
2054 Debugger::removeDebuggee(JSContext *cx, unsigned argc, Value *vp) |
|
2055 { |
|
2056 REQUIRE_ARGC("Debugger.removeDebuggee", 1); |
|
2057 THIS_DEBUGGER(cx, argc, vp, "removeDebuggee", args, dbg); |
|
2058 GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]); |
|
2059 if (!global) |
|
2060 return false; |
|
2061 if (dbg->debuggees.has(global)) { |
|
2062 if (!dbg->removeDebuggeeGlobal(cx, global, nullptr, nullptr)) |
|
2063 return false; |
|
2064 } |
|
2065 args.rval().setUndefined(); |
|
2066 return true; |
|
2067 } |
|
2068 |
|
2069 bool |
|
2070 Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp) |
|
2071 { |
|
2072 THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg); |
|
2073 |
|
2074 for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) { |
|
2075 if (!dbg->removeDebuggeeGlobal(cx, e.front(), nullptr, &e)) |
|
2076 return false; |
|
2077 } |
|
2078 |
|
2079 args.rval().setUndefined(); |
|
2080 return true; |
|
2081 } |
|
2082 |
|
2083 bool |
|
2084 Debugger::hasDebuggee(JSContext *cx, unsigned argc, Value *vp) |
|
2085 { |
|
2086 REQUIRE_ARGC("Debugger.hasDebuggee", 1); |
|
2087 THIS_DEBUGGER(cx, argc, vp, "hasDebuggee", args, dbg); |
|
2088 GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]); |
|
2089 if (!global) |
|
2090 return false; |
|
2091 args.rval().setBoolean(!!dbg->debuggees.lookup(global)); |
|
2092 return true; |
|
2093 } |
|
2094 |
|
2095 bool |
|
2096 Debugger::getDebuggees(JSContext *cx, unsigned argc, Value *vp) |
|
2097 { |
|
2098 THIS_DEBUGGER(cx, argc, vp, "getDebuggees", args, dbg); |
|
2099 RootedObject arrobj(cx, NewDenseAllocatedArray(cx, dbg->debuggees.count())); |
|
2100 if (!arrobj) |
|
2101 return false; |
|
2102 arrobj->ensureDenseInitializedLength(cx, 0, dbg->debuggees.count()); |
|
2103 unsigned i = 0; |
|
2104 for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) { |
|
2105 RootedValue v(cx, ObjectValue(*e.front())); |
|
2106 if (!dbg->wrapDebuggeeValue(cx, &v)) |
|
2107 return false; |
|
2108 arrobj->setDenseElement(i++, v); |
|
2109 } |
|
2110 args.rval().setObject(*arrobj); |
|
2111 return true; |
|
2112 } |
|
2113 |
|
2114 bool |
|
2115 Debugger::getNewestFrame(JSContext *cx, unsigned argc, Value *vp) |
|
2116 { |
|
2117 THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg); |
|
2118 |
|
2119 /* Since there may be multiple contexts, use AllFramesIter. */ |
|
2120 for (AllFramesIter i(cx); !i.done(); ++i) { |
|
2121 if (dbg->observesFrame(i)) { |
|
2122 // Ensure that Ion frames are rematerialized. Only rematerialized |
|
2123 // Ion frames may be used as AbstractFramePtrs. |
|
2124 if (i.isIon() && !i.ensureHasRematerializedFrame()) |
|
2125 return false; |
|
2126 AbstractFramePtr frame = i.abstractFramePtr(); |
|
2127 ScriptFrameIter iter(i.activation()->cx(), ScriptFrameIter::GO_THROUGH_SAVED); |
|
2128 while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame) |
|
2129 ++iter; |
|
2130 return dbg->getScriptFrame(cx, iter, args.rval()); |
|
2131 } |
|
2132 } |
|
2133 args.rval().setNull(); |
|
2134 return true; |
|
2135 } |
|
2136 |
|
2137 bool |
|
2138 Debugger::clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp) |
|
2139 { |
|
2140 THIS_DEBUGGER(cx, argc, vp, "clearAllBreakpoints", args, dbg); |
|
2141 for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) |
|
2142 r.front()->compartment()->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), |
|
2143 dbg, NullPtr()); |
|
2144 return true; |
|
2145 } |
|
2146 |
|
2147 bool |
|
2148 Debugger::construct(JSContext *cx, unsigned argc, Value *vp) |
|
2149 { |
|
2150 CallArgs args = CallArgsFromVp(argc, vp); |
|
2151 |
|
2152 /* Check that the arguments, if any, are cross-compartment wrappers. */ |
|
2153 for (unsigned i = 0; i < args.length(); i++) { |
|
2154 const Value &arg = args[i]; |
|
2155 if (!arg.isObject()) |
|
2156 return ReportObjectRequired(cx); |
|
2157 JSObject *argobj = &arg.toObject(); |
|
2158 if (!argobj->is<CrossCompartmentWrapperObject>()) { |
|
2159 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CCW_REQUIRED, |
|
2160 "Debugger"); |
|
2161 return false; |
|
2162 } |
|
2163 } |
|
2164 |
|
2165 /* Get Debugger.prototype. */ |
|
2166 RootedValue v(cx); |
|
2167 RootedObject callee(cx, &args.callee()); |
|
2168 if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &v)) |
|
2169 return false; |
|
2170 RootedObject proto(cx, &v.toObject()); |
|
2171 JS_ASSERT(proto->getClass() == &Debugger::jsclass); |
|
2172 /* |
|
2173 * Make the new Debugger object. Each one has a reference to |
|
2174 * Debugger.{Frame,Object,Script,Memory}.prototype in reserved slots. The |
|
2175 * rest of the reserved slots are for hooks; they default to undefined. |
|
2176 */ |
|
2177 RootedObject obj(cx, NewObjectWithGivenProto(cx, &Debugger::jsclass, proto, nullptr)); |
|
2178 if (!obj) |
|
2179 return false; |
|
2180 for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP; slot++) |
|
2181 obj->setReservedSlot(slot, proto->getReservedSlot(slot)); |
|
2182 /* Create the Debugger.Memory instance accessible by the |
|
2183 * |Debugger.prototype.memory| getter. */ |
|
2184 Value memoryProto = obj->getReservedSlot(JSSLOT_DEBUG_MEMORY_PROTO); |
|
2185 RootedObject memory(cx, NewObjectWithGivenProto(cx, &DebuggerMemory::class_, |
|
2186 &memoryProto.toObject(), nullptr)); |
|
2187 if (!memory) |
|
2188 return false; |
|
2189 obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, ObjectValue(*memory)); |
|
2190 |
|
2191 /* Construct the underlying C++ object. */ |
|
2192 Debugger *dbg = cx->new_<Debugger>(cx, obj.get()); |
|
2193 if (!dbg) |
|
2194 return false; |
|
2195 if (!dbg->init(cx)) { |
|
2196 js_delete(dbg); |
|
2197 return false; |
|
2198 } |
|
2199 obj->setPrivate(dbg); |
|
2200 /* Now the JSObject owns the js::Debugger instance, so we needn't delete it. */ |
|
2201 |
|
2202 /* Add the initial debuggees, if any. */ |
|
2203 for (unsigned i = 0; i < args.length(); i++) { |
|
2204 Rooted<GlobalObject*> |
|
2205 debuggee(cx, &args[i].toObject().as<ProxyObject>().private_().toObject().global()); |
|
2206 if (!dbg->addDebuggeeGlobal(cx, debuggee)) |
|
2207 return false; |
|
2208 } |
|
2209 |
|
2210 args.rval().setObject(*obj); |
|
2211 return true; |
|
2212 } |
|
2213 |
|
2214 bool |
|
2215 Debugger::addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> global) |
|
2216 { |
|
2217 AutoDebugModeInvalidation invalidate(global->compartment()); |
|
2218 return addDebuggeeGlobal(cx, global, invalidate); |
|
2219 } |
|
2220 |
|
2221 bool |
|
2222 Debugger::addDebuggeeGlobal(JSContext *cx, |
|
2223 Handle<GlobalObject*> global, |
|
2224 AutoDebugModeInvalidation &invalidate) |
|
2225 { |
|
2226 if (debuggees.has(global)) |
|
2227 return true; |
|
2228 |
|
2229 // Callers should generally be unable to get a reference to a debugger- |
|
2230 // invisible global in order to pass it to addDebuggee. But this is possible |
|
2231 // with certain testing aides we expose in the shell, so just make addDebuggee |
|
2232 // throw in that case. |
|
2233 JSCompartment *debuggeeCompartment = global->compartment(); |
|
2234 if (debuggeeCompartment->options().invisibleToDebugger()) { |
|
2235 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
2236 JSMSG_DEBUG_CANT_DEBUG_GLOBAL); |
|
2237 return false; |
|
2238 } |
|
2239 |
|
2240 /* |
|
2241 * Check for cycles. If global's compartment is reachable from this |
|
2242 * Debugger object's compartment by following debuggee-to-debugger links, |
|
2243 * then adding global would create a cycle. (Typically nobody is debugging |
|
2244 * the debugger, in which case we zip through this code without looping.) |
|
2245 */ |
|
2246 Vector<JSCompartment *> visited(cx); |
|
2247 if (!visited.append(object->compartment())) |
|
2248 return false; |
|
2249 for (size_t i = 0; i < visited.length(); i++) { |
|
2250 JSCompartment *c = visited[i]; |
|
2251 if (c == debuggeeCompartment) { |
|
2252 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_LOOP); |
|
2253 return false; |
|
2254 } |
|
2255 |
|
2256 /* |
|
2257 * Find all compartments containing debuggers debugging global objects |
|
2258 * in c. Add those compartments to visited. |
|
2259 */ |
|
2260 for (GlobalObjectSet::Range r = c->getDebuggees().all(); !r.empty(); r.popFront()) { |
|
2261 GlobalObject::DebuggerVector *v = r.front()->getDebuggers(); |
|
2262 for (Debugger **p = v->begin(); p != v->end(); p++) { |
|
2263 JSCompartment *next = (*p)->object->compartment(); |
|
2264 if (Find(visited, next) == visited.end() && !visited.append(next)) |
|
2265 return false; |
|
2266 } |
|
2267 } |
|
2268 } |
|
2269 |
|
2270 /* |
|
2271 * Each debugger-debuggee relation must be stored in up to three places. |
|
2272 * JSCompartment::addDebuggee enables debug mode if needed. |
|
2273 */ |
|
2274 AutoCompartment ac(cx, global); |
|
2275 GlobalObject::DebuggerVector *v = GlobalObject::getOrCreateDebuggers(cx, global); |
|
2276 if (!v || !v->append(this)) { |
|
2277 js_ReportOutOfMemory(cx); |
|
2278 } else { |
|
2279 if (!debuggees.put(global)) { |
|
2280 js_ReportOutOfMemory(cx); |
|
2281 } else { |
|
2282 if (global->getDebuggers()->length() > 1) |
|
2283 return true; |
|
2284 if (debuggeeCompartment->addDebuggee(cx, global, invalidate)) |
|
2285 return true; |
|
2286 |
|
2287 /* Maintain consistency on error. */ |
|
2288 debuggees.remove(global); |
|
2289 } |
|
2290 JS_ASSERT(v->back() == this); |
|
2291 v->popBack(); |
|
2292 } |
|
2293 return false; |
|
2294 } |
|
2295 |
|
2296 void |
|
2297 Debugger::cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global, |
|
2298 AutoDebugModeInvalidation &invalidate, |
|
2299 GlobalObjectSet::Enum *compartmentEnum, |
|
2300 GlobalObjectSet::Enum *debugEnum) |
|
2301 { |
|
2302 /* |
|
2303 * Each debuggee is in two HashSets: one for its compartment and one for |
|
2304 * its debugger (this). The caller might be enumerating either set; if so, |
|
2305 * use HashSet::Enum::removeFront rather than HashSet::remove below, to |
|
2306 * avoid invalidating the live enumerator. |
|
2307 */ |
|
2308 JS_ASSERT(global->compartment()->getDebuggees().has(global)); |
|
2309 JS_ASSERT_IF(compartmentEnum, compartmentEnum->front() == global); |
|
2310 JS_ASSERT(debuggees.has(global)); |
|
2311 JS_ASSERT_IF(debugEnum, debugEnum->front() == global); |
|
2312 |
|
2313 /* |
|
2314 * FIXME Debugger::slowPathOnLeaveFrame needs to kill all Debugger.Frame |
|
2315 * objects referring to a particular JS stack frame. This is hard if |
|
2316 * Debugger objects that are no longer debugging the relevant global might |
|
2317 * have live Frame objects. So we take the easy way out and kill them here. |
|
2318 * This is a bug, since it's observable and contrary to the spec. One |
|
2319 * possible fix would be to put such objects into a compartment-wide bag |
|
2320 * which slowPathOnLeaveFrame would have to examine. |
|
2321 */ |
|
2322 for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) { |
|
2323 AbstractFramePtr frame = e.front().key(); |
|
2324 JSObject *frameobj = e.front().value(); |
|
2325 if (&frame.script()->global() == global) { |
|
2326 DebuggerFrame_freeScriptFrameIterData(fop, frameobj); |
|
2327 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj); |
|
2328 e.removeFront(); |
|
2329 } |
|
2330 } |
|
2331 |
|
2332 GlobalObject::DebuggerVector *v = global->getDebuggers(); |
|
2333 Debugger **p; |
|
2334 for (p = v->begin(); p != v->end(); p++) { |
|
2335 if (*p == this) |
|
2336 break; |
|
2337 } |
|
2338 JS_ASSERT(p != v->end()); |
|
2339 |
|
2340 /* |
|
2341 * The relation must be removed from up to three places: *v and debuggees |
|
2342 * for sure, and possibly the compartment's debuggee set. |
|
2343 */ |
|
2344 v->erase(p); |
|
2345 if (debugEnum) |
|
2346 debugEnum->removeFront(); |
|
2347 else |
|
2348 debuggees.remove(global); |
|
2349 |
|
2350 /* Remove all breakpoints for the debuggee. */ |
|
2351 Breakpoint *nextbp; |
|
2352 for (Breakpoint *bp = firstBreakpoint(); bp; bp = nextbp) { |
|
2353 nextbp = bp->nextInDebugger(); |
|
2354 if (bp->site->script->compartment() == global->compartment()) |
|
2355 bp->destroy(fop); |
|
2356 } |
|
2357 JS_ASSERT_IF(debuggees.empty(), !firstBreakpoint()); |
|
2358 } |
|
2359 |
|
2360 bool |
|
2361 Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global, |
|
2362 GlobalObjectSet::Enum *compartmentEnum, |
|
2363 GlobalObjectSet::Enum *debugEnum) |
|
2364 { |
|
2365 AutoDebugModeInvalidation invalidate(global->compartment()); |
|
2366 return removeDebuggeeGlobal(cx, global, invalidate, compartmentEnum, debugEnum); |
|
2367 } |
|
2368 |
|
2369 bool |
|
2370 Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global, |
|
2371 AutoDebugModeInvalidation &invalidate, |
|
2372 GlobalObjectSet::Enum *compartmentEnum, |
|
2373 GlobalObjectSet::Enum *debugEnum) |
|
2374 { |
|
2375 cleanupDebuggeeGlobalBeforeRemoval(cx->runtime()->defaultFreeOp(), global, |
|
2376 invalidate, compartmentEnum, debugEnum); |
|
2377 |
|
2378 // The debuggee needs to be removed from the compartment last to save a root. |
|
2379 if (global->getDebuggers()->empty()) |
|
2380 return global->compartment()->removeDebuggee(cx, global, invalidate, compartmentEnum); |
|
2381 |
|
2382 return true; |
|
2383 } |
|
2384 |
|
2385 void |
|
2386 Debugger::removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global, |
|
2387 GlobalObjectSet::Enum *compartmentEnum, |
|
2388 GlobalObjectSet::Enum *debugEnum) |
|
2389 { |
|
2390 AutoDebugModeInvalidation invalidate(global->compartment()); |
|
2391 removeDebuggeeGlobalUnderGC(fop, global, invalidate, compartmentEnum, debugEnum); |
|
2392 } |
|
2393 |
|
2394 void |
|
2395 Debugger::removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global, |
|
2396 AutoDebugModeInvalidation &invalidate, |
|
2397 GlobalObjectSet::Enum *compartmentEnum, |
|
2398 GlobalObjectSet::Enum *debugEnum) |
|
2399 { |
|
2400 cleanupDebuggeeGlobalBeforeRemoval(fop, global, invalidate, compartmentEnum, debugEnum); |
|
2401 |
|
2402 /* |
|
2403 * The debuggee needs to be removed from the compartment last, as this can |
|
2404 * trigger GCs if the compartment's debug mode is being changed, and the |
|
2405 * global cannot be rooted on the stack without a cx. |
|
2406 */ |
|
2407 if (global->getDebuggers()->empty()) |
|
2408 global->compartment()->removeDebuggeeUnderGC(fop, global, invalidate, compartmentEnum); |
|
2409 } |
|
2410 |
|
2411 /* |
|
2412 * A class for parsing 'findScripts' query arguments and searching for |
|
2413 * scripts that match the criteria they represent. |
|
2414 */ |
|
2415 class Debugger::ScriptQuery { |
|
2416 public: |
|
2417 /* Construct a ScriptQuery to use matching scripts for |dbg|. */ |
|
2418 ScriptQuery(JSContext *cx, Debugger *dbg): |
|
2419 cx(cx), debugger(dbg), compartments(cx->runtime()), url(cx), displayURL(cx), |
|
2420 displayURLChars(nullptr), innermostForCompartment(cx->runtime()) |
|
2421 {} |
|
2422 |
|
2423 /* |
|
2424 * Initialize this ScriptQuery. Raise an error and return false if we |
|
2425 * haven't enough memory. |
|
2426 */ |
|
2427 bool init() { |
|
2428 if (!compartments.init() || |
|
2429 !innermostForCompartment.init()) |
|
2430 { |
|
2431 js_ReportOutOfMemory(cx); |
|
2432 return false; |
|
2433 } |
|
2434 |
|
2435 return true; |
|
2436 } |
|
2437 |
|
2438 /* |
|
2439 * Parse the query object |query|, and prepare to match only the scripts |
|
2440 * it specifies. |
|
2441 */ |
|
2442 bool parseQuery(HandleObject query) { |
|
2443 /* |
|
2444 * Check for a 'global' property, which limits the results to those |
|
2445 * scripts scoped to a particular global object. |
|
2446 */ |
|
2447 RootedValue global(cx); |
|
2448 if (!JSObject::getProperty(cx, query, query, cx->names().global, &global)) |
|
2449 return false; |
|
2450 if (global.isUndefined()) { |
|
2451 matchAllDebuggeeGlobals(); |
|
2452 } else { |
|
2453 GlobalObject *globalObject = debugger->unwrapDebuggeeArgument(cx, global); |
|
2454 if (!globalObject) |
|
2455 return false; |
|
2456 |
|
2457 /* |
|
2458 * If the given global isn't a debuggee, just leave the set of |
|
2459 * acceptable globals empty; we'll return no scripts. |
|
2460 */ |
|
2461 if (debugger->debuggees.has(globalObject)) { |
|
2462 if (!matchSingleGlobal(globalObject)) |
|
2463 return false; |
|
2464 } |
|
2465 } |
|
2466 |
|
2467 /* Check for a 'url' property. */ |
|
2468 if (!JSObject::getProperty(cx, query, query, cx->names().url, &url)) |
|
2469 return false; |
|
2470 if (!url.isUndefined() && !url.isString()) { |
|
2471 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
|
2472 "query object's 'url' property", "neither undefined nor a string"); |
|
2473 return false; |
|
2474 } |
|
2475 |
|
2476 /* Check for a 'line' property. */ |
|
2477 RootedValue lineProperty(cx); |
|
2478 if (!JSObject::getProperty(cx, query, query, cx->names().line, &lineProperty)) |
|
2479 return false; |
|
2480 if (lineProperty.isUndefined()) { |
|
2481 hasLine = false; |
|
2482 } else if (lineProperty.isNumber()) { |
|
2483 if (url.isUndefined()) { |
|
2484 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
2485 JSMSG_QUERY_LINE_WITHOUT_URL); |
|
2486 return false; |
|
2487 } |
|
2488 double doubleLine = lineProperty.toNumber(); |
|
2489 if (doubleLine <= 0 || (unsigned int) doubleLine != doubleLine) { |
|
2490 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE); |
|
2491 return false; |
|
2492 } |
|
2493 hasLine = true; |
|
2494 line = doubleLine; |
|
2495 } else { |
|
2496 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
|
2497 "query object's 'line' property", |
|
2498 "neither undefined nor an integer"); |
|
2499 return false; |
|
2500 } |
|
2501 |
|
2502 /* Check for an 'innermost' property. */ |
|
2503 PropertyName *innermostName = cx->names().innermost; |
|
2504 RootedValue innermostProperty(cx); |
|
2505 if (!JSObject::getProperty(cx, query, query, innermostName, &innermostProperty)) |
|
2506 return false; |
|
2507 innermost = ToBoolean(innermostProperty); |
|
2508 if (innermost) { |
|
2509 /* Technically, we need only check hasLine, but this is clearer. */ |
|
2510 if (url.isUndefined() || !hasLine) { |
|
2511 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
2512 JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL); |
|
2513 return false; |
|
2514 } |
|
2515 } |
|
2516 |
|
2517 /* Check for a 'displayURL' property. */ |
|
2518 if (!JSObject::getProperty(cx, query, query, cx->names().displayURL, &displayURL)) |
|
2519 return false; |
|
2520 if (!displayURL.isUndefined() && !displayURL.isString()) { |
|
2521 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
|
2522 "query object's 'displayURL' property", |
|
2523 "neither undefined nor a string"); |
|
2524 return false; |
|
2525 } |
|
2526 |
|
2527 return true; |
|
2528 } |
|
2529 |
|
2530 /* Set up this ScriptQuery appropriately for a missing query argument. */ |
|
2531 bool omittedQuery() { |
|
2532 url.setUndefined(); |
|
2533 hasLine = false; |
|
2534 innermost = false; |
|
2535 displayURLChars = nullptr; |
|
2536 return matchAllDebuggeeGlobals(); |
|
2537 } |
|
2538 |
|
2539 /* |
|
2540 * Search all relevant compartments and the stack for scripts matching |
|
2541 * this query, and append the matching scripts to |vector|. |
|
2542 */ |
|
2543 bool findScripts(AutoScriptVector *v) { |
|
2544 if (!prepareQuery()) |
|
2545 return false; |
|
2546 |
|
2547 JSCompartment *singletonComp = nullptr; |
|
2548 if (compartments.count() == 1) |
|
2549 singletonComp = compartments.all().front(); |
|
2550 |
|
2551 /* Search each compartment for debuggee scripts. */ |
|
2552 vector = v; |
|
2553 oom = false; |
|
2554 IterateScripts(cx->runtime(), singletonComp, this, considerScript); |
|
2555 if (oom) { |
|
2556 js_ReportOutOfMemory(cx); |
|
2557 return false; |
|
2558 } |
|
2559 |
|
2560 /* |
|
2561 * For most queries, we just accumulate results in 'vector' as we find |
|
2562 * them. But if this is an 'innermost' query, then we've accumulated the |
|
2563 * results in the 'innermostForCompartment' map. In that case, we now need to |
|
2564 * walk that map and populate 'vector'. |
|
2565 */ |
|
2566 if (innermost) { |
|
2567 for (CompartmentToScriptMap::Range r = innermostForCompartment.all(); |
|
2568 !r.empty(); |
|
2569 r.popFront()) { |
|
2570 if (!v->append(r.front().value())) { |
|
2571 js_ReportOutOfMemory(cx); |
|
2572 return false; |
|
2573 } |
|
2574 } |
|
2575 } |
|
2576 |
|
2577 return true; |
|
2578 } |
|
2579 |
|
2580 private: |
|
2581 /* The context in which we should do our work. */ |
|
2582 JSContext *cx; |
|
2583 |
|
2584 /* The debugger for which we conduct queries. */ |
|
2585 Debugger *debugger; |
|
2586 |
|
2587 typedef HashSet<JSCompartment *, DefaultHasher<JSCompartment *>, RuntimeAllocPolicy> |
|
2588 CompartmentSet; |
|
2589 |
|
2590 /* A script must be in one of these compartments to match the query. */ |
|
2591 CompartmentSet compartments; |
|
2592 |
|
2593 /* If this is a string, matching scripts have urls equal to it. */ |
|
2594 RootedValue url; |
|
2595 |
|
2596 /* url as a C string. */ |
|
2597 JSAutoByteString urlCString; |
|
2598 |
|
2599 /* If this is a string, matching scripts' sources have displayURLs equal to |
|
2600 * it. */ |
|
2601 RootedValue displayURL; |
|
2602 |
|
2603 /* displayURL as a jschar* */ |
|
2604 const jschar *displayURLChars; |
|
2605 size_t displayURLLength; |
|
2606 |
|
2607 /* True if the query contained a 'line' property. */ |
|
2608 bool hasLine; |
|
2609 |
|
2610 /* The line matching scripts must cover. */ |
|
2611 unsigned int line; |
|
2612 |
|
2613 /* True if the query has an 'innermost' property whose value is true. */ |
|
2614 bool innermost; |
|
2615 |
|
2616 typedef HashMap<JSCompartment *, JSScript *, DefaultHasher<JSCompartment *>, RuntimeAllocPolicy> |
|
2617 CompartmentToScriptMap; |
|
2618 |
|
2619 /* |
|
2620 * For 'innermost' queries, a map from compartments to the innermost script |
|
2621 * we've seen so far in that compartment. (Template instantiation code size |
|
2622 * explosion ho!) |
|
2623 */ |
|
2624 CompartmentToScriptMap innermostForCompartment; |
|
2625 |
|
2626 /* The vector to which to append the scripts found. */ |
|
2627 AutoScriptVector *vector; |
|
2628 |
|
2629 /* Indicates whether OOM has occurred while matching. */ |
|
2630 bool oom; |
|
2631 |
|
2632 bool addCompartment(JSCompartment *comp) { |
|
2633 { |
|
2634 // All scripts in the debuggee compartment must be visible, so |
|
2635 // delazify everything. |
|
2636 AutoCompartment ac(cx, comp); |
|
2637 if (!comp->ensureDelazifyScriptsForDebugMode(cx)) |
|
2638 return false; |
|
2639 } |
|
2640 return compartments.put(comp); |
|
2641 } |
|
2642 |
|
2643 /* Arrange for this ScriptQuery to match only scripts that run in |global|. */ |
|
2644 bool matchSingleGlobal(GlobalObject *global) { |
|
2645 JS_ASSERT(compartments.count() == 0); |
|
2646 if (!addCompartment(global->compartment())) { |
|
2647 js_ReportOutOfMemory(cx); |
|
2648 return false; |
|
2649 } |
|
2650 return true; |
|
2651 } |
|
2652 |
|
2653 /* |
|
2654 * Arrange for this ScriptQuery to match all scripts running in debuggee |
|
2655 * globals. |
|
2656 */ |
|
2657 bool matchAllDebuggeeGlobals() { |
|
2658 JS_ASSERT(compartments.count() == 0); |
|
2659 /* Build our compartment set from the debugger's set of debuggee globals. */ |
|
2660 for (GlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) { |
|
2661 if (!addCompartment(r.front()->compartment())) { |
|
2662 js_ReportOutOfMemory(cx); |
|
2663 return false; |
|
2664 } |
|
2665 } |
|
2666 return true; |
|
2667 } |
|
2668 |
|
2669 /* |
|
2670 * Given that parseQuery or omittedQuery has been called, prepare to match |
|
2671 * scripts. Set urlCString and displayURLChars as appropriate. |
|
2672 */ |
|
2673 bool prepareQuery() { |
|
2674 /* Compute urlCString and displayURLChars, if a url or displayURL was |
|
2675 * given respectively. */ |
|
2676 if (url.isString()) { |
|
2677 if (!urlCString.encodeLatin1(cx, url.toString())) |
|
2678 return false; |
|
2679 } |
|
2680 if (displayURL.isString()) { |
|
2681 JSString *s = displayURL.toString(); |
|
2682 displayURLChars = s->getChars(cx); |
|
2683 displayURLLength = s->length(); |
|
2684 if (!displayURLChars) |
|
2685 return false; |
|
2686 } |
|
2687 |
|
2688 return true; |
|
2689 } |
|
2690 |
|
2691 static void considerScript(JSRuntime *rt, void *data, JSScript *script) { |
|
2692 ScriptQuery *self = static_cast<ScriptQuery *>(data); |
|
2693 self->consider(script); |
|
2694 } |
|
2695 |
|
2696 /* |
|
2697 * If |script| matches this query, append it to |vector| or place it in |
|
2698 * |innermostForCompartment|, as appropriate. Set |oom| if an out of memory |
|
2699 * condition occurred. |
|
2700 */ |
|
2701 void consider(JSScript *script) { |
|
2702 // We check for presence of script->code() because it is possible that |
|
2703 // the script was created and thus exposed to GC, but *not* fully |
|
2704 // initialized from fullyInit{FromEmitter,Trivial} due to errors. |
|
2705 if (oom || script->selfHosted() || !script->code()) |
|
2706 return; |
|
2707 JSCompartment *compartment = script->compartment(); |
|
2708 if (!compartments.has(compartment)) |
|
2709 return; |
|
2710 if (urlCString.ptr()) { |
|
2711 bool gotFilename = false; |
|
2712 if (script->filename() && strcmp(script->filename(), urlCString.ptr()) == 0) |
|
2713 gotFilename = true; |
|
2714 |
|
2715 bool gotSourceURL = false; |
|
2716 if (!gotFilename && script->scriptSource()->introducerFilename() && |
|
2717 strcmp(script->scriptSource()->introducerFilename(), urlCString.ptr()) == 0) |
|
2718 { |
|
2719 gotSourceURL = true; |
|
2720 } |
|
2721 if (!gotFilename && !gotSourceURL) |
|
2722 return; |
|
2723 } |
|
2724 if (hasLine) { |
|
2725 if (line < script->lineno() || script->lineno() + js_GetScriptLineExtent(script) < line) |
|
2726 return; |
|
2727 } |
|
2728 if (displayURLChars) { |
|
2729 if (!script->scriptSource() || !script->scriptSource()->hasDisplayURL()) |
|
2730 return; |
|
2731 const jschar *s = script->scriptSource()->displayURL(); |
|
2732 if (CompareChars(s, js_strlen(s), displayURLChars, displayURLLength) != 0) { |
|
2733 return; |
|
2734 } |
|
2735 } |
|
2736 |
|
2737 if (innermost) { |
|
2738 /* |
|
2739 * For 'innermost' queries, we don't place scripts in |vector| right |
|
2740 * away; we may later find another script that is nested inside this |
|
2741 * one. Instead, we record the innermost script we've found so far |
|
2742 * for each compartment in innermostForCompartment, and only |
|
2743 * populate |vector| at the bottom of findScripts, when we've |
|
2744 * traversed all the scripts. |
|
2745 * |
|
2746 * So: check this script against the innermost one we've found so |
|
2747 * far (if any), as recorded in innermostForCompartment, and replace |
|
2748 * that if it's better. |
|
2749 */ |
|
2750 CompartmentToScriptMap::AddPtr p = innermostForCompartment.lookupForAdd(compartment); |
|
2751 if (p) { |
|
2752 /* Is our newly found script deeper than the last one we found? */ |
|
2753 JSScript *incumbent = p->value(); |
|
2754 if (script->staticLevel() > incumbent->staticLevel()) |
|
2755 p->value() = script; |
|
2756 } else { |
|
2757 /* |
|
2758 * This is the first matching script we've encountered for this |
|
2759 * compartment, so it is thus the innermost such script. |
|
2760 */ |
|
2761 if (!innermostForCompartment.add(p, compartment, script)) { |
|
2762 oom = true; |
|
2763 return; |
|
2764 } |
|
2765 } |
|
2766 } else { |
|
2767 /* Record this matching script in the results vector. */ |
|
2768 if (!vector->append(script)) { |
|
2769 oom = true; |
|
2770 return; |
|
2771 } |
|
2772 } |
|
2773 |
|
2774 return; |
|
2775 } |
|
2776 }; |
|
2777 |
|
2778 bool |
|
2779 Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp) |
|
2780 { |
|
2781 THIS_DEBUGGER(cx, argc, vp, "findScripts", args, dbg); |
|
2782 |
|
2783 ScriptQuery query(cx, dbg); |
|
2784 if (!query.init()) |
|
2785 return false; |
|
2786 |
|
2787 if (args.length() >= 1) { |
|
2788 RootedObject queryObject(cx, NonNullObject(cx, args[0])); |
|
2789 if (!queryObject || !query.parseQuery(queryObject)) |
|
2790 return false; |
|
2791 } else { |
|
2792 if (!query.omittedQuery()) |
|
2793 return false; |
|
2794 } |
|
2795 |
|
2796 /* |
|
2797 * Accumulate the scripts in an AutoScriptVector, instead of creating |
|
2798 * the JS array as we go, because we mustn't allocate JS objects or GC |
|
2799 * while we use the CellIter. |
|
2800 */ |
|
2801 AutoScriptVector scripts(cx); |
|
2802 |
|
2803 if (!query.findScripts(&scripts)) |
|
2804 return false; |
|
2805 |
|
2806 RootedObject result(cx, NewDenseAllocatedArray(cx, scripts.length())); |
|
2807 if (!result) |
|
2808 return false; |
|
2809 |
|
2810 result->ensureDenseInitializedLength(cx, 0, scripts.length()); |
|
2811 |
|
2812 for (size_t i = 0; i < scripts.length(); i++) { |
|
2813 JSObject *scriptObject = dbg->wrapScript(cx, scripts.handleAt(i)); |
|
2814 if (!scriptObject) |
|
2815 return false; |
|
2816 result->setDenseElement(i, ObjectValue(*scriptObject)); |
|
2817 } |
|
2818 |
|
2819 args.rval().setObject(*result); |
|
2820 return true; |
|
2821 } |
|
2822 |
|
2823 bool |
|
2824 Debugger::findAllGlobals(JSContext *cx, unsigned argc, Value *vp) |
|
2825 { |
|
2826 THIS_DEBUGGER(cx, argc, vp, "findAllGlobals", args, dbg); |
|
2827 |
|
2828 RootedObject result(cx, NewDenseEmptyArray(cx)); |
|
2829 if (!result) |
|
2830 return false; |
|
2831 |
|
2832 for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) { |
|
2833 if (c->options().invisibleToDebugger()) |
|
2834 continue; |
|
2835 |
|
2836 c->zone()->scheduledForDestruction = false; |
|
2837 |
|
2838 GlobalObject *global = c->maybeGlobal(); |
|
2839 |
|
2840 if (cx->runtime()->isSelfHostingGlobal(global)) |
|
2841 continue; |
|
2842 |
|
2843 if (global) { |
|
2844 /* |
|
2845 * We pulled |global| out of nowhere, so it's possible that it was |
|
2846 * marked gray by XPConnect. Since we're now exposing it to JS code, |
|
2847 * we need to mark it black. |
|
2848 */ |
|
2849 JS::ExposeGCThingToActiveJS(global, JSTRACE_OBJECT); |
|
2850 |
|
2851 RootedValue globalValue(cx, ObjectValue(*global)); |
|
2852 if (!dbg->wrapDebuggeeValue(cx, &globalValue)) |
|
2853 return false; |
|
2854 if (!NewbornArrayPush(cx, result, globalValue)) |
|
2855 return false; |
|
2856 } |
|
2857 } |
|
2858 |
|
2859 args.rval().setObject(*result); |
|
2860 return true; |
|
2861 } |
|
2862 |
|
2863 bool |
|
2864 Debugger::makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp) |
|
2865 { |
|
2866 REQUIRE_ARGC("Debugger.makeGlobalObjectReference", 1); |
|
2867 THIS_DEBUGGER(cx, argc, vp, "makeGlobalObjectReference", args, dbg); |
|
2868 |
|
2869 Rooted<GlobalObject *> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); |
|
2870 if (!global) |
|
2871 return false; |
|
2872 |
|
2873 args.rval().setObject(*global); |
|
2874 return dbg->wrapDebuggeeValue(cx, args.rval()); |
|
2875 } |
|
2876 |
|
2877 const JSPropertySpec Debugger::properties[] = { |
|
2878 JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0), |
|
2879 JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement, |
|
2880 Debugger::setOnDebuggerStatement, 0), |
|
2881 JS_PSGS("onExceptionUnwind", Debugger::getOnExceptionUnwind, |
|
2882 Debugger::setOnExceptionUnwind, 0), |
|
2883 JS_PSGS("onNewScript", Debugger::getOnNewScript, Debugger::setOnNewScript, 0), |
|
2884 JS_PSGS("onEnterFrame", Debugger::getOnEnterFrame, Debugger::setOnEnterFrame, 0), |
|
2885 JS_PSGS("onNewGlobalObject", Debugger::getOnNewGlobalObject, Debugger::setOnNewGlobalObject, 0), |
|
2886 JS_PSGS("uncaughtExceptionHook", Debugger::getUncaughtExceptionHook, |
|
2887 Debugger::setUncaughtExceptionHook, 0), |
|
2888 JS_PSG("memory", Debugger::getMemory, 0), |
|
2889 JS_PS_END |
|
2890 }; |
|
2891 const JSFunctionSpec Debugger::methods[] = { |
|
2892 JS_FN("addDebuggee", Debugger::addDebuggee, 1, 0), |
|
2893 JS_FN("addAllGlobalsAsDebuggees", Debugger::addAllGlobalsAsDebuggees, 0, 0), |
|
2894 JS_FN("removeDebuggee", Debugger::removeDebuggee, 1, 0), |
|
2895 JS_FN("removeAllDebuggees", Debugger::removeAllDebuggees, 0, 0), |
|
2896 JS_FN("hasDebuggee", Debugger::hasDebuggee, 1, 0), |
|
2897 JS_FN("getDebuggees", Debugger::getDebuggees, 0, 0), |
|
2898 JS_FN("getNewestFrame", Debugger::getNewestFrame, 0, 0), |
|
2899 JS_FN("clearAllBreakpoints", Debugger::clearAllBreakpoints, 1, 0), |
|
2900 JS_FN("findScripts", Debugger::findScripts, 1, 0), |
|
2901 JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0), |
|
2902 JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0), |
|
2903 JS_FS_END |
|
2904 }; |
|
2905 |
|
2906 |
|
2907 /*** Debugger.Script *****************************************************************************/ |
|
2908 |
|
2909 static inline JSScript * |
|
2910 GetScriptReferent(JSObject *obj) |
|
2911 { |
|
2912 JS_ASSERT(obj->getClass() == &DebuggerScript_class); |
|
2913 return static_cast<JSScript *>(obj->getPrivate()); |
|
2914 } |
|
2915 |
|
2916 static void |
|
2917 DebuggerScript_trace(JSTracer *trc, JSObject *obj) |
|
2918 { |
|
2919 /* This comes from a private pointer, so no barrier needed. */ |
|
2920 if (JSScript *script = GetScriptReferent(obj)) { |
|
2921 MarkCrossCompartmentScriptUnbarriered(trc, obj, &script, "Debugger.Script referent"); |
|
2922 obj->setPrivateUnbarriered(script); |
|
2923 } |
|
2924 } |
|
2925 |
|
2926 const Class DebuggerScript_class = { |
|
2927 "Script", |
|
2928 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | |
|
2929 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT), |
|
2930 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
|
2931 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, |
|
2932 nullptr, /* call */ |
|
2933 nullptr, /* hasInstance */ |
|
2934 nullptr, /* construct */ |
|
2935 DebuggerScript_trace |
|
2936 }; |
|
2937 |
|
2938 JSObject * |
|
2939 Debugger::newDebuggerScript(JSContext *cx, HandleScript script) |
|
2940 { |
|
2941 assertSameCompartment(cx, object.get()); |
|
2942 |
|
2943 JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO).toObject(); |
|
2944 JS_ASSERT(proto); |
|
2945 JSObject *scriptobj = NewObjectWithGivenProto(cx, &DebuggerScript_class, proto, nullptr, TenuredObject); |
|
2946 if (!scriptobj) |
|
2947 return nullptr; |
|
2948 scriptobj->setReservedSlot(JSSLOT_DEBUGSCRIPT_OWNER, ObjectValue(*object)); |
|
2949 scriptobj->setPrivateGCThing(script); |
|
2950 |
|
2951 return scriptobj; |
|
2952 } |
|
2953 |
|
2954 JSObject * |
|
2955 Debugger::wrapScript(JSContext *cx, HandleScript script) |
|
2956 { |
|
2957 assertSameCompartment(cx, object.get()); |
|
2958 JS_ASSERT(cx->compartment() != script->compartment()); |
|
2959 DependentAddPtr<ScriptWeakMap> p(cx, scripts, script); |
|
2960 if (!p) { |
|
2961 JSObject *scriptobj = newDebuggerScript(cx, script); |
|
2962 if (!scriptobj) |
|
2963 return nullptr; |
|
2964 |
|
2965 if (!p.add(cx, scripts, script, scriptobj)) { |
|
2966 js_ReportOutOfMemory(cx); |
|
2967 return nullptr; |
|
2968 } |
|
2969 |
|
2970 CrossCompartmentKey key(CrossCompartmentKey::DebuggerScript, object, script); |
|
2971 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*scriptobj))) { |
|
2972 scripts.remove(script); |
|
2973 js_ReportOutOfMemory(cx); |
|
2974 return nullptr; |
|
2975 } |
|
2976 } |
|
2977 |
|
2978 JS_ASSERT(GetScriptReferent(p->value()) == script); |
|
2979 return p->value(); |
|
2980 } |
|
2981 |
|
2982 static JSObject * |
|
2983 DebuggerScript_check(JSContext *cx, const Value &v, const char *clsname, const char *fnname) |
|
2984 { |
|
2985 if (!v.isObject()) { |
|
2986 ReportObjectRequired(cx); |
|
2987 return nullptr; |
|
2988 } |
|
2989 JSObject *thisobj = &v.toObject(); |
|
2990 if (thisobj->getClass() != &DebuggerScript_class) { |
|
2991 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
2992 clsname, fnname, thisobj->getClass()->name); |
|
2993 return nullptr; |
|
2994 } |
|
2995 |
|
2996 /* |
|
2997 * Check for Debugger.Script.prototype, which is of class DebuggerScript_class |
|
2998 * but whose script is null. |
|
2999 */ |
|
3000 if (!GetScriptReferent(thisobj)) { |
|
3001 JS_ASSERT(!GetScriptReferent(thisobj)); |
|
3002 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
3003 clsname, fnname, "prototype object"); |
|
3004 return nullptr; |
|
3005 } |
|
3006 |
|
3007 return thisobj; |
|
3008 } |
|
3009 |
|
3010 static JSObject * |
|
3011 DebuggerScript_checkThis(JSContext *cx, const CallArgs &args, const char *fnname) |
|
3012 { |
|
3013 return DebuggerScript_check(cx, args.thisv(), "Debugger.Script", fnname); |
|
3014 } |
|
3015 |
|
3016 #define THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, fnname, args, obj, script) \ |
|
3017 CallArgs args = CallArgsFromVp(argc, vp); \ |
|
3018 RootedObject obj(cx, DebuggerScript_checkThis(cx, args, fnname)); \ |
|
3019 if (!obj) \ |
|
3020 return false; \ |
|
3021 Rooted<JSScript*> script(cx, GetScriptReferent(obj)) |
|
3022 |
|
3023 static bool |
|
3024 DebuggerScript_getUrl(JSContext *cx, unsigned argc, Value *vp) |
|
3025 { |
|
3026 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get url)", args, obj, script); |
|
3027 |
|
3028 if (script->filename()) { |
|
3029 JSString *str; |
|
3030 if (script->scriptSource()->introducerFilename()) |
|
3031 str = js_NewStringCopyZ<CanGC>(cx, script->scriptSource()->introducerFilename()); |
|
3032 else |
|
3033 str = js_NewStringCopyZ<CanGC>(cx, script->filename()); |
|
3034 if (!str) |
|
3035 return false; |
|
3036 args.rval().setString(str); |
|
3037 } else { |
|
3038 args.rval().setNull(); |
|
3039 } |
|
3040 return true; |
|
3041 } |
|
3042 |
|
3043 static bool |
|
3044 DebuggerScript_getStartLine(JSContext *cx, unsigned argc, Value *vp) |
|
3045 { |
|
3046 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get startLine)", args, obj, script); |
|
3047 args.rval().setNumber(uint32_t(script->lineno())); |
|
3048 return true; |
|
3049 } |
|
3050 |
|
3051 static bool |
|
3052 DebuggerScript_getLineCount(JSContext *cx, unsigned argc, Value *vp) |
|
3053 { |
|
3054 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get lineCount)", args, obj, script); |
|
3055 |
|
3056 unsigned maxLine = js_GetScriptLineExtent(script); |
|
3057 args.rval().setNumber(double(maxLine)); |
|
3058 return true; |
|
3059 } |
|
3060 |
|
3061 static bool |
|
3062 DebuggerScript_getSource(JSContext *cx, unsigned argc, Value *vp) |
|
3063 { |
|
3064 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get source)", args, obj, script); |
|
3065 Debugger *dbg = Debugger::fromChildJSObject(obj); |
|
3066 |
|
3067 RootedScriptSource source(cx, &UncheckedUnwrap(script->sourceObject())->as<ScriptSourceObject>()); |
|
3068 RootedObject sourceObject(cx, dbg->wrapSource(cx, source)); |
|
3069 if (!sourceObject) |
|
3070 return false; |
|
3071 |
|
3072 args.rval().setObject(*sourceObject); |
|
3073 return true; |
|
3074 } |
|
3075 |
|
3076 static bool |
|
3077 DebuggerScript_getSourceStart(JSContext *cx, unsigned argc, Value *vp) |
|
3078 { |
|
3079 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceStart)", args, obj, script); |
|
3080 args.rval().setNumber(uint32_t(script->sourceStart())); |
|
3081 return true; |
|
3082 } |
|
3083 |
|
3084 static bool |
|
3085 DebuggerScript_getSourceLength(JSContext *cx, unsigned argc, Value *vp) |
|
3086 { |
|
3087 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceEnd)", args, obj, script); |
|
3088 args.rval().setNumber(uint32_t(script->sourceEnd() - script->sourceStart())); |
|
3089 return true; |
|
3090 } |
|
3091 |
|
3092 static bool |
|
3093 DebuggerScript_getStaticLevel(JSContext *cx, unsigned argc, Value *vp) |
|
3094 { |
|
3095 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get staticLevel)", args, obj, script); |
|
3096 args.rval().setNumber(uint32_t(script->staticLevel())); |
|
3097 return true; |
|
3098 } |
|
3099 |
|
3100 static bool |
|
3101 DebuggerScript_getSourceMapUrl(JSContext *cx, unsigned argc, Value *vp) |
|
3102 { |
|
3103 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceMapURL)", args, obj, script); |
|
3104 |
|
3105 ScriptSource *source = script->scriptSource(); |
|
3106 JS_ASSERT(source); |
|
3107 |
|
3108 if (source->hasSourceMapURL()) { |
|
3109 JSString *str = JS_NewUCStringCopyZ(cx, source->sourceMapURL()); |
|
3110 if (!str) |
|
3111 return false; |
|
3112 args.rval().setString(str); |
|
3113 } else { |
|
3114 args.rval().setNull(); |
|
3115 } |
|
3116 |
|
3117 return true; |
|
3118 } |
|
3119 |
|
3120 static bool |
|
3121 DebuggerScript_getGlobal(JSContext *cx, unsigned argc, Value *vp) |
|
3122 { |
|
3123 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get global)", args, obj, script); |
|
3124 Debugger *dbg = Debugger::fromChildJSObject(obj); |
|
3125 |
|
3126 RootedValue v(cx, ObjectValue(script->global())); |
|
3127 if (!dbg->wrapDebuggeeValue(cx, &v)) |
|
3128 return false; |
|
3129 args.rval().set(v); |
|
3130 return true; |
|
3131 } |
|
3132 |
|
3133 static bool |
|
3134 DebuggerScript_getChildScripts(JSContext *cx, unsigned argc, Value *vp) |
|
3135 { |
|
3136 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getChildScripts", args, obj, script); |
|
3137 Debugger *dbg = Debugger::fromChildJSObject(obj); |
|
3138 |
|
3139 RootedObject result(cx, NewDenseEmptyArray(cx)); |
|
3140 if (!result) |
|
3141 return false; |
|
3142 if (script->hasObjects()) { |
|
3143 /* |
|
3144 * script->savedCallerFun indicates that this is a direct eval script |
|
3145 * and the calling function is stored as script->objects()->vector[0]. |
|
3146 * It is not really a child script of this script, so skip it using |
|
3147 * innerObjectsStart(). |
|
3148 */ |
|
3149 ObjectArray *objects = script->objects(); |
|
3150 RootedFunction fun(cx); |
|
3151 RootedScript funScript(cx); |
|
3152 RootedObject obj(cx), s(cx); |
|
3153 for (uint32_t i = script->innerObjectsStart(); i < objects->length; i++) { |
|
3154 obj = objects->vector[i]; |
|
3155 if (obj->is<JSFunction>()) { |
|
3156 fun = &obj->as<JSFunction>(); |
|
3157 funScript = GetOrCreateFunctionScript(cx, fun); |
|
3158 if (!funScript) |
|
3159 return false; |
|
3160 s = dbg->wrapScript(cx, funScript); |
|
3161 if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s))) |
|
3162 return false; |
|
3163 } |
|
3164 } |
|
3165 } |
|
3166 args.rval().setObject(*result); |
|
3167 return true; |
|
3168 } |
|
3169 |
|
3170 static bool |
|
3171 ScriptOffset(JSContext *cx, JSScript *script, const Value &v, size_t *offsetp) |
|
3172 { |
|
3173 double d; |
|
3174 size_t off; |
|
3175 |
|
3176 bool ok = v.isNumber(); |
|
3177 if (ok) { |
|
3178 d = v.toNumber(); |
|
3179 off = size_t(d); |
|
3180 } |
|
3181 if (!ok || off != d || !IsValidBytecodeOffset(cx, script, off)) { |
|
3182 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_OFFSET); |
|
3183 return false; |
|
3184 } |
|
3185 *offsetp = off; |
|
3186 return true; |
|
3187 } |
|
3188 |
|
3189 static bool |
|
3190 DebuggerScript_getOffsetLine(JSContext *cx, unsigned argc, Value *vp) |
|
3191 { |
|
3192 REQUIRE_ARGC("Debugger.Script.getOffsetLine", 1); |
|
3193 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetLine", args, obj, script); |
|
3194 size_t offset; |
|
3195 if (!ScriptOffset(cx, script, args[0], &offset)) |
|
3196 return false; |
|
3197 unsigned lineno = JS_PCToLineNumber(cx, script, script->offsetToPC(offset)); |
|
3198 args.rval().setNumber(lineno); |
|
3199 return true; |
|
3200 } |
|
3201 |
|
3202 namespace { |
|
3203 |
|
3204 class BytecodeRangeWithPosition : private BytecodeRange |
|
3205 { |
|
3206 public: |
|
3207 using BytecodeRange::empty; |
|
3208 using BytecodeRange::frontPC; |
|
3209 using BytecodeRange::frontOpcode; |
|
3210 using BytecodeRange::frontOffset; |
|
3211 |
|
3212 BytecodeRangeWithPosition(JSContext *cx, JSScript *script) |
|
3213 : BytecodeRange(cx, script), lineno(script->lineno()), column(0), |
|
3214 sn(script->notes()), snpc(script->code()) |
|
3215 { |
|
3216 if (!SN_IS_TERMINATOR(sn)) |
|
3217 snpc += SN_DELTA(sn); |
|
3218 updatePosition(); |
|
3219 while (frontPC() != script->main()) |
|
3220 popFront(); |
|
3221 } |
|
3222 |
|
3223 void popFront() { |
|
3224 BytecodeRange::popFront(); |
|
3225 if (!empty()) |
|
3226 updatePosition(); |
|
3227 } |
|
3228 |
|
3229 size_t frontLineNumber() const { return lineno; } |
|
3230 size_t frontColumnNumber() const { return column; } |
|
3231 |
|
3232 private: |
|
3233 void updatePosition() { |
|
3234 /* |
|
3235 * Determine the current line number by reading all source notes up to |
|
3236 * and including the current offset. |
|
3237 */ |
|
3238 while (!SN_IS_TERMINATOR(sn) && snpc <= frontPC()) { |
|
3239 SrcNoteType type = (SrcNoteType) SN_TYPE(sn); |
|
3240 if (type == SRC_COLSPAN) { |
|
3241 ptrdiff_t colspan = js_GetSrcNoteOffset(sn, 0); |
|
3242 |
|
3243 if (colspan >= SN_COLSPAN_DOMAIN / 2) |
|
3244 colspan -= SN_COLSPAN_DOMAIN; |
|
3245 JS_ASSERT(ptrdiff_t(column) + colspan >= 0); |
|
3246 column += colspan; |
|
3247 } if (type == SRC_SETLINE) { |
|
3248 lineno = size_t(js_GetSrcNoteOffset(sn, 0)); |
|
3249 column = 0; |
|
3250 } else if (type == SRC_NEWLINE) { |
|
3251 lineno++; |
|
3252 column = 0; |
|
3253 } |
|
3254 |
|
3255 sn = SN_NEXT(sn); |
|
3256 snpc += SN_DELTA(sn); |
|
3257 } |
|
3258 } |
|
3259 |
|
3260 size_t lineno; |
|
3261 size_t column; |
|
3262 jssrcnote *sn; |
|
3263 jsbytecode *snpc; |
|
3264 }; |
|
3265 |
|
3266 /* |
|
3267 * FlowGraphSummary::populate(cx, script) computes a summary of script's |
|
3268 * control flow graph used by DebuggerScript_{getAllOffsets,getLineOffsets}. |
|
3269 * |
|
3270 * An instruction on a given line is an entry point for that line if it can be |
|
3271 * reached from (an instruction on) a different line. We distinguish between the |
|
3272 * following cases: |
|
3273 * - hasNoEdges: |
|
3274 * The instruction cannot be reached, so the instruction is not an entry |
|
3275 * point for the line it is on. |
|
3276 * - hasSingleEdge: |
|
3277 * - hasMultipleEdgesFromSingleLine: |
|
3278 * The instruction can be reached from a single line. If this line is |
|
3279 * different from the line the instruction is on, the instruction is an |
|
3280 * entry point for that line. |
|
3281 * - hasMultipleEdgesFromMultipleLines: |
|
3282 * The instruction can be reached from multiple lines. At least one of |
|
3283 * these lines is guaranteed to be different from the line the instruction |
|
3284 * is on, so the instruction is an entry point for that line. |
|
3285 * |
|
3286 * Similarly, an instruction on a given position (line/column pair) is an |
|
3287 * entry point for that position if it can be reached from (an instruction on) a |
|
3288 * different position. Again, we distinguish between the following cases: |
|
3289 * - hasNoEdges: |
|
3290 * The instruction cannot be reached, so the instruction is not an entry |
|
3291 * point for the position it is on. |
|
3292 * - hasSingleEdge: |
|
3293 * The instruction can be reached from a single position. If this line is |
|
3294 * different from the position the instruction is on, the instruction is |
|
3295 * an entry point for that position. |
|
3296 * - hasMultipleEdgesFromSingleLine: |
|
3297 * - hasMultipleEdgesFromMultipleLines: |
|
3298 * The instruction can be reached from multiple positions. At least one |
|
3299 * of these positions is guaranteed to be different from the position the |
|
3300 * instruction is on, so the instruction is an entry point for that |
|
3301 * position. |
|
3302 */ |
|
3303 class FlowGraphSummary { |
|
3304 public: |
|
3305 class Entry { |
|
3306 public: |
|
3307 static Entry createWithNoEdges() { |
|
3308 return Entry(SIZE_MAX, 0); |
|
3309 } |
|
3310 |
|
3311 static Entry createWithSingleEdge(size_t lineno, size_t column) { |
|
3312 return Entry(lineno, column); |
|
3313 } |
|
3314 |
|
3315 static Entry createWithMultipleEdgesFromSingleLine(size_t lineno) { |
|
3316 return Entry(lineno, SIZE_MAX); |
|
3317 } |
|
3318 |
|
3319 static Entry createWithMultipleEdgesFromMultipleLines() { |
|
3320 return Entry(SIZE_MAX, SIZE_MAX); |
|
3321 } |
|
3322 |
|
3323 Entry() {} |
|
3324 |
|
3325 bool hasNoEdges() const { |
|
3326 return lineno_ == SIZE_MAX && column_ != SIZE_MAX; |
|
3327 } |
|
3328 |
|
3329 bool hasSingleEdge() const { |
|
3330 return lineno_ != SIZE_MAX && column_ != SIZE_MAX; |
|
3331 } |
|
3332 |
|
3333 bool hasMultipleEdgesFromSingleLine() const { |
|
3334 return lineno_ != SIZE_MAX && column_ == SIZE_MAX; |
|
3335 } |
|
3336 |
|
3337 bool hasMultipleEdgesFromMultipleLines() const { |
|
3338 return lineno_ == SIZE_MAX && column_ == SIZE_MAX; |
|
3339 } |
|
3340 |
|
3341 bool operator==(const Entry &other) const { |
|
3342 return lineno_ == other.lineno_ && column_ == other.column_; |
|
3343 } |
|
3344 |
|
3345 bool operator!=(const Entry &other) const { |
|
3346 return lineno_ != other.lineno_ || column_ != other.column_; |
|
3347 } |
|
3348 |
|
3349 size_t lineno() const { |
|
3350 return lineno_; |
|
3351 } |
|
3352 |
|
3353 size_t column() const { |
|
3354 return column_; |
|
3355 } |
|
3356 |
|
3357 private: |
|
3358 Entry(size_t lineno, size_t column) : lineno_(lineno), column_(column) {} |
|
3359 |
|
3360 size_t lineno_; |
|
3361 size_t column_; |
|
3362 }; |
|
3363 |
|
3364 FlowGraphSummary(JSContext *cx) : entries_(cx) {} |
|
3365 |
|
3366 Entry &operator[](size_t index) { |
|
3367 return entries_[index]; |
|
3368 } |
|
3369 |
|
3370 bool populate(JSContext *cx, JSScript *script) { |
|
3371 if (!entries_.growBy(script->length())) |
|
3372 return false; |
|
3373 unsigned mainOffset = script->pcToOffset(script->main()); |
|
3374 entries_[mainOffset] = Entry::createWithMultipleEdgesFromMultipleLines(); |
|
3375 for (size_t i = mainOffset + 1; i < script->length(); i++) |
|
3376 entries_[i] = Entry::createWithNoEdges(); |
|
3377 |
|
3378 size_t prevLineno = script->lineno(); |
|
3379 size_t prevColumn = 0; |
|
3380 JSOp prevOp = JSOP_NOP; |
|
3381 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) { |
|
3382 size_t lineno = r.frontLineNumber(); |
|
3383 size_t column = r.frontColumnNumber(); |
|
3384 JSOp op = r.frontOpcode(); |
|
3385 |
|
3386 if (FlowsIntoNext(prevOp)) |
|
3387 addEdge(prevLineno, prevColumn, r.frontOffset()); |
|
3388 |
|
3389 if (js_CodeSpec[op].type() == JOF_JUMP) { |
|
3390 addEdge(lineno, column, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC())); |
|
3391 } else if (op == JSOP_TABLESWITCH) { |
|
3392 jsbytecode *pc = r.frontPC(); |
|
3393 size_t offset = r.frontOffset(); |
|
3394 ptrdiff_t step = JUMP_OFFSET_LEN; |
|
3395 size_t defaultOffset = offset + GET_JUMP_OFFSET(pc); |
|
3396 pc += step; |
|
3397 addEdge(lineno, column, defaultOffset); |
|
3398 |
|
3399 int32_t low = GET_JUMP_OFFSET(pc); |
|
3400 pc += JUMP_OFFSET_LEN; |
|
3401 int ncases = GET_JUMP_OFFSET(pc) - low + 1; |
|
3402 pc += JUMP_OFFSET_LEN; |
|
3403 |
|
3404 for (int i = 0; i < ncases; i++) { |
|
3405 size_t target = offset + GET_JUMP_OFFSET(pc); |
|
3406 addEdge(lineno, column, target); |
|
3407 pc += step; |
|
3408 } |
|
3409 } |
|
3410 |
|
3411 prevLineno = lineno; |
|
3412 prevColumn = column; |
|
3413 prevOp = op; |
|
3414 } |
|
3415 |
|
3416 return true; |
|
3417 } |
|
3418 |
|
3419 private: |
|
3420 void addEdge(size_t sourceLineno, size_t sourceColumn, size_t targetOffset) { |
|
3421 if (entries_[targetOffset].hasNoEdges()) |
|
3422 entries_[targetOffset] = Entry::createWithSingleEdge(sourceLineno, sourceColumn); |
|
3423 else if (entries_[targetOffset].lineno() != sourceLineno) |
|
3424 entries_[targetOffset] = Entry::createWithMultipleEdgesFromMultipleLines(); |
|
3425 else if (entries_[targetOffset].column() != sourceColumn) |
|
3426 entries_[targetOffset] = Entry::createWithMultipleEdgesFromSingleLine(sourceLineno); |
|
3427 } |
|
3428 |
|
3429 Vector<Entry> entries_; |
|
3430 }; |
|
3431 |
|
3432 } /* anonymous namespace */ |
|
3433 |
|
3434 static bool |
|
3435 DebuggerScript_getAllOffsets(JSContext *cx, unsigned argc, Value *vp) |
|
3436 { |
|
3437 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script); |
|
3438 |
|
3439 /* |
|
3440 * First pass: determine which offsets in this script are jump targets and |
|
3441 * which line numbers jump to them. |
|
3442 */ |
|
3443 FlowGraphSummary flowData(cx); |
|
3444 if (!flowData.populate(cx, script)) |
|
3445 return false; |
|
3446 |
|
3447 /* Second pass: build the result array. */ |
|
3448 RootedObject result(cx, NewDenseEmptyArray(cx)); |
|
3449 if (!result) |
|
3450 return false; |
|
3451 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) { |
|
3452 size_t offset = r.frontOffset(); |
|
3453 size_t lineno = r.frontLineNumber(); |
|
3454 |
|
3455 /* Make a note, if the current instruction is an entry point for the current line. */ |
|
3456 if (!flowData[offset].hasNoEdges() && flowData[offset].lineno() != lineno) { |
|
3457 /* Get the offsets array for this line. */ |
|
3458 RootedObject offsets(cx); |
|
3459 RootedValue offsetsv(cx); |
|
3460 |
|
3461 RootedId id(cx, INT_TO_JSID(lineno)); |
|
3462 |
|
3463 bool found; |
|
3464 if (!js::HasOwnProperty(cx, result, id, &found)) |
|
3465 return false; |
|
3466 if (found && !JSObject::getGeneric(cx, result, result, id, &offsetsv)) |
|
3467 return false; |
|
3468 |
|
3469 if (offsetsv.isObject()) { |
|
3470 offsets = &offsetsv.toObject(); |
|
3471 } else { |
|
3472 JS_ASSERT(offsetsv.isUndefined()); |
|
3473 |
|
3474 /* |
|
3475 * Create an empty offsets array for this line. |
|
3476 * Store it in the result array. |
|
3477 */ |
|
3478 RootedId id(cx); |
|
3479 RootedValue v(cx, NumberValue(lineno)); |
|
3480 offsets = NewDenseEmptyArray(cx); |
|
3481 if (!offsets || |
|
3482 !ValueToId<CanGC>(cx, v, &id)) |
|
3483 { |
|
3484 return false; |
|
3485 } |
|
3486 |
|
3487 RootedValue value(cx, ObjectValue(*offsets)); |
|
3488 if (!JSObject::defineGeneric(cx, result, id, value)) |
|
3489 return false; |
|
3490 } |
|
3491 |
|
3492 /* Append the current offset to the offsets array. */ |
|
3493 if (!NewbornArrayPush(cx, offsets, NumberValue(offset))) |
|
3494 return false; |
|
3495 } |
|
3496 } |
|
3497 |
|
3498 args.rval().setObject(*result); |
|
3499 return true; |
|
3500 } |
|
3501 |
|
3502 static bool |
|
3503 DebuggerScript_getAllColumnOffsets(JSContext *cx, unsigned argc, Value *vp) |
|
3504 { |
|
3505 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllColumnOffsets", args, obj, script); |
|
3506 |
|
3507 /* |
|
3508 * First pass: determine which offsets in this script are jump targets and |
|
3509 * which positions jump to them. |
|
3510 */ |
|
3511 FlowGraphSummary flowData(cx); |
|
3512 if (!flowData.populate(cx, script)) |
|
3513 return false; |
|
3514 |
|
3515 /* Second pass: build the result array. */ |
|
3516 RootedObject result(cx, NewDenseEmptyArray(cx)); |
|
3517 if (!result) |
|
3518 return false; |
|
3519 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) { |
|
3520 size_t lineno = r.frontLineNumber(); |
|
3521 size_t column = r.frontColumnNumber(); |
|
3522 size_t offset = r.frontOffset(); |
|
3523 |
|
3524 /* Make a note, if the current instruction is an entry point for the current position. */ |
|
3525 if (!flowData[offset].hasNoEdges() && |
|
3526 (flowData[offset].lineno() != lineno || |
|
3527 flowData[offset].column() != column)) { |
|
3528 RootedObject entry(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); |
|
3529 if (!entry) |
|
3530 return false; |
|
3531 |
|
3532 RootedId id(cx, NameToId(cx->names().lineNumber)); |
|
3533 RootedValue value(cx, NumberValue(lineno)); |
|
3534 if (!JSObject::defineGeneric(cx, entry, id, value)) |
|
3535 return false; |
|
3536 |
|
3537 value = NumberValue(column); |
|
3538 if (!JSObject::defineProperty(cx, entry, cx->names().columnNumber, value)) |
|
3539 return false; |
|
3540 |
|
3541 id = NameToId(cx->names().offset); |
|
3542 value = NumberValue(offset); |
|
3543 if (!JSObject::defineGeneric(cx, entry, id, value)) |
|
3544 return false; |
|
3545 |
|
3546 if (!NewbornArrayPush(cx, result, ObjectValue(*entry))) |
|
3547 return false; |
|
3548 } |
|
3549 } |
|
3550 |
|
3551 args.rval().setObject(*result); |
|
3552 return true; |
|
3553 } |
|
3554 |
|
3555 static bool |
|
3556 DebuggerScript_getLineOffsets(JSContext *cx, unsigned argc, Value *vp) |
|
3557 { |
|
3558 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineOffsets", args, obj, script); |
|
3559 REQUIRE_ARGC("Debugger.Script.getLineOffsets", 1); |
|
3560 |
|
3561 /* Parse lineno argument. */ |
|
3562 RootedValue linenoValue(cx, args[0]); |
|
3563 size_t lineno; |
|
3564 if (!ToNumber(cx, &linenoValue)) |
|
3565 return false; |
|
3566 { |
|
3567 double d = linenoValue.toNumber(); |
|
3568 lineno = size_t(d); |
|
3569 if (lineno != d) { |
|
3570 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE); |
|
3571 return false; |
|
3572 } |
|
3573 } |
|
3574 |
|
3575 /* |
|
3576 * First pass: determine which offsets in this script are jump targets and |
|
3577 * which line numbers jump to them. |
|
3578 */ |
|
3579 FlowGraphSummary flowData(cx); |
|
3580 if (!flowData.populate(cx, script)) |
|
3581 return false; |
|
3582 |
|
3583 /* Second pass: build the result array. */ |
|
3584 RootedObject result(cx, NewDenseEmptyArray(cx)); |
|
3585 if (!result) |
|
3586 return false; |
|
3587 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) { |
|
3588 size_t offset = r.frontOffset(); |
|
3589 |
|
3590 /* If the op at offset is an entry point, append offset to result. */ |
|
3591 if (r.frontLineNumber() == lineno && |
|
3592 !flowData[offset].hasNoEdges() && |
|
3593 flowData[offset].lineno() != lineno) |
|
3594 { |
|
3595 if (!NewbornArrayPush(cx, result, NumberValue(offset))) |
|
3596 return false; |
|
3597 } |
|
3598 } |
|
3599 |
|
3600 args.rval().setObject(*result); |
|
3601 return true; |
|
3602 } |
|
3603 |
|
3604 bool |
|
3605 Debugger::observesFrame(AbstractFramePtr frame) const |
|
3606 { |
|
3607 return observesScript(frame.script()); |
|
3608 } |
|
3609 |
|
3610 bool |
|
3611 Debugger::observesFrame(const ScriptFrameIter &iter) const |
|
3612 { |
|
3613 return observesScript(iter.script()); |
|
3614 } |
|
3615 |
|
3616 bool |
|
3617 Debugger::observesScript(JSScript *script) const |
|
3618 { |
|
3619 if (!enabled) |
|
3620 return false; |
|
3621 return observesGlobal(&script->global()) && (!script->selfHosted() || |
|
3622 SelfHostedFramesVisible()); |
|
3623 } |
|
3624 |
|
3625 /* static */ bool |
|
3626 Debugger::replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to, |
|
3627 ScriptFrameIter &iter) |
|
3628 { |
|
3629 for (Debugger::FrameRange r(from); !r.empty(); r.popFront()) { |
|
3630 RootedObject frameobj(cx, r.frontFrame()); |
|
3631 Debugger *dbg = r.frontDebugger(); |
|
3632 JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj)); |
|
3633 |
|
3634 // Update frame object's ScriptFrameIter::data pointer. |
|
3635 DebuggerFrame_freeScriptFrameIterData(cx->runtime()->defaultFreeOp(), frameobj); |
|
3636 ScriptFrameIter::Data *data = iter.copyData(); |
|
3637 if (!data) |
|
3638 return false; |
|
3639 frameobj->setPrivate(data); |
|
3640 |
|
3641 // Remove the old entry before mutating the HashMap. |
|
3642 r.removeFrontFrame(); |
|
3643 |
|
3644 // Add the frame object with |to| as key. |
|
3645 if (!dbg->frames.putNew(to, frameobj)) { |
|
3646 js_ReportOutOfMemory(cx); |
|
3647 return false; |
|
3648 } |
|
3649 } |
|
3650 |
|
3651 return true; |
|
3652 } |
|
3653 |
|
3654 /* static */ bool |
|
3655 Debugger::handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to) |
|
3656 { |
|
3657 ScriptFrameIter iter(cx); |
|
3658 JS_ASSERT(iter.abstractFramePtr() == to); |
|
3659 return replaceFrameGuts(cx, from, to, iter); |
|
3660 } |
|
3661 |
|
3662 /* static */ bool |
|
3663 Debugger::handleIonBailout(JSContext *cx, jit::RematerializedFrame *from, jit::BaselineFrame *to) |
|
3664 { |
|
3665 // When we return to a bailed-out Ion real frame, we must update all |
|
3666 // Debugger.Frames that refer to its inline frames. However, since we |
|
3667 // can't pop individual inline frames off the stack (we can only pop the |
|
3668 // real frame that contains them all, as a unit), we cannot assume that |
|
3669 // the frame we're dealing with is the top frame. Advance the iterator |
|
3670 // across any inlined frames younger than |to|, the baseline frame |
|
3671 // reconstructed during bailout from the Ion frame corresponding to |
|
3672 // |from|. |
|
3673 ScriptFrameIter iter(cx); |
|
3674 while (iter.abstractFramePtr() != to) |
|
3675 ++iter; |
|
3676 return replaceFrameGuts(cx, from, to, iter); |
|
3677 } |
|
3678 |
|
3679 static bool |
|
3680 DebuggerScript_setBreakpoint(JSContext *cx, unsigned argc, Value *vp) |
|
3681 { |
|
3682 REQUIRE_ARGC("Debugger.Script.setBreakpoint", 2); |
|
3683 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script); |
|
3684 Debugger *dbg = Debugger::fromChildJSObject(obj); |
|
3685 |
|
3686 if (!dbg->observesScript(script)) { |
|
3687 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGING); |
|
3688 return false; |
|
3689 } |
|
3690 |
|
3691 size_t offset; |
|
3692 if (!ScriptOffset(cx, script, args[0], &offset)) |
|
3693 return false; |
|
3694 |
|
3695 JSObject *handler = NonNullObject(cx, args[1]); |
|
3696 if (!handler) |
|
3697 return false; |
|
3698 |
|
3699 jsbytecode *pc = script->offsetToPC(offset); |
|
3700 BreakpointSite *site = script->getOrCreateBreakpointSite(cx, pc); |
|
3701 if (!site) |
|
3702 return false; |
|
3703 site->inc(cx->runtime()->defaultFreeOp()); |
|
3704 if (cx->runtime()->new_<Breakpoint>(dbg, site, handler)) { |
|
3705 args.rval().setUndefined(); |
|
3706 return true; |
|
3707 } |
|
3708 site->dec(cx->runtime()->defaultFreeOp()); |
|
3709 site->destroyIfEmpty(cx->runtime()->defaultFreeOp()); |
|
3710 return false; |
|
3711 } |
|
3712 |
|
3713 static bool |
|
3714 DebuggerScript_getBreakpoints(JSContext *cx, unsigned argc, Value *vp) |
|
3715 { |
|
3716 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getBreakpoints", args, obj, script); |
|
3717 Debugger *dbg = Debugger::fromChildJSObject(obj); |
|
3718 |
|
3719 jsbytecode *pc; |
|
3720 if (args.length() > 0) { |
|
3721 size_t offset; |
|
3722 if (!ScriptOffset(cx, script, args[0], &offset)) |
|
3723 return false; |
|
3724 pc = script->offsetToPC(offset); |
|
3725 } else { |
|
3726 pc = nullptr; |
|
3727 } |
|
3728 |
|
3729 RootedObject arr(cx, NewDenseEmptyArray(cx)); |
|
3730 if (!arr) |
|
3731 return false; |
|
3732 |
|
3733 for (unsigned i = 0; i < script->length(); i++) { |
|
3734 BreakpointSite *site = script->getBreakpointSite(script->offsetToPC(i)); |
|
3735 if (site && (!pc || site->pc == pc)) { |
|
3736 for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) { |
|
3737 if (bp->debugger == dbg && |
|
3738 !NewbornArrayPush(cx, arr, ObjectValue(*bp->getHandler()))) |
|
3739 { |
|
3740 return false; |
|
3741 } |
|
3742 } |
|
3743 } |
|
3744 } |
|
3745 args.rval().setObject(*arr); |
|
3746 return true; |
|
3747 } |
|
3748 |
|
3749 static bool |
|
3750 DebuggerScript_clearBreakpoint(JSContext *cx, unsigned argc, Value *vp) |
|
3751 { |
|
3752 REQUIRE_ARGC("Debugger.Script.clearBreakpoint", 1); |
|
3753 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script); |
|
3754 Debugger *dbg = Debugger::fromChildJSObject(obj); |
|
3755 |
|
3756 JSObject *handler = NonNullObject(cx, args[0]); |
|
3757 if (!handler) |
|
3758 return false; |
|
3759 |
|
3760 script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, handler); |
|
3761 args.rval().setUndefined(); |
|
3762 return true; |
|
3763 } |
|
3764 |
|
3765 static bool |
|
3766 DebuggerScript_clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp) |
|
3767 { |
|
3768 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearAllBreakpoints", args, obj, script); |
|
3769 Debugger *dbg = Debugger::fromChildJSObject(obj); |
|
3770 script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, nullptr); |
|
3771 args.rval().setUndefined(); |
|
3772 return true; |
|
3773 } |
|
3774 |
|
3775 static bool |
|
3776 DebuggerScript_isInCatchScope(JSContext *cx, unsigned argc, Value* vp) |
|
3777 { |
|
3778 REQUIRE_ARGC("Debugger.Script.isInCatchScope", 1); |
|
3779 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "isInCatchScope", args, obj, script); |
|
3780 |
|
3781 size_t offset; |
|
3782 if (!ScriptOffset(cx, script, args[0], &offset)) |
|
3783 return false; |
|
3784 |
|
3785 /* |
|
3786 * Try note ranges are relative to the mainOffset of the script, so adjust |
|
3787 * offset accordingly. |
|
3788 */ |
|
3789 offset -= script->mainOffset(); |
|
3790 |
|
3791 args.rval().setBoolean(false); |
|
3792 if (script->hasTrynotes()) { |
|
3793 JSTryNote* tnBegin = script->trynotes()->vector; |
|
3794 JSTryNote* tnEnd = tnBegin + script->trynotes()->length; |
|
3795 while (tnBegin != tnEnd) { |
|
3796 if (tnBegin->start <= offset && |
|
3797 offset <= tnBegin->start + tnBegin->length && |
|
3798 tnBegin->kind == JSTRY_CATCH) |
|
3799 { |
|
3800 args.rval().setBoolean(true); |
|
3801 break; |
|
3802 } |
|
3803 ++tnBegin; |
|
3804 } |
|
3805 } |
|
3806 return true; |
|
3807 } |
|
3808 |
|
3809 static bool |
|
3810 DebuggerScript_construct(JSContext *cx, unsigned argc, Value *vp) |
|
3811 { |
|
3812 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, |
|
3813 "Debugger.Script"); |
|
3814 return false; |
|
3815 } |
|
3816 |
|
3817 static const JSPropertySpec DebuggerScript_properties[] = { |
|
3818 JS_PSG("url", DebuggerScript_getUrl, 0), |
|
3819 JS_PSG("startLine", DebuggerScript_getStartLine, 0), |
|
3820 JS_PSG("lineCount", DebuggerScript_getLineCount, 0), |
|
3821 JS_PSG("source", DebuggerScript_getSource, 0), |
|
3822 JS_PSG("sourceStart", DebuggerScript_getSourceStart, 0), |
|
3823 JS_PSG("sourceLength", DebuggerScript_getSourceLength, 0), |
|
3824 JS_PSG("staticLevel", DebuggerScript_getStaticLevel, 0), |
|
3825 JS_PSG("sourceMapURL", DebuggerScript_getSourceMapUrl, 0), |
|
3826 JS_PSG("global", DebuggerScript_getGlobal, 0), |
|
3827 JS_PS_END |
|
3828 }; |
|
3829 |
|
3830 static const JSFunctionSpec DebuggerScript_methods[] = { |
|
3831 JS_FN("getChildScripts", DebuggerScript_getChildScripts, 0, 0), |
|
3832 JS_FN("getAllOffsets", DebuggerScript_getAllOffsets, 0, 0), |
|
3833 JS_FN("getAllColumnOffsets", DebuggerScript_getAllColumnOffsets, 0, 0), |
|
3834 JS_FN("getLineOffsets", DebuggerScript_getLineOffsets, 1, 0), |
|
3835 JS_FN("getOffsetLine", DebuggerScript_getOffsetLine, 0, 0), |
|
3836 JS_FN("setBreakpoint", DebuggerScript_setBreakpoint, 2, 0), |
|
3837 JS_FN("getBreakpoints", DebuggerScript_getBreakpoints, 1, 0), |
|
3838 JS_FN("clearBreakpoint", DebuggerScript_clearBreakpoint, 1, 0), |
|
3839 JS_FN("clearAllBreakpoints", DebuggerScript_clearAllBreakpoints, 0, 0), |
|
3840 JS_FN("isInCatchScope", DebuggerScript_isInCatchScope, 1, 0), |
|
3841 JS_FS_END |
|
3842 }; |
|
3843 |
|
3844 |
|
3845 /*** Debugger.Source *****************************************************************************/ |
|
3846 |
|
3847 static inline ScriptSourceObject * |
|
3848 GetSourceReferent(JSObject *obj) |
|
3849 { |
|
3850 JS_ASSERT(obj->getClass() == &DebuggerSource_class); |
|
3851 return static_cast<ScriptSourceObject *>(obj->getPrivate()); |
|
3852 } |
|
3853 |
|
3854 static void |
|
3855 DebuggerSource_trace(JSTracer *trc, JSObject *obj) |
|
3856 { |
|
3857 /* |
|
3858 * There is a barrier on private pointers, so the Unbarriered marking |
|
3859 * is okay. |
|
3860 */ |
|
3861 if (JSObject *referent = GetSourceReferent(obj)) { |
|
3862 MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Source referent"); |
|
3863 obj->setPrivateUnbarriered(referent); |
|
3864 } |
|
3865 } |
|
3866 |
|
3867 const Class DebuggerSource_class = { |
|
3868 "Source", |
|
3869 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | |
|
3870 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSOURCE_COUNT), |
|
3871 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
|
3872 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, |
|
3873 nullptr, /* call */ |
|
3874 nullptr, /* hasInstance */ |
|
3875 nullptr, /* construct */ |
|
3876 DebuggerSource_trace |
|
3877 }; |
|
3878 |
|
3879 JSObject * |
|
3880 Debugger::newDebuggerSource(JSContext *cx, HandleScriptSource source) |
|
3881 { |
|
3882 assertSameCompartment(cx, object.get()); |
|
3883 |
|
3884 JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SOURCE_PROTO).toObject(); |
|
3885 JS_ASSERT(proto); |
|
3886 JSObject *sourceobj = NewObjectWithGivenProto(cx, &DebuggerSource_class, proto, nullptr, TenuredObject); |
|
3887 if (!sourceobj) |
|
3888 return nullptr; |
|
3889 sourceobj->setReservedSlot(JSSLOT_DEBUGSOURCE_OWNER, ObjectValue(*object)); |
|
3890 sourceobj->setPrivateGCThing(source); |
|
3891 |
|
3892 return sourceobj; |
|
3893 } |
|
3894 |
|
3895 JSObject * |
|
3896 Debugger::wrapSource(JSContext *cx, HandleScriptSource source) |
|
3897 { |
|
3898 assertSameCompartment(cx, object.get()); |
|
3899 JS_ASSERT(cx->compartment() != source->compartment()); |
|
3900 DependentAddPtr<SourceWeakMap> p(cx, sources, source); |
|
3901 if (!p) { |
|
3902 JSObject *sourceobj = newDebuggerSource(cx, source); |
|
3903 if (!sourceobj) |
|
3904 return nullptr; |
|
3905 |
|
3906 if (!p.add(cx, sources, source, sourceobj)) { |
|
3907 js_ReportOutOfMemory(cx); |
|
3908 return nullptr; |
|
3909 } |
|
3910 |
|
3911 CrossCompartmentKey key(CrossCompartmentKey::DebuggerSource, object, source); |
|
3912 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*sourceobj))) { |
|
3913 sources.remove(source); |
|
3914 js_ReportOutOfMemory(cx); |
|
3915 return nullptr; |
|
3916 } |
|
3917 } |
|
3918 |
|
3919 JS_ASSERT(GetSourceReferent(p->value()) == source); |
|
3920 return p->value(); |
|
3921 } |
|
3922 |
|
3923 static bool |
|
3924 DebuggerSource_construct(JSContext *cx, unsigned argc, Value *vp) |
|
3925 { |
|
3926 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, |
|
3927 "Debugger.Source"); |
|
3928 return false; |
|
3929 } |
|
3930 |
|
3931 static JSObject * |
|
3932 DebuggerSource_checkThis(JSContext *cx, const CallArgs &args, const char *fnname) |
|
3933 { |
|
3934 if (!args.thisv().isObject()) { |
|
3935 ReportObjectRequired(cx); |
|
3936 return nullptr; |
|
3937 } |
|
3938 |
|
3939 JSObject *thisobj = &args.thisv().toObject(); |
|
3940 if (thisobj->getClass() != &DebuggerSource_class) { |
|
3941 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
3942 "Debugger.Source", fnname, thisobj->getClass()->name); |
|
3943 return nullptr; |
|
3944 } |
|
3945 |
|
3946 if (!GetSourceReferent(thisobj)) { |
|
3947 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
3948 "Debugger.Frame", fnname, "prototype object"); |
|
3949 return nullptr; |
|
3950 } |
|
3951 |
|
3952 return thisobj; |
|
3953 } |
|
3954 |
|
3955 #define THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, fnname, args, obj, sourceObject) \ |
|
3956 CallArgs args = CallArgsFromVp(argc, vp); \ |
|
3957 RootedObject obj(cx, DebuggerSource_checkThis(cx, args, fnname)); \ |
|
3958 if (!obj) \ |
|
3959 return false; \ |
|
3960 RootedScriptSource sourceObject(cx, GetSourceReferent(obj)); \ |
|
3961 if (!sourceObject) \ |
|
3962 return false; |
|
3963 |
|
3964 static bool |
|
3965 DebuggerSource_getText(JSContext *cx, unsigned argc, Value *vp) |
|
3966 { |
|
3967 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get text)", args, obj, sourceObject); |
|
3968 |
|
3969 ScriptSource *ss = sourceObject->source(); |
|
3970 bool hasSourceData = ss->hasSourceData(); |
|
3971 if (!ss->hasSourceData() && !JSScript::loadSource(cx, ss, &hasSourceData)) |
|
3972 return false; |
|
3973 |
|
3974 JSString *str = hasSourceData ? ss->substring(cx, 0, ss->length()) |
|
3975 : js_NewStringCopyZ<CanGC>(cx, "[no source]"); |
|
3976 if (!str) |
|
3977 return false; |
|
3978 |
|
3979 args.rval().setString(str); |
|
3980 return true; |
|
3981 } |
|
3982 |
|
3983 static bool |
|
3984 DebuggerSource_getUrl(JSContext *cx, unsigned argc, Value *vp) |
|
3985 { |
|
3986 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject); |
|
3987 |
|
3988 ScriptSource *ss = sourceObject->source(); |
|
3989 if (ss->filename()) { |
|
3990 JSString *str = js_NewStringCopyZ<CanGC>(cx, ss->filename()); |
|
3991 if (!str) |
|
3992 return false; |
|
3993 args.rval().setString(str); |
|
3994 } else { |
|
3995 args.rval().setNull(); |
|
3996 } |
|
3997 return true; |
|
3998 } |
|
3999 |
|
4000 static bool |
|
4001 DebuggerSource_getDisplayURL(JSContext *cx, unsigned argc, Value *vp) |
|
4002 { |
|
4003 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject); |
|
4004 |
|
4005 ScriptSource *ss = sourceObject->source(); |
|
4006 JS_ASSERT(ss); |
|
4007 |
|
4008 if (ss->hasDisplayURL()) { |
|
4009 JSString *str = JS_NewUCStringCopyZ(cx, ss->displayURL()); |
|
4010 if (!str) |
|
4011 return false; |
|
4012 args.rval().setString(str); |
|
4013 } else { |
|
4014 args.rval().setNull(); |
|
4015 } |
|
4016 |
|
4017 return true; |
|
4018 } |
|
4019 |
|
4020 static bool |
|
4021 DebuggerSource_getElement(JSContext *cx, unsigned argc, Value *vp) |
|
4022 { |
|
4023 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get element)", args, obj, sourceObject); |
|
4024 |
|
4025 if (sourceObject->element()) { |
|
4026 args.rval().setObjectOrNull(sourceObject->element()); |
|
4027 if (!Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval())) |
|
4028 return false; |
|
4029 } else { |
|
4030 args.rval().setUndefined(); |
|
4031 } |
|
4032 return true; |
|
4033 } |
|
4034 |
|
4035 static bool |
|
4036 DebuggerSource_getElementProperty(JSContext *cx, unsigned argc, Value *vp) |
|
4037 { |
|
4038 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get elementAttributeName)", args, obj, sourceObject); |
|
4039 args.rval().set(sourceObject->elementAttributeName()); |
|
4040 return Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval()); |
|
4041 } |
|
4042 |
|
4043 static bool |
|
4044 DebuggerSource_getIntroductionScript(JSContext *cx, unsigned argc, Value *vp) |
|
4045 { |
|
4046 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionScript)", args, obj, sourceObject); |
|
4047 |
|
4048 RootedScript script(cx, sourceObject->introductionScript()); |
|
4049 if (script) { |
|
4050 RootedObject scriptDO(cx, Debugger::fromChildJSObject(obj)->wrapScript(cx, script)); |
|
4051 if (!scriptDO) |
|
4052 return false; |
|
4053 args.rval().setObject(*scriptDO); |
|
4054 } else { |
|
4055 args.rval().setUndefined(); |
|
4056 } |
|
4057 return true; |
|
4058 } |
|
4059 |
|
4060 static bool |
|
4061 DebuggerSource_getIntroductionOffset(JSContext *cx, unsigned argc, Value *vp) |
|
4062 { |
|
4063 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionOffset)", args, obj, sourceObject); |
|
4064 |
|
4065 // Regardless of what's recorded in the ScriptSourceObject and |
|
4066 // ScriptSource, only hand out the introduction offset if we also have |
|
4067 // the script within which it applies. |
|
4068 ScriptSource *ss = sourceObject->source(); |
|
4069 if (ss->hasIntroductionOffset() && sourceObject->introductionScript()) |
|
4070 args.rval().setInt32(ss->introductionOffset()); |
|
4071 else |
|
4072 args.rval().setUndefined(); |
|
4073 return true; |
|
4074 } |
|
4075 |
|
4076 static bool |
|
4077 DebuggerSource_getIntroductionType(JSContext *cx, unsigned argc, Value *vp) |
|
4078 { |
|
4079 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionType)", args, obj, sourceObject); |
|
4080 |
|
4081 ScriptSource *ss = sourceObject->source(); |
|
4082 if (ss->hasIntroductionType()) { |
|
4083 JSString *str = js_NewStringCopyZ<CanGC>(cx, ss->introductionType()); |
|
4084 if (!str) |
|
4085 return false; |
|
4086 args.rval().setString(str); |
|
4087 } else { |
|
4088 args.rval().setUndefined(); |
|
4089 } |
|
4090 return true; |
|
4091 } |
|
4092 |
|
4093 static const JSPropertySpec DebuggerSource_properties[] = { |
|
4094 JS_PSG("text", DebuggerSource_getText, 0), |
|
4095 JS_PSG("url", DebuggerSource_getUrl, 0), |
|
4096 JS_PSG("element", DebuggerSource_getElement, 0), |
|
4097 JS_PSG("displayURL", DebuggerSource_getDisplayURL, 0), |
|
4098 JS_PSG("introductionScript", DebuggerSource_getIntroductionScript, 0), |
|
4099 JS_PSG("introductionOffset", DebuggerSource_getIntroductionOffset, 0), |
|
4100 JS_PSG("introductionType", DebuggerSource_getIntroductionType, 0), |
|
4101 JS_PSG("elementAttributeName", DebuggerSource_getElementProperty, 0), |
|
4102 JS_PS_END |
|
4103 }; |
|
4104 |
|
4105 static const JSFunctionSpec DebuggerSource_methods[] = { |
|
4106 JS_FS_END |
|
4107 }; |
|
4108 |
|
4109 |
|
4110 /*** Debugger.Frame ******************************************************************************/ |
|
4111 |
|
4112 static void |
|
4113 UpdateFrameIterPc(FrameIter &iter) |
|
4114 { |
|
4115 if (iter.abstractFramePtr().isRematerializedFrame()) { |
|
4116 #ifdef DEBUG |
|
4117 // Rematerialized frames don't need their pc updated. The reason we |
|
4118 // need to update pc is because we might get the same Debugger.Frame |
|
4119 // object for multiple re-entries into debugger code from debuggee |
|
4120 // code. This reentrancy is not possible with rematerialized frames, |
|
4121 // because when returning to debuggee code, we would have bailed out |
|
4122 // to baseline. |
|
4123 // |
|
4124 // We walk the stack to assert that it doesn't need updating. |
|
4125 jit::RematerializedFrame *frame = iter.abstractFramePtr().asRematerializedFrame(); |
|
4126 jit::IonJSFrameLayout *jsFrame = (jit::IonJSFrameLayout *)frame->top(); |
|
4127 jit::JitActivation *activation = iter.activation()->asJit(); |
|
4128 |
|
4129 ActivationIterator activationIter(activation->cx()->runtime()); |
|
4130 while (activationIter.activation() != activation) |
|
4131 ++activationIter; |
|
4132 |
|
4133 jit::JitFrameIterator jitIter(activationIter); |
|
4134 while (!jitIter.isIonJS() || jitIter.jsFrame() != jsFrame) |
|
4135 ++jitIter; |
|
4136 |
|
4137 jit::InlineFrameIterator ionInlineIter(activation->cx(), &jitIter); |
|
4138 while (ionInlineIter.frameNo() != frame->frameNo()) |
|
4139 ++ionInlineIter; |
|
4140 |
|
4141 MOZ_ASSERT(ionInlineIter.pc() == iter.pc()); |
|
4142 #endif |
|
4143 return; |
|
4144 } |
|
4145 |
|
4146 iter.updatePcQuadratic(); |
|
4147 } |
|
4148 |
|
4149 static void |
|
4150 DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj) |
|
4151 { |
|
4152 AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->getPrivate()); |
|
4153 if (frame.isScriptFrameIterData()) |
|
4154 fop->delete_((ScriptFrameIter::Data *) frame.raw()); |
|
4155 obj->setPrivate(nullptr); |
|
4156 } |
|
4157 |
|
4158 static void |
|
4159 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp *fop, AbstractFramePtr frame, |
|
4160 JSObject *frameobj) |
|
4161 { |
|
4162 /* If this frame has an onStep handler, decrement the script's count. */ |
|
4163 if (!frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined()) |
|
4164 frame.script()->decrementStepModeCount(fop); |
|
4165 } |
|
4166 |
|
4167 static void |
|
4168 DebuggerFrame_finalize(FreeOp *fop, JSObject *obj) |
|
4169 { |
|
4170 DebuggerFrame_freeScriptFrameIterData(fop, obj); |
|
4171 } |
|
4172 |
|
4173 const Class DebuggerFrame_class = { |
|
4174 "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT), |
|
4175 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
|
4176 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, DebuggerFrame_finalize |
|
4177 }; |
|
4178 |
|
4179 static JSObject * |
|
4180 CheckThisFrame(JSContext *cx, const CallArgs &args, const char *fnname, bool checkLive) |
|
4181 { |
|
4182 if (!args.thisv().isObject()) { |
|
4183 ReportObjectRequired(cx); |
|
4184 return nullptr; |
|
4185 } |
|
4186 JSObject *thisobj = &args.thisv().toObject(); |
|
4187 if (thisobj->getClass() != &DebuggerFrame_class) { |
|
4188 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
4189 "Debugger.Frame", fnname, thisobj->getClass()->name); |
|
4190 return nullptr; |
|
4191 } |
|
4192 |
|
4193 /* |
|
4194 * Forbid Debugger.Frame.prototype, which is of class DebuggerFrame_class |
|
4195 * but isn't really a working Debugger.Frame object. The prototype object |
|
4196 * is distinguished by having a nullptr private value. Also, forbid popped |
|
4197 * frames. |
|
4198 */ |
|
4199 if (!thisobj->getPrivate()) { |
|
4200 if (thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_OWNER).isUndefined()) { |
|
4201 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
4202 "Debugger.Frame", fnname, "prototype object"); |
|
4203 return nullptr; |
|
4204 } |
|
4205 if (checkLive) { |
|
4206 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, |
|
4207 "Debugger.Frame"); |
|
4208 return nullptr; |
|
4209 } |
|
4210 } |
|
4211 return thisobj; |
|
4212 } |
|
4213 |
|
4214 /* |
|
4215 * To make frequently fired hooks like onEnterFrame more performant, |
|
4216 * Debugger.Frame methods should not create a ScriptFrameIter unless it |
|
4217 * absolutely needs to. That is, unless the method has to call a method on |
|
4218 * ScriptFrameIter that's otherwise not available on AbstractFramePtr. |
|
4219 * |
|
4220 * When a Debugger.Frame is first created, its private slot is set to the |
|
4221 * AbstractFramePtr itself. The first time the users asks for a |
|
4222 * ScriptFrameIter, we construct one, have it settle on the frame pointed to |
|
4223 * by the AbstractFramePtr and cache its internal Data in the Debugger.Frame |
|
4224 * object's private slot. Subsequent uses of the Debugger.Frame object will |
|
4225 * always create a ScriptFrameIter from the cached Data. |
|
4226 * |
|
4227 * Methods that only need the AbstractFramePtr should use THIS_FRAME. |
|
4228 * Methods that need a ScriptFrameIterator should use THIS_FRAME_ITER. |
|
4229 */ |
|
4230 |
|
4231 #define THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj) \ |
|
4232 CallArgs args = CallArgsFromVp(argc, vp); \ |
|
4233 RootedObject thisobj(cx, CheckThisFrame(cx, args, fnname, true)); \ |
|
4234 if (!thisobj) \ |
|
4235 return false |
|
4236 |
|
4237 #define THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame) \ |
|
4238 THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \ |
|
4239 AbstractFramePtr frame = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \ |
|
4240 if (frame.isScriptFrameIterData()) { \ |
|
4241 ScriptFrameIter iter(*(ScriptFrameIter::Data *)(frame.raw())); \ |
|
4242 frame = iter.abstractFramePtr(); \ |
|
4243 } |
|
4244 |
|
4245 #define THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter) \ |
|
4246 THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \ |
|
4247 Maybe<ScriptFrameIter> maybeIter; \ |
|
4248 { \ |
|
4249 AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \ |
|
4250 if (f.isScriptFrameIterData()) { \ |
|
4251 maybeIter.construct(*(ScriptFrameIter::Data *)(f.raw())); \ |
|
4252 } else { \ |
|
4253 maybeIter.construct(cx, ScriptFrameIter::ALL_CONTEXTS, \ |
|
4254 ScriptFrameIter::GO_THROUGH_SAVED); \ |
|
4255 ScriptFrameIter &iter = maybeIter.ref(); \ |
|
4256 while (iter.isIon() || iter.abstractFramePtr() != f) \ |
|
4257 ++iter; \ |
|
4258 AbstractFramePtr data = iter.copyDataAsAbstractFramePtr(); \ |
|
4259 if (!data) \ |
|
4260 return false; \ |
|
4261 thisobj->setPrivate(data.raw()); \ |
|
4262 } \ |
|
4263 } \ |
|
4264 ScriptFrameIter &iter = maybeIter.ref() |
|
4265 |
|
4266 #define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, frame, dbg) \ |
|
4267 THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame); \ |
|
4268 Debugger *dbg = Debugger::fromChildJSObject(thisobj) |
|
4269 |
|
4270 #define THIS_FRAME_OWNER_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter, dbg) \ |
|
4271 THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter); \ |
|
4272 Debugger *dbg = Debugger::fromChildJSObject(thisobj) |
|
4273 |
|
4274 static bool |
|
4275 DebuggerFrame_getType(JSContext *cx, unsigned argc, Value *vp) |
|
4276 { |
|
4277 THIS_FRAME(cx, argc, vp, "get type", args, thisobj, frame); |
|
4278 |
|
4279 /* |
|
4280 * Indirect eval frames are both isGlobalFrame() and isEvalFrame(), so the |
|
4281 * order of checks here is significant. |
|
4282 */ |
|
4283 args.rval().setString(frame.isEvalFrame() |
|
4284 ? cx->names().eval |
|
4285 : frame.isGlobalFrame() |
|
4286 ? cx->names().global |
|
4287 : cx->names().call); |
|
4288 return true; |
|
4289 } |
|
4290 |
|
4291 static bool |
|
4292 DebuggerFrame_getImplementation(JSContext *cx, unsigned argc, Value *vp) |
|
4293 { |
|
4294 THIS_FRAME(cx, argc, vp, "get implementation", args, thisobj, frame); |
|
4295 |
|
4296 const char *s; |
|
4297 if (frame.isBaselineFrame()) |
|
4298 s = "baseline"; |
|
4299 else if (frame.isRematerializedFrame()) |
|
4300 s = "ion"; |
|
4301 else |
|
4302 s = "interpreter"; |
|
4303 |
|
4304 JSAtom *str = Atomize(cx, s, strlen(s)); |
|
4305 if (!str) |
|
4306 return false; |
|
4307 |
|
4308 args.rval().setString(str); |
|
4309 return true; |
|
4310 } |
|
4311 |
|
4312 static bool |
|
4313 DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp) |
|
4314 { |
|
4315 THIS_FRAME_OWNER_ITER(cx, argc, vp, "get environment", args, thisobj, _, iter, dbg); |
|
4316 |
|
4317 Rooted<Env*> env(cx); |
|
4318 { |
|
4319 AutoCompartment ac(cx, iter.abstractFramePtr().scopeChain()); |
|
4320 UpdateFrameIterPc(iter); |
|
4321 env = GetDebugScopeForFrame(cx, iter.abstractFramePtr(), iter.pc()); |
|
4322 if (!env) |
|
4323 return false; |
|
4324 } |
|
4325 |
|
4326 return dbg->wrapEnvironment(cx, env, args.rval()); |
|
4327 } |
|
4328 |
|
4329 static bool |
|
4330 DebuggerFrame_getCallee(JSContext *cx, unsigned argc, Value *vp) |
|
4331 { |
|
4332 THIS_FRAME(cx, argc, vp, "get callee", args, thisobj, frame); |
|
4333 RootedValue calleev(cx, frame.isNonEvalFunctionFrame() ? frame.calleev() : NullValue()); |
|
4334 if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &calleev)) |
|
4335 return false; |
|
4336 args.rval().set(calleev); |
|
4337 return true; |
|
4338 } |
|
4339 |
|
4340 static bool |
|
4341 DebuggerFrame_getGenerator(JSContext *cx, unsigned argc, Value *vp) |
|
4342 { |
|
4343 THIS_FRAME(cx, argc, vp, "get generator", args, thisobj, frame); |
|
4344 args.rval().setBoolean(frame.isGeneratorFrame()); |
|
4345 return true; |
|
4346 } |
|
4347 |
|
4348 static bool |
|
4349 DebuggerFrame_getConstructing(JSContext *cx, unsigned argc, Value *vp) |
|
4350 { |
|
4351 THIS_FRAME_ITER(cx, argc, vp, "get constructing", args, thisobj, _, iter); |
|
4352 args.rval().setBoolean(iter.isFunctionFrame() && iter.isConstructing()); |
|
4353 return true; |
|
4354 } |
|
4355 |
|
4356 static bool |
|
4357 DebuggerFrame_getThis(JSContext *cx, unsigned argc, Value *vp) |
|
4358 { |
|
4359 THIS_FRAME_ITER(cx, argc, vp, "get this", args, thisobj, _, iter); |
|
4360 RootedValue thisv(cx); |
|
4361 { |
|
4362 AutoCompartment ac(cx, iter.scopeChain()); |
|
4363 if (!iter.computeThis(cx)) |
|
4364 return false; |
|
4365 thisv = iter.computedThisValue(); |
|
4366 } |
|
4367 if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &thisv)) |
|
4368 return false; |
|
4369 args.rval().set(thisv); |
|
4370 return true; |
|
4371 } |
|
4372 |
|
4373 static bool |
|
4374 DebuggerFrame_getOlder(JSContext *cx, unsigned argc, Value *vp) |
|
4375 { |
|
4376 THIS_FRAME_ITER(cx, argc, vp, "get this", args, thisobj, _, iter); |
|
4377 Debugger *dbg = Debugger::fromChildJSObject(thisobj); |
|
4378 |
|
4379 for (++iter; !iter.done(); ++iter) { |
|
4380 if (dbg->observesFrame(iter)) { |
|
4381 if (iter.isIon() && !iter.ensureHasRematerializedFrame()) |
|
4382 return false; |
|
4383 return dbg->getScriptFrame(cx, iter, args.rval()); |
|
4384 } |
|
4385 } |
|
4386 args.rval().setNull(); |
|
4387 return true; |
|
4388 } |
|
4389 |
|
4390 const Class DebuggerArguments_class = { |
|
4391 "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT), |
|
4392 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
|
4393 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub |
|
4394 }; |
|
4395 |
|
4396 /* The getter used for each element of frame.arguments. See DebuggerFrame_getArguments. */ |
|
4397 static bool |
|
4398 DebuggerArguments_getArg(JSContext *cx, unsigned argc, Value *vp) |
|
4399 { |
|
4400 CallArgs args = CallArgsFromVp(argc, vp); |
|
4401 int32_t i = args.callee().as<JSFunction>().getExtendedSlot(0).toInt32(); |
|
4402 |
|
4403 /* Check that the this value is an Arguments object. */ |
|
4404 if (!args.thisv().isObject()) { |
|
4405 ReportObjectRequired(cx); |
|
4406 return false; |
|
4407 } |
|
4408 RootedObject argsobj(cx, &args.thisv().toObject()); |
|
4409 if (argsobj->getClass() != &DebuggerArguments_class) { |
|
4410 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
4411 "Arguments", "getArgument", argsobj->getClass()->name); |
|
4412 return false; |
|
4413 } |
|
4414 |
|
4415 /* |
|
4416 * Put the Debugger.Frame into the this-value slot, then use THIS_FRAME |
|
4417 * to check that it is still live and get the fp. |
|
4418 */ |
|
4419 args.setThis(argsobj->getReservedSlot(JSSLOT_DEBUGARGUMENTS_FRAME)); |
|
4420 THIS_FRAME(cx, argc, vp, "get argument", ca2, thisobj, frame); |
|
4421 |
|
4422 /* |
|
4423 * Since getters can be extracted and applied to other objects, |
|
4424 * there is no guarantee this object has an ith argument. |
|
4425 */ |
|
4426 JS_ASSERT(i >= 0); |
|
4427 RootedValue arg(cx); |
|
4428 RootedScript script(cx); |
|
4429 if (unsigned(i) < frame.numActualArgs()) { |
|
4430 script = frame.script(); |
|
4431 if (unsigned(i) < frame.numFormalArgs() && script->formalIsAliased(i)) { |
|
4432 for (AliasedFormalIter fi(script); ; fi++) { |
|
4433 if (fi.frameIndex() == unsigned(i)) { |
|
4434 arg = frame.callObj().aliasedVar(fi); |
|
4435 break; |
|
4436 } |
|
4437 } |
|
4438 } else if (script->argsObjAliasesFormals() && frame.hasArgsObj()) { |
|
4439 arg = frame.argsObj().arg(i); |
|
4440 } else { |
|
4441 arg = frame.unaliasedActual(i, DONT_CHECK_ALIASING); |
|
4442 } |
|
4443 } else { |
|
4444 arg.setUndefined(); |
|
4445 } |
|
4446 |
|
4447 if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &arg)) |
|
4448 return false; |
|
4449 args.rval().set(arg); |
|
4450 return true; |
|
4451 } |
|
4452 |
|
4453 static bool |
|
4454 DebuggerFrame_getArguments(JSContext *cx, unsigned argc, Value *vp) |
|
4455 { |
|
4456 THIS_FRAME(cx, argc, vp, "get arguments", args, thisobj, frame); |
|
4457 Value argumentsv = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS); |
|
4458 if (!argumentsv.isUndefined()) { |
|
4459 JS_ASSERT(argumentsv.isObjectOrNull()); |
|
4460 args.rval().set(argumentsv); |
|
4461 return true; |
|
4462 } |
|
4463 |
|
4464 RootedObject argsobj(cx); |
|
4465 if (frame.hasArgs()) { |
|
4466 /* Create an arguments object. */ |
|
4467 Rooted<GlobalObject*> global(cx, &args.callee().global()); |
|
4468 JSObject *proto = GlobalObject::getOrCreateArrayPrototype(cx, global); |
|
4469 if (!proto) |
|
4470 return false; |
|
4471 argsobj = NewObjectWithGivenProto(cx, &DebuggerArguments_class, proto, global); |
|
4472 if (!argsobj) |
|
4473 return false; |
|
4474 SetReservedSlot(argsobj, JSSLOT_DEBUGARGUMENTS_FRAME, ObjectValue(*thisobj)); |
|
4475 |
|
4476 JS_ASSERT(frame.numActualArgs() <= 0x7fffffff); |
|
4477 unsigned fargc = frame.numActualArgs(); |
|
4478 RootedValue fargcVal(cx, Int32Value(fargc)); |
|
4479 if (!DefineNativeProperty(cx, argsobj, cx->names().length, |
|
4480 fargcVal, nullptr, nullptr, |
|
4481 JSPROP_PERMANENT | JSPROP_READONLY)) |
|
4482 { |
|
4483 return false; |
|
4484 } |
|
4485 |
|
4486 Rooted<jsid> id(cx); |
|
4487 for (unsigned i = 0; i < fargc; i++) { |
|
4488 RootedFunction getobj(cx); |
|
4489 getobj = NewFunction(cx, js::NullPtr(), DebuggerArguments_getArg, 0, |
|
4490 JSFunction::NATIVE_FUN, global, js::NullPtr(), |
|
4491 JSFunction::ExtendedFinalizeKind); |
|
4492 if (!getobj) |
|
4493 return false; |
|
4494 id = INT_TO_JSID(i); |
|
4495 if (!getobj || |
|
4496 !DefineNativeProperty(cx, argsobj, id, UndefinedHandleValue, |
|
4497 JS_DATA_TO_FUNC_PTR(PropertyOp, getobj.get()), nullptr, |
|
4498 JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER)) |
|
4499 { |
|
4500 return false; |
|
4501 } |
|
4502 getobj->setExtendedSlot(0, Int32Value(i)); |
|
4503 } |
|
4504 } else { |
|
4505 argsobj = nullptr; |
|
4506 } |
|
4507 args.rval().setObjectOrNull(argsobj); |
|
4508 thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS, args.rval()); |
|
4509 return true; |
|
4510 } |
|
4511 |
|
4512 static bool |
|
4513 DebuggerFrame_getScript(JSContext *cx, unsigned argc, Value *vp) |
|
4514 { |
|
4515 THIS_FRAME(cx, argc, vp, "get script", args, thisobj, frame); |
|
4516 Debugger *debug = Debugger::fromChildJSObject(thisobj); |
|
4517 |
|
4518 RootedObject scriptObject(cx); |
|
4519 if (frame.isFunctionFrame() && !frame.isEvalFrame()) { |
|
4520 RootedFunction callee(cx, frame.callee()); |
|
4521 if (callee->isInterpreted()) { |
|
4522 RootedScript script(cx, callee->nonLazyScript()); |
|
4523 scriptObject = debug->wrapScript(cx, script); |
|
4524 if (!scriptObject) |
|
4525 return false; |
|
4526 } |
|
4527 } else { |
|
4528 /* |
|
4529 * We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script |
|
4530 * frames. |
|
4531 */ |
|
4532 RootedScript script(cx, frame.script()); |
|
4533 scriptObject = debug->wrapScript(cx, script); |
|
4534 if (!scriptObject) |
|
4535 return false; |
|
4536 } |
|
4537 args.rval().setObjectOrNull(scriptObject); |
|
4538 return true; |
|
4539 } |
|
4540 |
|
4541 static bool |
|
4542 DebuggerFrame_getOffset(JSContext *cx, unsigned argc, Value *vp) |
|
4543 { |
|
4544 THIS_FRAME_ITER(cx, argc, vp, "get offset", args, thisobj, _, iter); |
|
4545 JSScript *script = iter.script(); |
|
4546 UpdateFrameIterPc(iter); |
|
4547 jsbytecode *pc = iter.pc(); |
|
4548 size_t offset = script->pcToOffset(pc); |
|
4549 args.rval().setNumber(double(offset)); |
|
4550 return true; |
|
4551 } |
|
4552 |
|
4553 static bool |
|
4554 DebuggerFrame_getLive(JSContext *cx, unsigned argc, Value *vp) |
|
4555 { |
|
4556 CallArgs args = CallArgsFromVp(argc, vp); |
|
4557 JSObject *thisobj = CheckThisFrame(cx, args, "get live", false); |
|
4558 if (!thisobj) |
|
4559 return false; |
|
4560 bool hasFrame = !!thisobj->getPrivate(); |
|
4561 args.rval().setBoolean(hasFrame); |
|
4562 return true; |
|
4563 } |
|
4564 |
|
4565 static bool |
|
4566 IsValidHook(const Value &v) |
|
4567 { |
|
4568 return v.isUndefined() || (v.isObject() && v.toObject().isCallable()); |
|
4569 } |
|
4570 |
|
4571 static bool |
|
4572 DebuggerFrame_getOnStep(JSContext *cx, unsigned argc, Value *vp) |
|
4573 { |
|
4574 THIS_FRAME(cx, argc, vp, "get onStep", args, thisobj, frame); |
|
4575 (void) frame; // Silence GCC warning |
|
4576 Value handler = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER); |
|
4577 JS_ASSERT(IsValidHook(handler)); |
|
4578 args.rval().set(handler); |
|
4579 return true; |
|
4580 } |
|
4581 |
|
4582 static bool |
|
4583 DebuggerFrame_setOnStep(JSContext *cx, unsigned argc, Value *vp) |
|
4584 { |
|
4585 REQUIRE_ARGC("Debugger.Frame.set onStep", 1); |
|
4586 THIS_FRAME(cx, argc, vp, "set onStep", args, thisobj, frame); |
|
4587 if (!IsValidHook(args[0])) { |
|
4588 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED); |
|
4589 return false; |
|
4590 } |
|
4591 |
|
4592 Value prior = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER); |
|
4593 if (!args[0].isUndefined() && prior.isUndefined()) { |
|
4594 // Single stepping toggled off->on. |
|
4595 AutoCompartment ac(cx, frame.scopeChain()); |
|
4596 if (!frame.script()->incrementStepModeCount(cx)) |
|
4597 return false; |
|
4598 } else if (args[0].isUndefined() && !prior.isUndefined()) { |
|
4599 // Single stepping toggled on->off. |
|
4600 frame.script()->decrementStepModeCount(cx->runtime()->defaultFreeOp()); |
|
4601 } |
|
4602 |
|
4603 /* Now that the step mode switch has succeeded, we can install the handler. */ |
|
4604 thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, args[0]); |
|
4605 args.rval().setUndefined(); |
|
4606 return true; |
|
4607 } |
|
4608 |
|
4609 static bool |
|
4610 DebuggerFrame_getOnPop(JSContext *cx, unsigned argc, Value *vp) |
|
4611 { |
|
4612 THIS_FRAME(cx, argc, vp, "get onPop", args, thisobj, frame); |
|
4613 (void) frame; // Silence GCC warning |
|
4614 Value handler = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER); |
|
4615 JS_ASSERT(IsValidHook(handler)); |
|
4616 args.rval().set(handler); |
|
4617 return true; |
|
4618 } |
|
4619 |
|
4620 static bool |
|
4621 DebuggerFrame_setOnPop(JSContext *cx, unsigned argc, Value *vp) |
|
4622 { |
|
4623 REQUIRE_ARGC("Debugger.Frame.set onPop", 1); |
|
4624 THIS_FRAME(cx, argc, vp, "set onPop", args, thisobj, frame); |
|
4625 (void) frame; // Silence GCC warning |
|
4626 if (!IsValidHook(args[0])) { |
|
4627 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED); |
|
4628 return false; |
|
4629 } |
|
4630 |
|
4631 thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER, args[0]); |
|
4632 args.rval().setUndefined(); |
|
4633 return true; |
|
4634 } |
|
4635 |
|
4636 /* |
|
4637 * Evaluate |chars[0..length-1]| in the environment |env|, treating that |
|
4638 * source as appearing starting at |lineno| in |filename|. Store the return |
|
4639 * value in |*rval|. Use |thisv| as the 'this' value. |
|
4640 * |
|
4641 * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env| |
|
4642 * must be either |frame|'s DebugScopeObject, or some extension of that |
|
4643 * environment; either way, |frame|'s scope is where newly declared variables |
|
4644 * go. In this case, |frame| must have a computed 'this' value, equal to |thisv|. |
|
4645 */ |
|
4646 bool |
|
4647 js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame, |
|
4648 ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno, |
|
4649 MutableHandleValue rval) |
|
4650 { |
|
4651 assertSameCompartment(cx, env, frame); |
|
4652 JS_ASSERT_IF(frame, thisv.get() == frame.thisValue()); |
|
4653 |
|
4654 JS_ASSERT(!IsPoisonedPtr(chars.get())); |
|
4655 |
|
4656 /* |
|
4657 * NB: This function breaks the assumption that the compiler can see all |
|
4658 * calls and properly compute a static level. In practice, any non-zero |
|
4659 * static level will suffice. |
|
4660 */ |
|
4661 CompileOptions options(cx); |
|
4662 options.setCompileAndGo(true) |
|
4663 .setForEval(true) |
|
4664 .setNoScriptRval(false) |
|
4665 .setFileAndLine(filename, lineno) |
|
4666 .setCanLazilyParse(false) |
|
4667 .setIntroductionType("debugger eval"); |
|
4668 RootedScript callerScript(cx, frame ? frame.script() : nullptr); |
|
4669 SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership); |
|
4670 RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), env, callerScript, |
|
4671 options, srcBuf, |
|
4672 /* source = */ nullptr, |
|
4673 /* staticLevel = */ frame ? 1 : 0)); |
|
4674 if (!script) |
|
4675 return false; |
|
4676 |
|
4677 script->setActiveEval(); |
|
4678 ExecuteType type = !frame ? EXECUTE_DEBUG_GLOBAL : EXECUTE_DEBUG; |
|
4679 return ExecuteKernel(cx, script, *env, thisv, type, frame, rval.address()); |
|
4680 } |
|
4681 |
|
4682 enum EvalBindings { EvalHasExtraBindings = true, EvalWithDefaultBindings = false }; |
|
4683 |
|
4684 static bool |
|
4685 DebuggerGenericEval(JSContext *cx, const char *fullMethodName, const Value &code, |
|
4686 EvalBindings evalWithBindings, HandleValue bindings, HandleValue options, |
|
4687 MutableHandleValue vp, Debugger *dbg, HandleObject scope, |
|
4688 ScriptFrameIter *iter) |
|
4689 { |
|
4690 /* Either we're specifying the frame, or a global. */ |
|
4691 JS_ASSERT_IF(iter, !scope); |
|
4692 JS_ASSERT_IF(!iter, scope && scope->is<GlobalObject>()); |
|
4693 |
|
4694 /* Check the first argument, the eval code string. */ |
|
4695 if (!code.isString()) { |
|
4696 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, |
|
4697 fullMethodName, "string", InformalValueTypeName(code)); |
|
4698 return false; |
|
4699 } |
|
4700 Rooted<JSFlatString *> flat(cx, code.toString()->ensureFlat(cx)); |
|
4701 if (!flat) |
|
4702 return false; |
|
4703 |
|
4704 /* |
|
4705 * Gather keys and values of bindings, if any. This must be done in the |
|
4706 * debugger compartment, since that is where any exceptions must be |
|
4707 * thrown. |
|
4708 */ |
|
4709 AutoIdVector keys(cx); |
|
4710 AutoValueVector values(cx); |
|
4711 if (evalWithBindings) { |
|
4712 RootedObject bindingsobj(cx, NonNullObject(cx, bindings)); |
|
4713 if (!bindingsobj || |
|
4714 !GetPropertyNames(cx, bindingsobj, JSITER_OWNONLY, &keys) || |
|
4715 !values.growBy(keys.length())) |
|
4716 { |
|
4717 return false; |
|
4718 } |
|
4719 for (size_t i = 0; i < keys.length(); i++) { |
|
4720 MutableHandleValue valp = values.handleAt(i); |
|
4721 if (!JSObject::getGeneric(cx, bindingsobj, bindingsobj, keys.handleAt(i), valp) || |
|
4722 !dbg->unwrapDebuggeeValue(cx, valp)) |
|
4723 { |
|
4724 return false; |
|
4725 } |
|
4726 } |
|
4727 } |
|
4728 |
|
4729 /* Set options from object if provided. */ |
|
4730 JSAutoByteString url_bytes; |
|
4731 char *url = nullptr; |
|
4732 unsigned lineNumber = 1; |
|
4733 |
|
4734 if (options.isObject()) { |
|
4735 RootedObject opts(cx, &options.toObject()); |
|
4736 RootedValue v(cx); |
|
4737 |
|
4738 if (!JS_GetProperty(cx, opts, "url", &v)) |
|
4739 return false; |
|
4740 if (!v.isUndefined()) { |
|
4741 RootedString url_str(cx, ToString<CanGC>(cx, v)); |
|
4742 if (!url_str) |
|
4743 return false; |
|
4744 url = url_bytes.encodeLatin1(cx, url_str); |
|
4745 if (!url) |
|
4746 return false; |
|
4747 } |
|
4748 |
|
4749 if (!JS_GetProperty(cx, opts, "lineNumber", &v)) |
|
4750 return false; |
|
4751 if (!v.isUndefined()) { |
|
4752 uint32_t lineno; |
|
4753 if (!ToUint32(cx, v, &lineno)) |
|
4754 return false; |
|
4755 lineNumber = lineno; |
|
4756 } |
|
4757 } |
|
4758 |
|
4759 Maybe<AutoCompartment> ac; |
|
4760 if (iter) |
|
4761 ac.construct(cx, iter->scopeChain()); |
|
4762 else |
|
4763 ac.construct(cx, scope); |
|
4764 |
|
4765 RootedValue thisv(cx); |
|
4766 Rooted<Env *> env(cx); |
|
4767 if (iter) { |
|
4768 /* ExecuteInEnv requires 'fp' to have a computed 'this" value. */ |
|
4769 if (!iter->computeThis(cx)) |
|
4770 return false; |
|
4771 thisv = iter->computedThisValue(); |
|
4772 env = GetDebugScopeForFrame(cx, iter->abstractFramePtr(), iter->pc()); |
|
4773 if (!env) |
|
4774 return false; |
|
4775 } else { |
|
4776 /* |
|
4777 * Use the global as 'this'. If the global is an inner object, it |
|
4778 * should have a thisObject hook that returns the appropriate outer |
|
4779 * object. |
|
4780 */ |
|
4781 JSObject *thisObj = JSObject::thisObject(cx, scope); |
|
4782 if (!thisObj) |
|
4783 return false; |
|
4784 thisv = ObjectValue(*thisObj); |
|
4785 env = scope; |
|
4786 } |
|
4787 |
|
4788 /* If evalWithBindings, create the inner environment. */ |
|
4789 if (evalWithBindings) { |
|
4790 /* TODO - This should probably be a Call object, like ES5 strict eval. */ |
|
4791 env = NewObjectWithGivenProto(cx, &JSObject::class_, nullptr, env); |
|
4792 if (!env) |
|
4793 return false; |
|
4794 RootedId id(cx); |
|
4795 for (size_t i = 0; i < keys.length(); i++) { |
|
4796 id = keys[i]; |
|
4797 MutableHandleValue val = values.handleAt(i); |
|
4798 if (!cx->compartment()->wrap(cx, val) || |
|
4799 !DefineNativeProperty(cx, env, id, val, nullptr, nullptr, 0)) |
|
4800 { |
|
4801 return false; |
|
4802 } |
|
4803 } |
|
4804 } |
|
4805 |
|
4806 /* Run the code and produce the completion value. */ |
|
4807 RootedValue rval(cx); |
|
4808 JS::Anchor<JSString *> anchor(flat); |
|
4809 AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr(); |
|
4810 bool ok = EvaluateInEnv(cx, env, thisv, frame, |
|
4811 ConstTwoByteChars(flat->chars(), flat->length()), |
|
4812 flat->length(), url ? url : "debugger eval code", lineNumber, &rval); |
|
4813 return dbg->receiveCompletionValue(ac, ok, rval, vp); |
|
4814 } |
|
4815 |
|
4816 static bool |
|
4817 DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp) |
|
4818 { |
|
4819 THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter); |
|
4820 REQUIRE_ARGC("Debugger.Frame.prototype.eval", 1); |
|
4821 Debugger *dbg = Debugger::fromChildJSObject(thisobj); |
|
4822 UpdateFrameIterPc(iter); |
|
4823 return DebuggerGenericEval(cx, "Debugger.Frame.prototype.eval", |
|
4824 args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue, |
|
4825 args.get(1), args.rval(), dbg, js::NullPtr(), &iter); |
|
4826 } |
|
4827 |
|
4828 static bool |
|
4829 DebuggerFrame_evalWithBindings(JSContext *cx, unsigned argc, Value *vp) |
|
4830 { |
|
4831 THIS_FRAME_ITER(cx, argc, vp, "evalWithBindings", args, thisobj, _, iter); |
|
4832 REQUIRE_ARGC("Debugger.Frame.prototype.evalWithBindings", 2); |
|
4833 Debugger *dbg = Debugger::fromChildJSObject(thisobj); |
|
4834 UpdateFrameIterPc(iter); |
|
4835 return DebuggerGenericEval(cx, "Debugger.Frame.prototype.evalWithBindings", |
|
4836 args[0], EvalHasExtraBindings, args[1], args.get(2), |
|
4837 args.rval(), dbg, js::NullPtr(), &iter); |
|
4838 } |
|
4839 |
|
4840 static bool |
|
4841 DebuggerFrame_construct(JSContext *cx, unsigned argc, Value *vp) |
|
4842 { |
|
4843 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, |
|
4844 "Debugger.Frame"); |
|
4845 return false; |
|
4846 } |
|
4847 |
|
4848 static const JSPropertySpec DebuggerFrame_properties[] = { |
|
4849 JS_PSG("arguments", DebuggerFrame_getArguments, 0), |
|
4850 JS_PSG("callee", DebuggerFrame_getCallee, 0), |
|
4851 JS_PSG("constructing", DebuggerFrame_getConstructing, 0), |
|
4852 JS_PSG("environment", DebuggerFrame_getEnvironment, 0), |
|
4853 JS_PSG("generator", DebuggerFrame_getGenerator, 0), |
|
4854 JS_PSG("live", DebuggerFrame_getLive, 0), |
|
4855 JS_PSG("offset", DebuggerFrame_getOffset, 0), |
|
4856 JS_PSG("older", DebuggerFrame_getOlder, 0), |
|
4857 JS_PSG("script", DebuggerFrame_getScript, 0), |
|
4858 JS_PSG("this", DebuggerFrame_getThis, 0), |
|
4859 JS_PSG("type", DebuggerFrame_getType, 0), |
|
4860 JS_PSG("implementation", DebuggerFrame_getImplementation, 0), |
|
4861 JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0), |
|
4862 JS_PSGS("onPop", DebuggerFrame_getOnPop, DebuggerFrame_setOnPop, 0), |
|
4863 JS_PS_END |
|
4864 }; |
|
4865 |
|
4866 static const JSFunctionSpec DebuggerFrame_methods[] = { |
|
4867 JS_FN("eval", DebuggerFrame_eval, 1, 0), |
|
4868 JS_FN("evalWithBindings", DebuggerFrame_evalWithBindings, 1, 0), |
|
4869 JS_FS_END |
|
4870 }; |
|
4871 |
|
4872 |
|
4873 /*** Debugger.Object *****************************************************************************/ |
|
4874 |
|
4875 static void |
|
4876 DebuggerObject_trace(JSTracer *trc, JSObject *obj) |
|
4877 { |
|
4878 /* |
|
4879 * There is a barrier on private pointers, so the Unbarriered marking |
|
4880 * is okay. |
|
4881 */ |
|
4882 if (JSObject *referent = (JSObject *) obj->getPrivate()) { |
|
4883 MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Object referent"); |
|
4884 obj->setPrivateUnbarriered(referent); |
|
4885 } |
|
4886 } |
|
4887 |
|
4888 const Class DebuggerObject_class = { |
|
4889 "Object", |
|
4890 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | |
|
4891 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT), |
|
4892 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
|
4893 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, |
|
4894 nullptr, /* call */ |
|
4895 nullptr, /* hasInstance */ |
|
4896 nullptr, /* construct */ |
|
4897 DebuggerObject_trace |
|
4898 }; |
|
4899 |
|
4900 static JSObject * |
|
4901 DebuggerObject_checkThis(JSContext *cx, const CallArgs &args, const char *fnname) |
|
4902 { |
|
4903 if (!args.thisv().isObject()) { |
|
4904 ReportObjectRequired(cx); |
|
4905 return nullptr; |
|
4906 } |
|
4907 JSObject *thisobj = &args.thisv().toObject(); |
|
4908 if (thisobj->getClass() != &DebuggerObject_class) { |
|
4909 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
4910 "Debugger.Object", fnname, thisobj->getClass()->name); |
|
4911 return nullptr; |
|
4912 } |
|
4913 |
|
4914 /* |
|
4915 * Forbid Debugger.Object.prototype, which is of class DebuggerObject_class |
|
4916 * but isn't a real working Debugger.Object. The prototype object is |
|
4917 * distinguished by having no referent. |
|
4918 */ |
|
4919 if (!thisobj->getPrivate()) { |
|
4920 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
4921 "Debugger.Object", fnname, "prototype object"); |
|
4922 return nullptr; |
|
4923 } |
|
4924 return thisobj; |
|
4925 } |
|
4926 |
|
4927 #define THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, fnname, args, obj) \ |
|
4928 CallArgs args = CallArgsFromVp(argc, vp); \ |
|
4929 RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname)); \ |
|
4930 if (!obj) \ |
|
4931 return false; \ |
|
4932 obj = (JSObject *) obj->getPrivate(); \ |
|
4933 JS_ASSERT(obj) |
|
4934 |
|
4935 #define THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, fnname, args, dbg, obj) \ |
|
4936 CallArgs args = CallArgsFromVp(argc, vp); \ |
|
4937 RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname)); \ |
|
4938 if (!obj) \ |
|
4939 return false; \ |
|
4940 Debugger *dbg = Debugger::fromChildJSObject(obj); \ |
|
4941 obj = (JSObject *) obj->getPrivate(); \ |
|
4942 JS_ASSERT(obj) |
|
4943 |
|
4944 static bool |
|
4945 DebuggerObject_construct(JSContext *cx, unsigned argc, Value *vp) |
|
4946 { |
|
4947 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, |
|
4948 "Debugger.Object"); |
|
4949 return false; |
|
4950 } |
|
4951 |
|
4952 static bool |
|
4953 DebuggerObject_getProto(JSContext *cx, unsigned argc, Value *vp) |
|
4954 { |
|
4955 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get proto", args, dbg, refobj); |
|
4956 RootedObject proto(cx); |
|
4957 { |
|
4958 AutoCompartment ac(cx, refobj); |
|
4959 if (!JSObject::getProto(cx, refobj, &proto)) |
|
4960 return false; |
|
4961 } |
|
4962 RootedValue protov(cx, ObjectOrNullValue(proto)); |
|
4963 if (!dbg->wrapDebuggeeValue(cx, &protov)) |
|
4964 return false; |
|
4965 args.rval().set(protov); |
|
4966 return true; |
|
4967 } |
|
4968 |
|
4969 static bool |
|
4970 DebuggerObject_getClass(JSContext *cx, unsigned argc, Value *vp) |
|
4971 { |
|
4972 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get class", args, refobj); |
|
4973 const char *className; |
|
4974 { |
|
4975 AutoCompartment ac(cx, refobj); |
|
4976 className = JSObject::className(cx, refobj); |
|
4977 } |
|
4978 JSAtom *str = Atomize(cx, className, strlen(className)); |
|
4979 if (!str) |
|
4980 return false; |
|
4981 args.rval().setString(str); |
|
4982 return true; |
|
4983 } |
|
4984 |
|
4985 static bool |
|
4986 DebuggerObject_getCallable(JSContext *cx, unsigned argc, Value *vp) |
|
4987 { |
|
4988 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get callable", args, refobj); |
|
4989 args.rval().setBoolean(refobj->isCallable()); |
|
4990 return true; |
|
4991 } |
|
4992 |
|
4993 static bool |
|
4994 DebuggerObject_getName(JSContext *cx, unsigned argc, Value *vp) |
|
4995 { |
|
4996 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get name", args, dbg, obj); |
|
4997 if (!obj->is<JSFunction>()) { |
|
4998 args.rval().setUndefined(); |
|
4999 return true; |
|
5000 } |
|
5001 |
|
5002 JSString *name = obj->as<JSFunction>().atom(); |
|
5003 if (!name) { |
|
5004 args.rval().setUndefined(); |
|
5005 return true; |
|
5006 } |
|
5007 |
|
5008 RootedValue namev(cx, StringValue(name)); |
|
5009 if (!dbg->wrapDebuggeeValue(cx, &namev)) |
|
5010 return false; |
|
5011 args.rval().set(namev); |
|
5012 return true; |
|
5013 } |
|
5014 |
|
5015 static bool |
|
5016 DebuggerObject_getDisplayName(JSContext *cx, unsigned argc, Value *vp) |
|
5017 { |
|
5018 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get display name", args, dbg, obj); |
|
5019 if (!obj->is<JSFunction>()) { |
|
5020 args.rval().setUndefined(); |
|
5021 return true; |
|
5022 } |
|
5023 |
|
5024 JSString *name = obj->as<JSFunction>().displayAtom(); |
|
5025 if (!name) { |
|
5026 args.rval().setUndefined(); |
|
5027 return true; |
|
5028 } |
|
5029 |
|
5030 RootedValue namev(cx, StringValue(name)); |
|
5031 if (!dbg->wrapDebuggeeValue(cx, &namev)) |
|
5032 return false; |
|
5033 args.rval().set(namev); |
|
5034 return true; |
|
5035 } |
|
5036 |
|
5037 static bool |
|
5038 DebuggerObject_getParameterNames(JSContext *cx, unsigned argc, Value *vp) |
|
5039 { |
|
5040 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get parameterNames", args, dbg, obj); |
|
5041 if (!obj->is<JSFunction>()) { |
|
5042 args.rval().setUndefined(); |
|
5043 return true; |
|
5044 } |
|
5045 |
|
5046 RootedFunction fun(cx, &obj->as<JSFunction>()); |
|
5047 |
|
5048 /* Only hand out parameter info for debuggee functions. */ |
|
5049 if (!dbg->observesGlobal(&fun->global())) { |
|
5050 args.rval().setUndefined(); |
|
5051 return true; |
|
5052 } |
|
5053 |
|
5054 RootedObject result(cx, NewDenseAllocatedArray(cx, fun->nargs())); |
|
5055 if (!result) |
|
5056 return false; |
|
5057 result->ensureDenseInitializedLength(cx, 0, fun->nargs()); |
|
5058 |
|
5059 if (fun->isInterpreted()) { |
|
5060 RootedScript script(cx, GetOrCreateFunctionScript(cx, fun)); |
|
5061 if (!script) |
|
5062 return false; |
|
5063 |
|
5064 JS_ASSERT(fun->nargs() == script->bindings.numArgs()); |
|
5065 |
|
5066 if (fun->nargs() > 0) { |
|
5067 BindingVector bindings(cx); |
|
5068 if (!FillBindingVector(script, &bindings)) |
|
5069 return false; |
|
5070 for (size_t i = 0; i < fun->nargs(); i++) { |
|
5071 Value v; |
|
5072 if (bindings[i].name()->length() == 0) |
|
5073 v = UndefinedValue(); |
|
5074 else |
|
5075 v = StringValue(bindings[i].name()); |
|
5076 result->setDenseElement(i, v); |
|
5077 } |
|
5078 } |
|
5079 } else { |
|
5080 for (size_t i = 0; i < fun->nargs(); i++) |
|
5081 result->setDenseElement(i, UndefinedValue()); |
|
5082 } |
|
5083 |
|
5084 args.rval().setObject(*result); |
|
5085 return true; |
|
5086 } |
|
5087 |
|
5088 static bool |
|
5089 DebuggerObject_getScript(JSContext *cx, unsigned argc, Value *vp) |
|
5090 { |
|
5091 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get script", args, dbg, obj); |
|
5092 |
|
5093 if (!obj->is<JSFunction>()) { |
|
5094 args.rval().setUndefined(); |
|
5095 return true; |
|
5096 } |
|
5097 |
|
5098 RootedFunction fun(cx, &obj->as<JSFunction>()); |
|
5099 if (fun->isBuiltin()) { |
|
5100 args.rval().setUndefined(); |
|
5101 return true; |
|
5102 } |
|
5103 |
|
5104 RootedScript script(cx, GetOrCreateFunctionScript(cx, fun)); |
|
5105 if (!script) |
|
5106 return false; |
|
5107 |
|
5108 /* Only hand out debuggee scripts. */ |
|
5109 if (!dbg->observesScript(script)) { |
|
5110 args.rval().setNull(); |
|
5111 return true; |
|
5112 } |
|
5113 |
|
5114 RootedObject scriptObject(cx, dbg->wrapScript(cx, script)); |
|
5115 if (!scriptObject) |
|
5116 return false; |
|
5117 |
|
5118 args.rval().setObject(*scriptObject); |
|
5119 return true; |
|
5120 } |
|
5121 |
|
5122 static bool |
|
5123 DebuggerObject_getEnvironment(JSContext *cx, unsigned argc, Value *vp) |
|
5124 { |
|
5125 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get environment", args, dbg, obj); |
|
5126 |
|
5127 /* Don't bother switching compartments just to check obj's type and get its env. */ |
|
5128 if (!obj->is<JSFunction>() || !obj->as<JSFunction>().isInterpreted()) { |
|
5129 args.rval().setUndefined(); |
|
5130 return true; |
|
5131 } |
|
5132 |
|
5133 /* Only hand out environments of debuggee functions. */ |
|
5134 if (!dbg->observesGlobal(&obj->global())) { |
|
5135 args.rval().setNull(); |
|
5136 return true; |
|
5137 } |
|
5138 |
|
5139 Rooted<Env*> env(cx); |
|
5140 { |
|
5141 AutoCompartment ac(cx, obj); |
|
5142 RootedFunction fun(cx, &obj->as<JSFunction>()); |
|
5143 env = GetDebugScopeForFunction(cx, fun); |
|
5144 if (!env) |
|
5145 return false; |
|
5146 } |
|
5147 |
|
5148 return dbg->wrapEnvironment(cx, env, args.rval()); |
|
5149 } |
|
5150 |
|
5151 static bool |
|
5152 DebuggerObject_getGlobal(JSContext *cx, unsigned argc, Value *vp) |
|
5153 { |
|
5154 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get global", args, dbg, obj); |
|
5155 |
|
5156 RootedValue v(cx, ObjectValue(obj->global())); |
|
5157 if (!dbg->wrapDebuggeeValue(cx, &v)) |
|
5158 return false; |
|
5159 args.rval().set(v); |
|
5160 return true; |
|
5161 } |
|
5162 |
|
5163 static bool |
|
5164 DebuggerObject_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp) |
|
5165 { |
|
5166 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "getOwnPropertyDescriptor", args, dbg, obj); |
|
5167 |
|
5168 RootedId id(cx); |
|
5169 if (!ValueToId<CanGC>(cx, args.get(0), &id)) |
|
5170 return false; |
|
5171 |
|
5172 /* Bug: This can cause the debuggee to run! */ |
|
5173 Rooted<PropertyDescriptor> desc(cx); |
|
5174 { |
|
5175 Maybe<AutoCompartment> ac; |
|
5176 ac.construct(cx, obj); |
|
5177 if (!cx->compartment()->wrapId(cx, id.address())) |
|
5178 return false; |
|
5179 |
|
5180 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5181 if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) |
|
5182 return false; |
|
5183 } |
|
5184 |
|
5185 if (desc.object()) { |
|
5186 /* Rewrap the debuggee values in desc for the debugger. */ |
|
5187 if (!dbg->wrapDebuggeeValue(cx, desc.value())) |
|
5188 return false; |
|
5189 |
|
5190 if (desc.hasGetterObject()) { |
|
5191 RootedValue get(cx, ObjectOrNullValue(desc.getterObject())); |
|
5192 if (!dbg->wrapDebuggeeValue(cx, &get)) |
|
5193 return false; |
|
5194 desc.setGetterObject(get.toObjectOrNull()); |
|
5195 } |
|
5196 if (desc.hasSetterObject()) { |
|
5197 RootedValue set(cx, ObjectOrNullValue(desc.setterObject())); |
|
5198 if (!dbg->wrapDebuggeeValue(cx, &set)) |
|
5199 return false; |
|
5200 desc.setSetterObject(set.toObjectOrNull()); |
|
5201 } |
|
5202 } |
|
5203 |
|
5204 return NewPropertyDescriptorObject(cx, desc, args.rval()); |
|
5205 } |
|
5206 |
|
5207 static bool |
|
5208 DebuggerObject_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp) |
|
5209 { |
|
5210 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "getOwnPropertyNames", args, dbg, obj); |
|
5211 |
|
5212 AutoIdVector keys(cx); |
|
5213 { |
|
5214 Maybe<AutoCompartment> ac; |
|
5215 ac.construct(cx, obj); |
|
5216 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5217 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &keys)) |
|
5218 return false; |
|
5219 } |
|
5220 |
|
5221 AutoValueVector vals(cx); |
|
5222 if (!vals.resize(keys.length())) |
|
5223 return false; |
|
5224 |
|
5225 for (size_t i = 0, len = keys.length(); i < len; i++) { |
|
5226 jsid id = keys[i]; |
|
5227 if (JSID_IS_INT(id)) { |
|
5228 JSString *str = Int32ToString<CanGC>(cx, JSID_TO_INT(id)); |
|
5229 if (!str) |
|
5230 return false; |
|
5231 vals[i].setString(str); |
|
5232 } else if (JSID_IS_ATOM(id)) { |
|
5233 vals[i].setString(JSID_TO_STRING(id)); |
|
5234 if (!cx->compartment()->wrap(cx, vals.handleAt(i))) |
|
5235 return false; |
|
5236 } else { |
|
5237 vals[i].setObject(*JSID_TO_OBJECT(id)); |
|
5238 if (!dbg->wrapDebuggeeValue(cx, vals.handleAt(i))) |
|
5239 return false; |
|
5240 } |
|
5241 } |
|
5242 |
|
5243 JSObject *aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin()); |
|
5244 if (!aobj) |
|
5245 return false; |
|
5246 args.rval().setObject(*aobj); |
|
5247 return true; |
|
5248 } |
|
5249 |
|
5250 static bool |
|
5251 DebuggerObject_defineProperty(JSContext *cx, unsigned argc, Value *vp) |
|
5252 { |
|
5253 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperty", args, dbg, obj); |
|
5254 REQUIRE_ARGC("Debugger.Object.defineProperty", 2); |
|
5255 |
|
5256 RootedId id(cx); |
|
5257 if (!ValueToId<CanGC>(cx, args[0], &id)) |
|
5258 return false; |
|
5259 |
|
5260 AutoPropDescArrayRooter descs(cx); |
|
5261 if (!descs.reserve(3)) // desc, unwrappedDesc, rewrappedDesc |
|
5262 return false; |
|
5263 PropDesc *desc = descs.append(); |
|
5264 if (!desc || !desc->initialize(cx, args[1], false)) |
|
5265 return false; |
|
5266 desc->clearPd(); |
|
5267 |
|
5268 PropDesc *unwrappedDesc = descs.append(); |
|
5269 if (!unwrappedDesc || !desc->unwrapDebuggerObjectsInto(cx, dbg, obj, unwrappedDesc)) |
|
5270 return false; |
|
5271 if (!unwrappedDesc->checkGetter(cx) || !unwrappedDesc->checkSetter(cx)) |
|
5272 return false; |
|
5273 |
|
5274 { |
|
5275 PropDesc *rewrappedDesc = descs.append(); |
|
5276 if (!rewrappedDesc) |
|
5277 return false; |
|
5278 RootedId wrappedId(cx); |
|
5279 |
|
5280 Maybe<AutoCompartment> ac; |
|
5281 ac.construct(cx, obj); |
|
5282 if (!unwrappedDesc->wrapInto(cx, obj, id, wrappedId.address(), rewrappedDesc)) |
|
5283 return false; |
|
5284 |
|
5285 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5286 bool dummy; |
|
5287 if (!DefineProperty(cx, obj, wrappedId, *rewrappedDesc, true, &dummy)) |
|
5288 return false; |
|
5289 } |
|
5290 |
|
5291 args.rval().setUndefined(); |
|
5292 return true; |
|
5293 } |
|
5294 |
|
5295 static bool |
|
5296 DebuggerObject_defineProperties(JSContext *cx, unsigned argc, Value *vp) |
|
5297 { |
|
5298 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperties", args, dbg, obj); |
|
5299 REQUIRE_ARGC("Debugger.Object.defineProperties", 1); |
|
5300 |
|
5301 RootedValue arg(cx, args[0]); |
|
5302 RootedObject props(cx, ToObject(cx, arg)); |
|
5303 if (!props) |
|
5304 return false; |
|
5305 |
|
5306 AutoIdVector ids(cx); |
|
5307 AutoPropDescArrayRooter descs(cx); |
|
5308 if (!ReadPropertyDescriptors(cx, props, false, &ids, &descs)) |
|
5309 return false; |
|
5310 size_t n = ids.length(); |
|
5311 |
|
5312 AutoPropDescArrayRooter unwrappedDescs(cx); |
|
5313 for (size_t i = 0; i < n; i++) { |
|
5314 if (!unwrappedDescs.append()) |
|
5315 return false; |
|
5316 if (!descs[i].unwrapDebuggerObjectsInto(cx, dbg, obj, &unwrappedDescs[i])) |
|
5317 return false; |
|
5318 if (!unwrappedDescs[i].checkGetter(cx) || !unwrappedDescs[i].checkSetter(cx)) |
|
5319 return false; |
|
5320 } |
|
5321 |
|
5322 { |
|
5323 AutoIdVector rewrappedIds(cx); |
|
5324 AutoPropDescArrayRooter rewrappedDescs(cx); |
|
5325 |
|
5326 Maybe<AutoCompartment> ac; |
|
5327 ac.construct(cx, obj); |
|
5328 RootedId id(cx); |
|
5329 for (size_t i = 0; i < n; i++) { |
|
5330 if (!rewrappedIds.append(JSID_VOID) || !rewrappedDescs.append()) |
|
5331 return false; |
|
5332 id = ids[i]; |
|
5333 if (!unwrappedDescs[i].wrapInto(cx, obj, id, &rewrappedIds[i], &rewrappedDescs[i])) |
|
5334 return false; |
|
5335 } |
|
5336 |
|
5337 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5338 for (size_t i = 0; i < n; i++) { |
|
5339 bool dummy; |
|
5340 if (!DefineProperty(cx, obj, rewrappedIds.handleAt(i), |
|
5341 rewrappedDescs[i], true, &dummy)) |
|
5342 { |
|
5343 return false; |
|
5344 } |
|
5345 } |
|
5346 } |
|
5347 |
|
5348 args.rval().setUndefined(); |
|
5349 return true; |
|
5350 } |
|
5351 |
|
5352 /* |
|
5353 * This does a non-strict delete, as a matter of API design. The case where the |
|
5354 * property is non-configurable isn't necessarily exceptional here. |
|
5355 */ |
|
5356 static bool |
|
5357 DebuggerObject_deleteProperty(JSContext *cx, unsigned argc, Value *vp) |
|
5358 { |
|
5359 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "deleteProperty", args, dbg, obj); |
|
5360 RootedValue nameArg(cx, args.get(0)); |
|
5361 |
|
5362 Maybe<AutoCompartment> ac; |
|
5363 ac.construct(cx, obj); |
|
5364 if (!cx->compartment()->wrap(cx, &nameArg)) |
|
5365 return false; |
|
5366 |
|
5367 bool succeeded; |
|
5368 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5369 if (!JSObject::deleteByValue(cx, obj, nameArg, &succeeded)) |
|
5370 return false; |
|
5371 args.rval().setBoolean(succeeded); |
|
5372 return true; |
|
5373 } |
|
5374 |
|
5375 enum SealHelperOp { Seal, Freeze, PreventExtensions }; |
|
5376 |
|
5377 static bool |
|
5378 DebuggerObject_sealHelper(JSContext *cx, unsigned argc, Value *vp, SealHelperOp op, const char *name) |
|
5379 { |
|
5380 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, name, args, dbg, obj); |
|
5381 |
|
5382 Maybe<AutoCompartment> ac; |
|
5383 ac.construct(cx, obj); |
|
5384 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5385 bool ok; |
|
5386 if (op == Seal) { |
|
5387 ok = JSObject::seal(cx, obj); |
|
5388 } else if (op == Freeze) { |
|
5389 ok = JSObject::freeze(cx, obj); |
|
5390 } else { |
|
5391 JS_ASSERT(op == PreventExtensions); |
|
5392 bool extensible; |
|
5393 if (!JSObject::isExtensible(cx, obj, &extensible)) |
|
5394 return false; |
|
5395 if (!extensible) { |
|
5396 args.rval().setUndefined(); |
|
5397 return true; |
|
5398 } |
|
5399 ok = JSObject::preventExtensions(cx, obj); |
|
5400 } |
|
5401 if (!ok) |
|
5402 return false; |
|
5403 args.rval().setUndefined(); |
|
5404 return true; |
|
5405 } |
|
5406 |
|
5407 static bool |
|
5408 DebuggerObject_seal(JSContext *cx, unsigned argc, Value *vp) |
|
5409 { |
|
5410 return DebuggerObject_sealHelper(cx, argc, vp, Seal, "seal"); |
|
5411 } |
|
5412 |
|
5413 static bool |
|
5414 DebuggerObject_freeze(JSContext *cx, unsigned argc, Value *vp) |
|
5415 { |
|
5416 return DebuggerObject_sealHelper(cx, argc, vp, Freeze, "freeze"); |
|
5417 } |
|
5418 |
|
5419 static bool |
|
5420 DebuggerObject_preventExtensions(JSContext *cx, unsigned argc, Value *vp) |
|
5421 { |
|
5422 return DebuggerObject_sealHelper(cx, argc, vp, PreventExtensions, "preventExtensions"); |
|
5423 } |
|
5424 |
|
5425 static bool |
|
5426 DebuggerObject_isSealedHelper(JSContext *cx, unsigned argc, Value *vp, SealHelperOp op, |
|
5427 const char *name) |
|
5428 { |
|
5429 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, name, args, dbg, obj); |
|
5430 |
|
5431 Maybe<AutoCompartment> ac; |
|
5432 ac.construct(cx, obj); |
|
5433 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5434 bool r; |
|
5435 if (op == Seal) { |
|
5436 if (!JSObject::isSealed(cx, obj, &r)) |
|
5437 return false; |
|
5438 } else if (op == Freeze) { |
|
5439 if (!JSObject::isFrozen(cx, obj, &r)) |
|
5440 return false; |
|
5441 } else { |
|
5442 if (!JSObject::isExtensible(cx, obj, &r)) |
|
5443 return false; |
|
5444 } |
|
5445 args.rval().setBoolean(r); |
|
5446 return true; |
|
5447 } |
|
5448 |
|
5449 static bool |
|
5450 DebuggerObject_isSealed(JSContext *cx, unsigned argc, Value *vp) |
|
5451 { |
|
5452 return DebuggerObject_isSealedHelper(cx, argc, vp, Seal, "isSealed"); |
|
5453 } |
|
5454 |
|
5455 static bool |
|
5456 DebuggerObject_isFrozen(JSContext *cx, unsigned argc, Value *vp) |
|
5457 { |
|
5458 return DebuggerObject_isSealedHelper(cx, argc, vp, Freeze, "isFrozen"); |
|
5459 } |
|
5460 |
|
5461 static bool |
|
5462 DebuggerObject_isExtensible(JSContext *cx, unsigned argc, Value *vp) |
|
5463 { |
|
5464 return DebuggerObject_isSealedHelper(cx, argc, vp, PreventExtensions, "isExtensible"); |
|
5465 } |
|
5466 |
|
5467 enum ApplyOrCallMode { ApplyMode, CallMode }; |
|
5468 |
|
5469 static bool |
|
5470 ApplyOrCall(JSContext *cx, unsigned argc, Value *vp, ApplyOrCallMode mode) |
|
5471 { |
|
5472 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "apply", args, dbg, obj); |
|
5473 |
|
5474 /* |
|
5475 * Any JS exceptions thrown must be in the debugger compartment, so do |
|
5476 * sanity checks and fallible conversions before entering the debuggee. |
|
5477 */ |
|
5478 RootedValue calleev(cx, ObjectValue(*obj)); |
|
5479 if (!obj->isCallable()) { |
|
5480 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
5481 "Debugger.Object", "apply", obj->getClass()->name); |
|
5482 return false; |
|
5483 } |
|
5484 |
|
5485 /* |
|
5486 * Unwrap Debugger.Objects. This happens in the debugger's compartment since |
|
5487 * that is where any exceptions must be reported. |
|
5488 */ |
|
5489 RootedValue thisv(cx, args.get(0)); |
|
5490 if (!dbg->unwrapDebuggeeValue(cx, &thisv)) |
|
5491 return false; |
|
5492 unsigned callArgc = 0; |
|
5493 Value *callArgv = nullptr; |
|
5494 AutoValueVector argv(cx); |
|
5495 if (mode == ApplyMode) { |
|
5496 if (args.length() >= 2 && !args[1].isNullOrUndefined()) { |
|
5497 if (!args[1].isObject()) { |
|
5498 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_APPLY_ARGS, |
|
5499 js_apply_str); |
|
5500 return false; |
|
5501 } |
|
5502 RootedObject argsobj(cx, &args[1].toObject()); |
|
5503 if (!GetLengthProperty(cx, argsobj, &callArgc)) |
|
5504 return false; |
|
5505 callArgc = unsigned(Min(callArgc, ARGS_LENGTH_MAX)); |
|
5506 if (!argv.growBy(callArgc) || !GetElements(cx, argsobj, callArgc, argv.begin())) |
|
5507 return false; |
|
5508 callArgv = argv.begin(); |
|
5509 } |
|
5510 } else { |
|
5511 callArgc = args.length() > 0 ? unsigned(Min(args.length() - 1, ARGS_LENGTH_MAX)) : 0; |
|
5512 callArgv = args.array() + 1; |
|
5513 } |
|
5514 |
|
5515 AutoArrayRooter callArgvRooter(cx, callArgc, callArgv); |
|
5516 for (unsigned i = 0; i < callArgc; i++) { |
|
5517 if (!dbg->unwrapDebuggeeValue(cx, callArgvRooter.handleAt(i))) |
|
5518 return false; |
|
5519 } |
|
5520 |
|
5521 /* |
|
5522 * Enter the debuggee compartment and rewrap all input value for that compartment. |
|
5523 * (Rewrapping always takes place in the destination compartment.) |
|
5524 */ |
|
5525 Maybe<AutoCompartment> ac; |
|
5526 ac.construct(cx, obj); |
|
5527 if (!cx->compartment()->wrap(cx, &calleev) || !cx->compartment()->wrap(cx, &thisv)) |
|
5528 return false; |
|
5529 |
|
5530 RootedValue arg(cx); |
|
5531 for (unsigned i = 0; i < callArgc; i++) { |
|
5532 if (!cx->compartment()->wrap(cx, callArgvRooter.handleAt(i))) |
|
5533 return false; |
|
5534 } |
|
5535 |
|
5536 /* |
|
5537 * Call the function. Use receiveCompletionValue to return to the debugger |
|
5538 * compartment and populate args.rval(). |
|
5539 */ |
|
5540 RootedValue rval(cx); |
|
5541 bool ok = Invoke(cx, thisv, calleev, callArgc, callArgv, &rval); |
|
5542 return dbg->receiveCompletionValue(ac, ok, rval, args.rval()); |
|
5543 } |
|
5544 |
|
5545 static bool |
|
5546 DebuggerObject_apply(JSContext *cx, unsigned argc, Value *vp) |
|
5547 { |
|
5548 return ApplyOrCall(cx, argc, vp, ApplyMode); |
|
5549 } |
|
5550 |
|
5551 static bool |
|
5552 DebuggerObject_call(JSContext *cx, unsigned argc, Value *vp) |
|
5553 { |
|
5554 return ApplyOrCall(cx, argc, vp, CallMode); |
|
5555 } |
|
5556 |
|
5557 static bool |
|
5558 DebuggerObject_makeDebuggeeValue(JSContext *cx, unsigned argc, Value *vp) |
|
5559 { |
|
5560 REQUIRE_ARGC("Debugger.Object.prototype.makeDebuggeeValue", 1); |
|
5561 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "makeDebuggeeValue", args, dbg, referent); |
|
5562 |
|
5563 RootedValue arg0(cx, args[0]); |
|
5564 |
|
5565 /* Non-objects are already debuggee values. */ |
|
5566 if (arg0.isObject()) { |
|
5567 // Enter this Debugger.Object's referent's compartment, and wrap the |
|
5568 // argument as appropriate for references from there. |
|
5569 { |
|
5570 AutoCompartment ac(cx, referent); |
|
5571 if (!cx->compartment()->wrap(cx, &arg0)) |
|
5572 return false; |
|
5573 } |
|
5574 |
|
5575 // Back in the debugger's compartment, produce a new Debugger.Object |
|
5576 // instance referring to the wrapped argument. |
|
5577 if (!dbg->wrapDebuggeeValue(cx, &arg0)) |
|
5578 return false; |
|
5579 } |
|
5580 |
|
5581 args.rval().set(arg0); |
|
5582 return true; |
|
5583 } |
|
5584 |
|
5585 static bool |
|
5586 RequireGlobalObject(JSContext *cx, HandleValue dbgobj, HandleObject referent) |
|
5587 { |
|
5588 RootedObject obj(cx, referent); |
|
5589 |
|
5590 if (!obj->is<GlobalObject>()) { |
|
5591 const char *isWrapper = ""; |
|
5592 const char *isWindowProxy = ""; |
|
5593 |
|
5594 /* Help the poor programmer by pointing out wrappers around globals... */ |
|
5595 if (obj->is<WrapperObject>()) { |
|
5596 obj = js::UncheckedUnwrap(obj); |
|
5597 isWrapper = "a wrapper around "; |
|
5598 } |
|
5599 |
|
5600 /* ... and WindowProxies around Windows. */ |
|
5601 if (IsOuterObject(obj)) { |
|
5602 obj = JS_ObjectToInnerObject(cx, obj); |
|
5603 isWindowProxy = "a WindowProxy referring to "; |
|
5604 } |
|
5605 |
|
5606 if (obj->is<GlobalObject>()) { |
|
5607 js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_WRAPPER_IN_WAY, |
|
5608 JSDVG_SEARCH_STACK, dbgobj, js::NullPtr(), |
|
5609 isWrapper, isWindowProxy); |
|
5610 } else { |
|
5611 js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_BAD_REFERENT, |
|
5612 JSDVG_SEARCH_STACK, dbgobj, js::NullPtr(), |
|
5613 "a global object", nullptr); |
|
5614 } |
|
5615 return false; |
|
5616 } |
|
5617 |
|
5618 return true; |
|
5619 } |
|
5620 |
|
5621 static bool |
|
5622 DebuggerObject_evalInGlobal(JSContext *cx, unsigned argc, Value *vp) |
|
5623 { |
|
5624 REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobal", 1); |
|
5625 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobal", args, dbg, referent); |
|
5626 if (!RequireGlobalObject(cx, args.thisv(), referent)) |
|
5627 return false; |
|
5628 |
|
5629 return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobal", |
|
5630 args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue, |
|
5631 args.get(1), args.rval(), dbg, referent, nullptr); |
|
5632 } |
|
5633 |
|
5634 static bool |
|
5635 DebuggerObject_evalInGlobalWithBindings(JSContext *cx, unsigned argc, Value *vp) |
|
5636 { |
|
5637 REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobalWithBindings", 2); |
|
5638 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobalWithBindings", args, dbg, referent); |
|
5639 if (!RequireGlobalObject(cx, args.thisv(), referent)) |
|
5640 return false; |
|
5641 |
|
5642 return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobalWithBindings", |
|
5643 args[0], EvalHasExtraBindings, args[1], args.get(2), |
|
5644 args.rval(), dbg, referent, nullptr); |
|
5645 } |
|
5646 |
|
5647 static bool |
|
5648 DebuggerObject_unwrap(JSContext *cx, unsigned argc, Value *vp) |
|
5649 { |
|
5650 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "unwrap", args, dbg, referent); |
|
5651 JSObject *unwrapped = UnwrapOneChecked(referent); |
|
5652 if (!unwrapped) { |
|
5653 args.rval().setNull(); |
|
5654 return true; |
|
5655 } |
|
5656 |
|
5657 args.rval().setObject(*unwrapped); |
|
5658 if (!dbg->wrapDebuggeeValue(cx, args.rval())) |
|
5659 return false; |
|
5660 return true; |
|
5661 } |
|
5662 |
|
5663 static bool |
|
5664 DebuggerObject_unsafeDereference(JSContext *cx, unsigned argc, Value *vp) |
|
5665 { |
|
5666 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "unsafeDereference", args, referent); |
|
5667 args.rval().setObject(*referent); |
|
5668 if (!cx->compartment()->wrap(cx, args.rval())) |
|
5669 return false; |
|
5670 |
|
5671 // Wrapping should outerize inner objects. |
|
5672 JS_ASSERT(!IsInnerObject(&args.rval().toObject())); |
|
5673 |
|
5674 return true; |
|
5675 } |
|
5676 |
|
5677 static const JSPropertySpec DebuggerObject_properties[] = { |
|
5678 JS_PSG("proto", DebuggerObject_getProto, 0), |
|
5679 JS_PSG("class", DebuggerObject_getClass, 0), |
|
5680 JS_PSG("callable", DebuggerObject_getCallable, 0), |
|
5681 JS_PSG("name", DebuggerObject_getName, 0), |
|
5682 JS_PSG("displayName", DebuggerObject_getDisplayName, 0), |
|
5683 JS_PSG("parameterNames", DebuggerObject_getParameterNames, 0), |
|
5684 JS_PSG("script", DebuggerObject_getScript, 0), |
|
5685 JS_PSG("environment", DebuggerObject_getEnvironment, 0), |
|
5686 JS_PSG("global", DebuggerObject_getGlobal, 0), |
|
5687 JS_PS_END |
|
5688 }; |
|
5689 |
|
5690 static const JSFunctionSpec DebuggerObject_methods[] = { |
|
5691 JS_FN("getOwnPropertyDescriptor", DebuggerObject_getOwnPropertyDescriptor, 1, 0), |
|
5692 JS_FN("getOwnPropertyNames", DebuggerObject_getOwnPropertyNames, 0, 0), |
|
5693 JS_FN("defineProperty", DebuggerObject_defineProperty, 2, 0), |
|
5694 JS_FN("defineProperties", DebuggerObject_defineProperties, 1, 0), |
|
5695 JS_FN("deleteProperty", DebuggerObject_deleteProperty, 1, 0), |
|
5696 JS_FN("seal", DebuggerObject_seal, 0, 0), |
|
5697 JS_FN("freeze", DebuggerObject_freeze, 0, 0), |
|
5698 JS_FN("preventExtensions", DebuggerObject_preventExtensions, 0, 0), |
|
5699 JS_FN("isSealed", DebuggerObject_isSealed, 0, 0), |
|
5700 JS_FN("isFrozen", DebuggerObject_isFrozen, 0, 0), |
|
5701 JS_FN("isExtensible", DebuggerObject_isExtensible, 0, 0), |
|
5702 JS_FN("apply", DebuggerObject_apply, 0, 0), |
|
5703 JS_FN("call", DebuggerObject_call, 0, 0), |
|
5704 JS_FN("makeDebuggeeValue", DebuggerObject_makeDebuggeeValue, 1, 0), |
|
5705 JS_FN("evalInGlobal", DebuggerObject_evalInGlobal, 1, 0), |
|
5706 JS_FN("evalInGlobalWithBindings", DebuggerObject_evalInGlobalWithBindings, 2, 0), |
|
5707 JS_FN("unwrap", DebuggerObject_unwrap, 0, 0), |
|
5708 JS_FN("unsafeDereference", DebuggerObject_unsafeDereference, 0, 0), |
|
5709 JS_FS_END |
|
5710 }; |
|
5711 |
|
5712 |
|
5713 /*** Debugger.Environment ************************************************************************/ |
|
5714 |
|
5715 static void |
|
5716 DebuggerEnv_trace(JSTracer *trc, JSObject *obj) |
|
5717 { |
|
5718 /* |
|
5719 * There is a barrier on private pointers, so the Unbarriered marking |
|
5720 * is okay. |
|
5721 */ |
|
5722 if (Env *referent = (JSObject *) obj->getPrivate()) { |
|
5723 MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Environment referent"); |
|
5724 obj->setPrivateUnbarriered(referent); |
|
5725 } |
|
5726 } |
|
5727 |
|
5728 const Class DebuggerEnv_class = { |
|
5729 "Environment", |
|
5730 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | |
|
5731 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGENV_COUNT), |
|
5732 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
|
5733 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, |
|
5734 nullptr, /* call */ |
|
5735 nullptr, /* hasInstance */ |
|
5736 nullptr, /* construct */ |
|
5737 DebuggerEnv_trace |
|
5738 }; |
|
5739 |
|
5740 static JSObject * |
|
5741 DebuggerEnv_checkThis(JSContext *cx, const CallArgs &args, const char *fnname, |
|
5742 bool requireDebuggee = true) |
|
5743 { |
|
5744 if (!args.thisv().isObject()) { |
|
5745 ReportObjectRequired(cx); |
|
5746 return nullptr; |
|
5747 } |
|
5748 JSObject *thisobj = &args.thisv().toObject(); |
|
5749 if (thisobj->getClass() != &DebuggerEnv_class) { |
|
5750 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
5751 "Debugger.Environment", fnname, thisobj->getClass()->name); |
|
5752 return nullptr; |
|
5753 } |
|
5754 |
|
5755 /* |
|
5756 * Forbid Debugger.Environment.prototype, which is of class DebuggerEnv_class |
|
5757 * but isn't a real working Debugger.Environment. The prototype object is |
|
5758 * distinguished by having no referent. |
|
5759 */ |
|
5760 if (!thisobj->getPrivate()) { |
|
5761 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
|
5762 "Debugger.Environment", fnname, "prototype object"); |
|
5763 return nullptr; |
|
5764 } |
|
5765 |
|
5766 /* |
|
5767 * Forbid access to Debugger.Environment objects that are not debuggee |
|
5768 * environments. |
|
5769 */ |
|
5770 if (requireDebuggee) { |
|
5771 Rooted<Env*> env(cx, static_cast<Env *>(thisobj->getPrivate())); |
|
5772 if (!Debugger::fromChildJSObject(thisobj)->observesGlobal(&env->global())) { |
|
5773 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGEE, |
|
5774 "Debugger.Environment", "environment"); |
|
5775 return nullptr; |
|
5776 } |
|
5777 } |
|
5778 |
|
5779 return thisobj; |
|
5780 } |
|
5781 |
|
5782 #define THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env) \ |
|
5783 CallArgs args = CallArgsFromVp(argc, vp); \ |
|
5784 JSObject *envobj = DebuggerEnv_checkThis(cx, args, fnname); \ |
|
5785 if (!envobj) \ |
|
5786 return false; \ |
|
5787 Rooted<Env*> env(cx, static_cast<Env *>(envobj->getPrivate())); \ |
|
5788 JS_ASSERT(env); \ |
|
5789 JS_ASSERT(!env->is<ScopeObject>()) |
|
5790 |
|
5791 #define THIS_DEBUGENV_OWNER(cx, argc, vp, fnname, args, envobj, env, dbg) \ |
|
5792 THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env); \ |
|
5793 Debugger *dbg = Debugger::fromChildJSObject(envobj) |
|
5794 |
|
5795 static bool |
|
5796 DebuggerEnv_construct(JSContext *cx, unsigned argc, Value *vp) |
|
5797 { |
|
5798 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, |
|
5799 "Debugger.Environment"); |
|
5800 return false; |
|
5801 } |
|
5802 |
|
5803 static bool |
|
5804 IsDeclarative(Env *env) |
|
5805 { |
|
5806 return env->is<DebugScopeObject>() && env->as<DebugScopeObject>().isForDeclarative(); |
|
5807 } |
|
5808 |
|
5809 static bool |
|
5810 IsWith(Env *env) |
|
5811 { |
|
5812 return env->is<DebugScopeObject>() && |
|
5813 env->as<DebugScopeObject>().scope().is<DynamicWithObject>(); |
|
5814 } |
|
5815 |
|
5816 static bool |
|
5817 DebuggerEnv_getType(JSContext *cx, unsigned argc, Value *vp) |
|
5818 { |
|
5819 THIS_DEBUGENV(cx, argc, vp, "get type", args, envobj, env); |
|
5820 |
|
5821 /* Don't bother switching compartments just to check env's class. */ |
|
5822 const char *s; |
|
5823 if (IsDeclarative(env)) |
|
5824 s = "declarative"; |
|
5825 else if (IsWith(env)) |
|
5826 s = "with"; |
|
5827 else |
|
5828 s = "object"; |
|
5829 |
|
5830 JSAtom *str = Atomize(cx, s, strlen(s), InternAtom); |
|
5831 if (!str) |
|
5832 return false; |
|
5833 args.rval().setString(str); |
|
5834 return true; |
|
5835 } |
|
5836 |
|
5837 static bool |
|
5838 DebuggerEnv_getParent(JSContext *cx, unsigned argc, Value *vp) |
|
5839 { |
|
5840 THIS_DEBUGENV_OWNER(cx, argc, vp, "get parent", args, envobj, env, dbg); |
|
5841 |
|
5842 /* Don't bother switching compartments just to get env's parent. */ |
|
5843 Rooted<Env*> parent(cx, env->enclosingScope()); |
|
5844 return dbg->wrapEnvironment(cx, parent, args.rval()); |
|
5845 } |
|
5846 |
|
5847 static bool |
|
5848 DebuggerEnv_getObject(JSContext *cx, unsigned argc, Value *vp) |
|
5849 { |
|
5850 THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg); |
|
5851 |
|
5852 /* |
|
5853 * Don't bother switching compartments just to check env's class and |
|
5854 * possibly get its proto. |
|
5855 */ |
|
5856 if (IsDeclarative(env)) { |
|
5857 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NO_SCOPE_OBJECT); |
|
5858 return false; |
|
5859 } |
|
5860 |
|
5861 JSObject *obj; |
|
5862 if (IsWith(env)) { |
|
5863 obj = &env->as<DebugScopeObject>().scope().as<DynamicWithObject>().object(); |
|
5864 } else { |
|
5865 obj = env; |
|
5866 JS_ASSERT(!obj->is<DebugScopeObject>()); |
|
5867 } |
|
5868 |
|
5869 args.rval().setObject(*obj); |
|
5870 if (!dbg->wrapDebuggeeValue(cx, args.rval())) |
|
5871 return false; |
|
5872 return true; |
|
5873 } |
|
5874 |
|
5875 static bool |
|
5876 DebuggerEnv_getCallee(JSContext *cx, unsigned argc, Value *vp) |
|
5877 { |
|
5878 THIS_DEBUGENV_OWNER(cx, argc, vp, "get callee", args, envobj, env, dbg); |
|
5879 |
|
5880 args.rval().setNull(); |
|
5881 |
|
5882 if (!env->is<DebugScopeObject>()) |
|
5883 return true; |
|
5884 |
|
5885 JSObject &scope = env->as<DebugScopeObject>().scope(); |
|
5886 if (!scope.is<CallObject>()) |
|
5887 return true; |
|
5888 |
|
5889 CallObject &callobj = scope.as<CallObject>(); |
|
5890 if (callobj.isForEval()) |
|
5891 return true; |
|
5892 |
|
5893 args.rval().setObject(callobj.callee()); |
|
5894 if (!dbg->wrapDebuggeeValue(cx, args.rval())) |
|
5895 return false; |
|
5896 return true; |
|
5897 } |
|
5898 |
|
5899 static bool |
|
5900 DebuggerEnv_getInspectable(JSContext *cx, unsigned argc, Value *vp) |
|
5901 { |
|
5902 CallArgs args = CallArgsFromVp(argc, vp); |
|
5903 JSObject *envobj = DebuggerEnv_checkThis(cx, args, "get inspectable", false); |
|
5904 if (!envobj) |
|
5905 return false; |
|
5906 Rooted<Env*> env(cx, static_cast<Env *>(envobj->getPrivate())); |
|
5907 JS_ASSERT(env); |
|
5908 JS_ASSERT(!env->is<ScopeObject>()); |
|
5909 |
|
5910 Debugger *dbg = Debugger::fromChildJSObject(envobj); |
|
5911 |
|
5912 args.rval().setBoolean(dbg->observesGlobal(&env->global())); |
|
5913 return true; |
|
5914 } |
|
5915 |
|
5916 static bool |
|
5917 DebuggerEnv_names(JSContext *cx, unsigned argc, Value *vp) |
|
5918 { |
|
5919 THIS_DEBUGENV_OWNER(cx, argc, vp, "names", args, envobj, env, dbg); |
|
5920 |
|
5921 AutoIdVector keys(cx); |
|
5922 { |
|
5923 Maybe<AutoCompartment> ac; |
|
5924 ac.construct(cx, env); |
|
5925 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5926 if (!GetPropertyNames(cx, env, JSITER_HIDDEN, &keys)) |
|
5927 return false; |
|
5928 } |
|
5929 |
|
5930 RootedObject arr(cx, NewDenseEmptyArray(cx)); |
|
5931 if (!arr) |
|
5932 return false; |
|
5933 RootedId id(cx); |
|
5934 for (size_t i = 0, len = keys.length(); i < len; i++) { |
|
5935 id = keys[i]; |
|
5936 if (JSID_IS_ATOM(id) && IsIdentifier(JSID_TO_ATOM(id))) { |
|
5937 if (!cx->compartment()->wrapId(cx, id.address())) |
|
5938 return false; |
|
5939 if (!NewbornArrayPush(cx, arr, StringValue(JSID_TO_STRING(id)))) |
|
5940 return false; |
|
5941 } |
|
5942 } |
|
5943 args.rval().setObject(*arr); |
|
5944 return true; |
|
5945 } |
|
5946 |
|
5947 static bool |
|
5948 DebuggerEnv_find(JSContext *cx, unsigned argc, Value *vp) |
|
5949 { |
|
5950 REQUIRE_ARGC("Debugger.Environment.find", 1); |
|
5951 THIS_DEBUGENV_OWNER(cx, argc, vp, "find", args, envobj, env, dbg); |
|
5952 |
|
5953 RootedId id(cx); |
|
5954 if (!ValueToIdentifier(cx, args[0], &id)) |
|
5955 return false; |
|
5956 |
|
5957 { |
|
5958 Maybe<AutoCompartment> ac; |
|
5959 ac.construct(cx, env); |
|
5960 if (!cx->compartment()->wrapId(cx, id.address())) |
|
5961 return false; |
|
5962 |
|
5963 /* This can trigger resolve hooks. */ |
|
5964 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5965 RootedShape prop(cx); |
|
5966 RootedObject pobj(cx); |
|
5967 for (; env && !prop; env = env->enclosingScope()) { |
|
5968 if (!JSObject::lookupGeneric(cx, env, id, &pobj, &prop)) |
|
5969 return false; |
|
5970 if (prop) |
|
5971 break; |
|
5972 } |
|
5973 } |
|
5974 |
|
5975 return dbg->wrapEnvironment(cx, env, args.rval()); |
|
5976 } |
|
5977 |
|
5978 static bool |
|
5979 DebuggerEnv_getVariable(JSContext *cx, unsigned argc, Value *vp) |
|
5980 { |
|
5981 REQUIRE_ARGC("Debugger.Environment.getVariable", 1); |
|
5982 THIS_DEBUGENV_OWNER(cx, argc, vp, "getVariable", args, envobj, env, dbg); |
|
5983 |
|
5984 RootedId id(cx); |
|
5985 if (!ValueToIdentifier(cx, args[0], &id)) |
|
5986 return false; |
|
5987 |
|
5988 RootedValue v(cx); |
|
5989 { |
|
5990 Maybe<AutoCompartment> ac; |
|
5991 ac.construct(cx, env); |
|
5992 if (!cx->compartment()->wrapId(cx, id.address())) |
|
5993 return false; |
|
5994 |
|
5995 /* This can trigger getters. */ |
|
5996 ErrorCopier ec(ac, dbg->toJSObject()); |
|
5997 |
|
5998 // For DebugScopeObjects, we get sentinel values for optimized out |
|
5999 // slots and arguments instead of throwing (the default behavior). |
|
6000 // |
|
6001 // See wrapDebuggeeValue for how the sentinel values are wrapped. |
|
6002 if (env->is<DebugScopeObject>()) { |
|
6003 if (!env->as<DebugScopeObject>().getMaybeSentinelValue(cx, id, &v)) |
|
6004 return false; |
|
6005 } else { |
|
6006 if (!JSObject::getGeneric(cx, env, env, id, &v)) |
|
6007 return false; |
|
6008 } |
|
6009 } |
|
6010 |
|
6011 if (!dbg->wrapDebuggeeValue(cx, &v)) |
|
6012 return false; |
|
6013 args.rval().set(v); |
|
6014 return true; |
|
6015 } |
|
6016 |
|
6017 static bool |
|
6018 DebuggerEnv_setVariable(JSContext *cx, unsigned argc, Value *vp) |
|
6019 { |
|
6020 REQUIRE_ARGC("Debugger.Environment.setVariable", 2); |
|
6021 THIS_DEBUGENV_OWNER(cx, argc, vp, "setVariable", args, envobj, env, dbg); |
|
6022 |
|
6023 RootedId id(cx); |
|
6024 if (!ValueToIdentifier(cx, args[0], &id)) |
|
6025 return false; |
|
6026 |
|
6027 RootedValue v(cx, args[1]); |
|
6028 if (!dbg->unwrapDebuggeeValue(cx, &v)) |
|
6029 return false; |
|
6030 |
|
6031 { |
|
6032 Maybe<AutoCompartment> ac; |
|
6033 ac.construct(cx, env); |
|
6034 if (!cx->compartment()->wrapId(cx, id.address()) || !cx->compartment()->wrap(cx, &v)) |
|
6035 return false; |
|
6036 |
|
6037 /* This can trigger setters. */ |
|
6038 ErrorCopier ec(ac, dbg->toJSObject()); |
|
6039 |
|
6040 /* Make sure the environment actually has the specified binding. */ |
|
6041 bool has; |
|
6042 if (!JSObject::hasProperty(cx, env, id, &has)) |
|
6043 return false; |
|
6044 if (!has) { |
|
6045 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_VARIABLE_NOT_FOUND); |
|
6046 return false; |
|
6047 } |
|
6048 |
|
6049 /* Just set the property. */ |
|
6050 if (!JSObject::setGeneric(cx, env, env, id, &v, true)) |
|
6051 return false; |
|
6052 } |
|
6053 |
|
6054 args.rval().setUndefined(); |
|
6055 return true; |
|
6056 } |
|
6057 |
|
6058 static const JSPropertySpec DebuggerEnv_properties[] = { |
|
6059 JS_PSG("type", DebuggerEnv_getType, 0), |
|
6060 JS_PSG("object", DebuggerEnv_getObject, 0), |
|
6061 JS_PSG("parent", DebuggerEnv_getParent, 0), |
|
6062 JS_PSG("callee", DebuggerEnv_getCallee, 0), |
|
6063 JS_PSG("inspectable", DebuggerEnv_getInspectable, 0), |
|
6064 JS_PS_END |
|
6065 }; |
|
6066 |
|
6067 static const JSFunctionSpec DebuggerEnv_methods[] = { |
|
6068 JS_FN("names", DebuggerEnv_names, 0, 0), |
|
6069 JS_FN("find", DebuggerEnv_find, 1, 0), |
|
6070 JS_FN("getVariable", DebuggerEnv_getVariable, 1, 0), |
|
6071 JS_FN("setVariable", DebuggerEnv_setVariable, 2, 0), |
|
6072 JS_FS_END |
|
6073 }; |
|
6074 |
|
6075 |
|
6076 |
|
6077 /*** Glue ****************************************************************************************/ |
|
6078 |
|
6079 extern JS_PUBLIC_API(bool) |
|
6080 JS_DefineDebuggerObject(JSContext *cx, HandleObject obj) |
|
6081 { |
|
6082 RootedObject |
|
6083 objProto(cx), |
|
6084 debugCtor(cx), |
|
6085 debugProto(cx), |
|
6086 frameProto(cx), |
|
6087 scriptProto(cx), |
|
6088 sourceProto(cx), |
|
6089 objectProto(cx), |
|
6090 envProto(cx), |
|
6091 memoryProto(cx); |
|
6092 objProto = obj->as<GlobalObject>().getOrCreateObjectPrototype(cx); |
|
6093 if (!objProto) |
|
6094 return false; |
|
6095 debugProto = js_InitClass(cx, obj, |
|
6096 objProto, &Debugger::jsclass, Debugger::construct, |
|
6097 1, Debugger::properties, Debugger::methods, nullptr, nullptr, |
|
6098 debugCtor.address()); |
|
6099 if (!debugProto) |
|
6100 return false; |
|
6101 |
|
6102 frameProto = js_InitClass(cx, debugCtor, objProto, &DebuggerFrame_class, |
|
6103 DebuggerFrame_construct, 0, |
|
6104 DebuggerFrame_properties, DebuggerFrame_methods, |
|
6105 nullptr, nullptr); |
|
6106 if (!frameProto) |
|
6107 return false; |
|
6108 |
|
6109 scriptProto = js_InitClass(cx, debugCtor, objProto, &DebuggerScript_class, |
|
6110 DebuggerScript_construct, 0, |
|
6111 DebuggerScript_properties, DebuggerScript_methods, |
|
6112 nullptr, nullptr); |
|
6113 if (!scriptProto) |
|
6114 return false; |
|
6115 |
|
6116 sourceProto = js_InitClass(cx, debugCtor, sourceProto, &DebuggerSource_class, |
|
6117 DebuggerSource_construct, 0, |
|
6118 DebuggerSource_properties, DebuggerSource_methods, |
|
6119 nullptr, nullptr); |
|
6120 if (!sourceProto) |
|
6121 return false; |
|
6122 |
|
6123 objectProto = js_InitClass(cx, debugCtor, objProto, &DebuggerObject_class, |
|
6124 DebuggerObject_construct, 0, |
|
6125 DebuggerObject_properties, DebuggerObject_methods, |
|
6126 nullptr, nullptr); |
|
6127 if (!objectProto) |
|
6128 return false; |
|
6129 envProto = js_InitClass(cx, debugCtor, objProto, &DebuggerEnv_class, |
|
6130 DebuggerEnv_construct, 0, |
|
6131 DebuggerEnv_properties, DebuggerEnv_methods, |
|
6132 nullptr, nullptr); |
|
6133 if (!envProto) |
|
6134 return false; |
|
6135 memoryProto = js_InitClass(cx, debugCtor, objProto, &DebuggerMemory::class_, |
|
6136 DebuggerMemory::construct, 0, DebuggerMemory::properties, |
|
6137 DebuggerMemory::methods, nullptr, nullptr); |
|
6138 if (!memoryProto) |
|
6139 return false; |
|
6140 |
|
6141 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto)); |
|
6142 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_OBJECT_PROTO, ObjectValue(*objectProto)); |
|
6143 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SCRIPT_PROTO, ObjectValue(*scriptProto)); |
|
6144 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SOURCE_PROTO, ObjectValue(*sourceProto)); |
|
6145 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_ENV_PROTO, ObjectValue(*envProto)); |
|
6146 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO, ObjectValue(*memoryProto)); |
|
6147 return true; |
|
6148 } |