Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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/. */
7 #include "vm/Debugger-inl.h"
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"
29 using namespace js;
31 using js::frontend::IsIdentifier;
32 using mozilla::ArrayLength;
33 using mozilla::Maybe;
36 /*** Forward declarations ************************************************************************/
38 extern const Class DebuggerFrame_class;
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 };
48 extern const Class DebuggerArguments_class;
50 enum {
51 JSSLOT_DEBUGARGUMENTS_FRAME,
52 JSSLOT_DEBUGARGUMENTS_COUNT
53 };
55 extern const Class DebuggerEnv_class;
57 enum {
58 JSSLOT_DEBUGENV_OWNER,
59 JSSLOT_DEBUGENV_COUNT
60 };
62 extern const Class DebuggerObject_class;
64 enum {
65 JSSLOT_DEBUGOBJECT_OWNER,
66 JSSLOT_DEBUGOBJECT_COUNT
67 };
69 extern const Class DebuggerScript_class;
71 enum {
72 JSSLOT_DEBUGSCRIPT_OWNER,
73 JSSLOT_DEBUGSCRIPT_COUNT
74 };
76 extern const Class DebuggerSource_class;
78 enum {
79 JSSLOT_DEBUGSOURCE_OWNER,
80 JSSLOT_DEBUGSOURCE_COUNT
81 };
84 /*** Utils ***************************************************************************************/
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 }
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 }
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 }
118 #define REQUIRE_ARGC(name, n) \
119 JS_BEGIN_MACRO \
120 if (argc < (n)) \
121 return ReportMoreArgsNeeded(cx, name, n); \
122 JS_END_MACRO
124 static bool
125 ReportObjectRequired(JSContext *cx)
126 {
127 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
128 return false;
129 }
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 }
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;
157 /* The debuggers in |fp|'s compartment, or nullptr if there are none. */
158 GlobalObject::DebuggerVector *debuggers;
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;
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;
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;
189 /* Find our global, if we were not given one. */
190 if (!global)
191 global = &frame.script()->global();
193 /* The frame and global must match. */
194 JS_ASSERT(&frame.script()->global() == global);
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 }
206 bool empty() const {
207 return nextDebugger >= debuggerCount;
208 }
210 JSObject *frontFrame() const {
211 JS_ASSERT(!empty());
212 return entry->value();
213 }
215 Debugger *frontDebugger() const {
216 JS_ASSERT(!empty());
217 return (*debuggers)[nextDebugger];
218 }
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 }
229 void popFront() {
230 JS_ASSERT(!empty());
231 nextDebugger++;
232 findNext();
233 }
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 };
251 /*** Breakpoints *********************************************************************************/
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 }
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 }
270 void
271 BreakpointSite::inc(FreeOp *fop)
272 {
273 enabledCount++;
274 if (enabledCount == 1 && !trapHandler)
275 recompile(fop);
276 }
278 void
279 BreakpointSite::dec(FreeOp *fop)
280 {
281 JS_ASSERT(enabledCount > 0);
282 enabledCount--;
283 if (enabledCount == 0 && !trapHandler)
284 recompile(fop);
285 }
287 void
288 BreakpointSite::setTrap(FreeOp *fop, JSTrapHandler handler, const Value &closure)
289 {
290 trapHandler = handler;
291 trapClosure = closure;
293 if (enabledCount == 0)
294 recompile(fop);
295 }
297 void
298 BreakpointSite::clearTrap(FreeOp *fop, JSTrapHandler *handlerp, Value *closurep)
299 {
300 if (handlerp)
301 *handlerp = trapHandler;
302 if (closurep)
303 *closurep = trapClosure;
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 }
316 void
317 BreakpointSite::destroyIfEmpty(FreeOp *fop)
318 {
319 if (JS_CLIST_IS_EMPTY(&breakpoints) && !trapHandler)
320 script->destroyBreakpointSite(fop, pc);
321 }
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 }
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 }
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 }
348 Breakpoint *
349 Breakpoint::fromDebuggerLinks(JSCList *links)
350 {
351 return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, debuggerLinks));
352 }
354 Breakpoint *
355 Breakpoint::fromSiteLinks(JSCList *links)
356 {
357 return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, siteLinks));
358 }
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 }
371 Breakpoint *
372 Breakpoint::nextInDebugger()
373 {
374 JSCList *link = JS_NEXT_LINK(&debuggerLinks);
375 return (link == &debugger->breakpoints) ? nullptr : fromDebuggerLinks(link);
376 }
378 Breakpoint *
379 Breakpoint::nextInSite()
380 {
381 JSCList *link = JS_NEXT_LINK(&siteLinks);
382 return (link == &site->breakpoints) ? nullptr : fromSiteLinks(link);
383 }
385 /*** Debugger hook dispatch **********************************************************************/
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);
393 cx->runtime()->debuggerList.insertBack(this);
394 JS_INIT_CLIST(&breakpoints);
395 JS_INIT_CLIST(&onNewGlobalObjectWatchersLink);
396 }
398 Debugger::~Debugger()
399 {
400 JS_ASSERT_IF(debuggees.initialized(), debuggees.empty());
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 }
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 }
426 Debugger *
427 Debugger::fromJSObject(JSObject *obj)
428 {
429 JS_ASSERT(js::GetObjectClass(obj) == &jsclass);
430 return (Debugger *) obj->getPrivate();
431 }
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));
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 }
450 bool
451 Debugger::getScriptFrameWithIter(JSContext *cx, AbstractFramePtr frame,
452 const ScriptFrameIter *maybeIter, MutableHandleValue vp)
453 {
454 MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == frame);
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;
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 }
476 frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*object));
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 }
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 }
495 bool
496 Debugger::hasAnyLiveHooks() const
497 {
498 if (!enabled)
499 return false;
501 if (getHook(OnDebuggerStatement) ||
502 getHook(OnExceptionUnwind) ||
503 getHook(OnNewScript) ||
504 getHook(OnEnterFrame))
505 {
506 return true;
507 }
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 }
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 }
522 return false;
523 }
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();
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 }
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 }
553 return JSTRAP_CONTINUE;
554 }
556 static void
557 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp *fop, AbstractFramePtr frame,
558 JSObject *frameobj);
560 static void
561 DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj);
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();
573 /* Save the frame's completion value. */
574 JSTrapStatus status;
575 RootedValue value(cx);
576 Debugger::resultToCompletion(cx, frameOk, frame.returnValue(), &status, &value);
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 }
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);
592 if (dbg->enabled &&
593 !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined()) {
594 RootedValue handler(cx, frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER));
596 Maybe<AutoCompartment> ac;
597 ac.construct(cx, dbg->object);
599 RootedValue completion(cx);
600 if (!dbg->newCompletionValue(cx, status, value, &completion)) {
601 status = dbg->handleUncaughtException(ac, false);
602 break;
603 }
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);
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());
619 /* JSTRAP_CONTINUE means "make no change". */
620 if (nextStatus != JSTRAP_CONTINUE) {
621 status = nextStatus;
622 value = nextValue;
623 }
624 }
625 }
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));
637 FreeOp *fop = cx->runtime()->defaultFreeOp();
638 DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
639 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
641 dbg->frames.remove(frame);
642 }
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 }
653 /* Establish (status, value) as our resumption value. */
654 switch (status) {
655 case JSTRAP_RETURN:
656 frame.setReturnValue(value);
657 return true;
659 case JSTRAP_THROW:
660 cx->setPendingException(value);
661 return false;
663 case JSTRAP_ERROR:
664 JS_ASSERT(!cx->isExceptionPending());
665 return false;
667 default:
668 MOZ_ASSUME_UNREACHABLE("bad final trap status");
669 }
670 }
672 bool
673 Debugger::wrapEnvironment(JSContext *cx, Handle<Env*> env, MutableHandleValue rval)
674 {
675 if (!env) {
676 rval.setNull();
677 return true;
678 }
680 /*
681 * DebuggerEnv should only wrap a debug scope chain obtained (transitively)
682 * from GetDebugScopeFor(Frame|Function).
683 */
684 JS_ASSERT(!env->is<ScopeObject>());
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 }
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 }
714 bool
715 Debugger::wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp)
716 {
717 assertSameCompartment(cx, object.get());
719 if (vp.isObject()) {
720 RootedObject obj(cx, &vp.toObject());
722 if (obj->is<JSFunction>()) {
723 RootedFunction fun(cx, &obj->as<JSFunction>());
724 if (!EnsureFunctionHasScript(cx, fun))
725 return false;
726 }
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));
741 if (!p.add(cx, objects, obj, dobj)) {
742 js_ReportOutOfMemory(cx);
743 return false;
744 }
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 }
755 vp.setObject(*dobj);
756 }
757 } else if (vp.isMagic()) {
758 RootedObject optObj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
759 if (!optObj)
760 return false;
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 }
773 RootedValue trueVal(cx, BooleanValue(true));
774 if (!JSObject::defineProperty(cx, optObj, name, trueVal))
775 return false;
777 vp.setObject(*optObj);
778 } else if (!cx->compartment()->wrap(cx, vp)) {
779 vp.setUndefined();
780 return false;
781 }
783 return true;
784 }
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 }
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 }
807 vp.setObject(*static_cast<JSObject*>(dobj->getPrivate()));
808 }
809 return true;
810 }
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 }
829 if (cx->isExceptionPending()) {
830 JS_ReportPendingException(cx);
831 cx->clearPendingException();
832 }
833 }
834 ac.destroy();
835 return JSTRAP_ERROR;
836 }
838 JSTrapStatus
839 Debugger::handleUncaughtException(Maybe<AutoCompartment> &ac, MutableHandleValue vp, bool callHook)
840 {
841 return handleUncaughtExceptionHelper(ac, &vp, callHook);
842 }
844 JSTrapStatus
845 Debugger::handleUncaughtException(Maybe<AutoCompartment> &ac, bool callHook)
846 {
847 return handleUncaughtExceptionHelper(ac, nullptr, callHook);
848 }
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());
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 }
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());
880 RootedId key(cx);
881 RootedValue value(cx, value_);
883 switch (status) {
884 case JSTRAP_RETURN:
885 key = NameToId(cx->names().return_);
886 break;
888 case JSTRAP_THROW:
889 key = NameToId(cx->names().throw_);
890 break;
892 case JSTRAP_ERROR:
893 result.setNull();
894 return true;
896 default:
897 MOZ_ASSUME_UNREACHABLE("bad status passed to Debugger::newCompletionValue");
898 }
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 }
910 result.setObject(*obj);
911 return true;
912 }
914 bool
915 Debugger::receiveCompletionValue(Maybe<AutoCompartment> &ac, bool ok,
916 HandleValue val,
917 MutableHandleValue vp)
918 {
919 JSContext *cx = ac.ref().context()->asJSContext();
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 }
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 }
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 }
967 RootedValue v(cx, vp.get());
968 if (!NativeGet(cx, obj, obj, shape, &v) || !unwrapDebuggeeValue(cx, &v))
969 return handleUncaughtException(ac, &v, callHook);
971 ac.destroy();
972 if (!cx->compartment()->wrap(cx, &v)) {
973 vp.setUndefined();
974 return JSTRAP_ERROR;
975 }
976 vp.set(v);
978 return shape->propid() == returnId ? JSTRAP_RETURN : JSTRAP_THROW;
979 }
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;
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 }
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());
1003 Maybe<AutoCompartment> ac;
1004 ac.construct(cx, object);
1006 ScriptFrameIter iter(cx);
1008 RootedValue scriptFrame(cx);
1009 if (!getScriptFrame(cx, iter, &scriptFrame))
1010 return handleUncaughtException(ac, false);
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 }
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());
1024 RootedValue exc(cx);
1025 if (!cx->getPendingException(&exc))
1026 return JSTRAP_ERROR;
1027 cx->clearPendingException();
1029 Maybe<AutoCompartment> ac;
1030 ac.construct(cx, object);
1032 JS::AutoValueArray<2> argv(cx);
1033 argv[0].setUndefined();
1034 argv[1].set(exc);
1036 ScriptFrameIter iter(cx);
1037 if (!getScriptFrame(cx, iter, argv[0]) || !wrapDebuggeeValue(cx, argv[1]))
1038 return handleUncaughtException(ac, false);
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 }
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());
1055 Maybe<AutoCompartment> ac;
1056 ac.construct(cx, object);
1058 RootedValue scriptFrame(cx);
1059 if (!getScriptFrame(cx, frame, &scriptFrame))
1060 return handleUncaughtException(ac, false);
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 }
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());
1074 Maybe<AutoCompartment> ac;
1075 ac.construct(cx, object);
1077 JSObject *dsobj = wrapScript(cx, script);
1078 if (!dsobj) {
1079 handleUncaughtException(ac, false);
1080 return;
1081 }
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 }
1089 JSTrapStatus
1090 Debugger::dispatchHook(JSContext *cx, MutableHandleValue vp, Hook which)
1091 {
1092 JS_ASSERT(which == OnDebuggerStatement || which == OnExceptionUnwind);
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 }
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 }
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 }
1149 void
1150 Debugger::slowPathOnNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal_)
1151 {
1152 Rooted<GlobalObject*> compileAndGoGlobal(cx, compileAndGoGlobal_);
1154 JS_ASSERT(script->compileAndGo() == !!compileAndGoGlobal);
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 }
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 }
1189 JSTrapStatus
1190 Debugger::onTrap(JSContext *cx, MutableHandleValue vp)
1191 {
1192 MOZ_ASSERT(cx->compartment()->debugMode());
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);
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 }
1208 for (Breakpoint **p = triggered.begin(); p != triggered.end(); p++) {
1209 Breakpoint *bp = *p;
1211 /* Handlers can clear breakpoints. Check that bp still exists. */
1212 if (!site || !site->hasBreakpoint(bp))
1213 continue;
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);
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;
1242 /* Calling JS code invalidates site. Reload it. */
1243 site = script->getBreakpointSite(pc);
1244 }
1245 }
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 }
1253 /* By convention, return the true op to the interpreter in vp. */
1254 vp.setInt32(op);
1255 return JSTRAP_CONTINUE;
1256 }
1258 JSTrapStatus
1259 Debugger::onSingleStep(JSContext *cx, MutableHandleValue vp)
1260 {
1261 ScriptFrameIter iter(cx);
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 }
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 }
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
1326 /* Preserve the debuggee's iterValue while handlers run. */
1327 class PreserveIterValue {
1328 JSContext *cx;
1329 RootedValue savedIterValue;
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);
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);
1346 Maybe<AutoCompartment> ac;
1347 ac.construct(cx, dbg->object);
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 }
1357 vp.setUndefined();
1358 if (exceptionPending)
1359 cx->setPendingException(exception);
1360 return JSTRAP_CONTINUE;
1361 }
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());
1370 Maybe<AutoCompartment> ac;
1371 ac.construct(cx, object);
1373 RootedValue wrappedGlobal(cx, ObjectValue(*global));
1374 if (!wrapDebuggeeValue(cx, &wrappedGlobal))
1375 return handleUncaughtException(ac, false);
1377 RootedValue rv(cx);
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 }
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;
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 }
1423 JSTrapStatus status = JSTRAP_CONTINUE;
1424 RootedValue value(cx);
1426 for (size_t i = 0; i < watchers.length(); i++) {
1427 Debugger *dbg = fromJSObject(watchers[i]);
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 }
1447 /*** Debugger JSObjects **************************************************************************/
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 }
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();
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 }
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;
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);
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;
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;
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 }
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 }
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();
1594 MarkObjectUnbarriered(trc, &global, "Global Object");
1595 if (global != e.front())
1596 e.rekeyFront(global);
1597 }
1599 HeapPtrObject &dbgobj = dbg->toJSObjectRef();
1600 MarkObject(trc, &dbgobj, "Debugger Object");
1602 dbg->scripts.trace(trc);
1603 dbg->sources.trace(trc);
1604 dbg->objects.trace(trc);
1605 dbg->environments.trace(trc);
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 }
1614 void
1615 Debugger::traceObject(JSTracer *trc, JSObject *obj)
1616 {
1617 if (Debugger *dbg = Debugger::fromJSObject(obj))
1618 dbg->trace(trc);
1619 }
1621 void
1622 Debugger::trace(JSTracer *trc)
1623 {
1624 if (uncaughtExceptionHook)
1625 MarkObject(trc, &uncaughtExceptionHook, "hooks");
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 }
1641 /* Trace the weak map from JSScript instances to Debugger.Script objects. */
1642 scripts.trace(trc);
1644 /* Trace the referent ->Debugger.Source weak map */
1645 sources.trace(trc);
1647 /* Trace the referent -> Debugger.Object weak map. */
1648 objects.trace(trc);
1650 /* Trace the referent -> Debugger.Environment weak map. */
1651 environments.trace(trc);
1652 }
1654 void
1655 Debugger::sweepAll(FreeOp *fop)
1656 {
1657 JSRuntime *rt = fop->runtime();
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 }
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 }
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 }
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 }
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 }
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 };
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 }
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 }
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
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 }
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]);
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 }
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 }
1821 dbg->enabled = enabled;
1822 args.rval().setUndefined();
1823 return true;
1824 }
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 }
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 }
1853 bool
1854 Debugger::getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp)
1855 {
1856 return getHookImpl(cx, argc, vp, OnDebuggerStatement);
1857 }
1859 bool
1860 Debugger::setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp)
1861 {
1862 return setHookImpl(cx, argc, vp, OnDebuggerStatement);
1863 }
1865 bool
1866 Debugger::getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp)
1867 {
1868 return getHookImpl(cx, argc, vp, OnExceptionUnwind);
1869 }
1871 bool
1872 Debugger::setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp)
1873 {
1874 return setHookImpl(cx, argc, vp, OnExceptionUnwind);
1875 }
1877 bool
1878 Debugger::getOnNewScript(JSContext *cx, unsigned argc, Value *vp)
1879 {
1880 return getHookImpl(cx, argc, vp, OnNewScript);
1881 }
1883 bool
1884 Debugger::setOnNewScript(JSContext *cx, unsigned argc, Value *vp)
1885 {
1886 return setHookImpl(cx, argc, vp, OnNewScript);
1887 }
1889 bool
1890 Debugger::getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp)
1891 {
1892 return getHookImpl(cx, argc, vp, OnEnterFrame);
1893 }
1895 bool
1896 Debugger::setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp)
1897 {
1898 return setHookImpl(cx, argc, vp, OnEnterFrame);
1899 }
1901 bool
1902 Debugger::getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp)
1903 {
1904 return getHookImpl(cx, argc, vp, OnNewGlobalObject);
1905 }
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));
1913 if (!setHookImpl(cx, argc, vp, OnNewGlobalObject))
1914 return false;
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 }
1934 return true;
1935 }
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 }
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 }
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 }
1976 RootedObject obj(cx, &v.toObject());
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 }
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 }
1993 /* If that produced an outer window, innerize it. */
1994 obj = GetInnerObject(cx, obj);
1995 if (!obj)
1996 return nullptr;
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 }
2005 return &obj->as<GlobalObject>();
2006 }
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;
2017 if (!dbg->addDebuggeeGlobal(cx, global))
2018 return false;
2020 RootedValue v(cx, ObjectValue(*global));
2021 if (!dbg->wrapDebuggeeValue(cx, &v))
2022 return false;
2023 args.rval().set(v);
2024 return true;
2025 }
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);
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 }
2049 args.rval().setUndefined();
2050 return true;
2051 }
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 }
2069 bool
2070 Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp)
2071 {
2072 THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg);
2074 for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
2075 if (!dbg->removeDebuggeeGlobal(cx, e.front(), nullptr, &e))
2076 return false;
2077 }
2079 args.rval().setUndefined();
2080 return true;
2081 }
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 }
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 }
2114 bool
2115 Debugger::getNewestFrame(JSContext *cx, unsigned argc, Value *vp)
2116 {
2117 THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
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 }
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 }
2147 bool
2148 Debugger::construct(JSContext *cx, unsigned argc, Value *vp)
2149 {
2150 CallArgs args = CallArgsFromVp(argc, vp);
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 }
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));
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. */
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 }
2210 args.rval().setObject(*obj);
2211 return true;
2212 }
2214 bool
2215 Debugger::addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> global)
2216 {
2217 AutoDebugModeInvalidation invalidate(global->compartment());
2218 return addDebuggeeGlobal(cx, global, invalidate);
2219 }
2221 bool
2222 Debugger::addDebuggeeGlobal(JSContext *cx,
2223 Handle<GlobalObject*> global,
2224 AutoDebugModeInvalidation &invalidate)
2225 {
2226 if (debuggees.has(global))
2227 return true;
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 }
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 }
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 }
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;
2287 /* Maintain consistency on error. */
2288 debuggees.remove(global);
2289 }
2290 JS_ASSERT(v->back() == this);
2291 v->popBack();
2292 }
2293 return false;
2294 }
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);
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 }
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());
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);
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 }
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 }
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);
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);
2382 return true;
2383 }
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 }
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);
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 }
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 {}
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 }
2435 return true;
2436 }
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;
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 }
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 }
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 }
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 }
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 }
2527 return true;
2528 }
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 }
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;
2547 JSCompartment *singletonComp = nullptr;
2548 if (compartments.count() == 1)
2549 singletonComp = compartments.all().front();
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 }
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 }
2577 return true;
2578 }
2580 private:
2581 /* The context in which we should do our work. */
2582 JSContext *cx;
2584 /* The debugger for which we conduct queries. */
2585 Debugger *debugger;
2587 typedef HashSet<JSCompartment *, DefaultHasher<JSCompartment *>, RuntimeAllocPolicy>
2588 CompartmentSet;
2590 /* A script must be in one of these compartments to match the query. */
2591 CompartmentSet compartments;
2593 /* If this is a string, matching scripts have urls equal to it. */
2594 RootedValue url;
2596 /* url as a C string. */
2597 JSAutoByteString urlCString;
2599 /* If this is a string, matching scripts' sources have displayURLs equal to
2600 * it. */
2601 RootedValue displayURL;
2603 /* displayURL as a jschar* */
2604 const jschar *displayURLChars;
2605 size_t displayURLLength;
2607 /* True if the query contained a 'line' property. */
2608 bool hasLine;
2610 /* The line matching scripts must cover. */
2611 unsigned int line;
2613 /* True if the query has an 'innermost' property whose value is true. */
2614 bool innermost;
2616 typedef HashMap<JSCompartment *, JSScript *, DefaultHasher<JSCompartment *>, RuntimeAllocPolicy>
2617 CompartmentToScriptMap;
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;
2626 /* The vector to which to append the scripts found. */
2627 AutoScriptVector *vector;
2629 /* Indicates whether OOM has occurred while matching. */
2630 bool oom;
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 }
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 }
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 }
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 }
2688 return true;
2689 }
2691 static void considerScript(JSRuntime *rt, void *data, JSScript *script) {
2692 ScriptQuery *self = static_cast<ScriptQuery *>(data);
2693 self->consider(script);
2694 }
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;
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 }
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 }
2774 return;
2775 }
2776 };
2778 bool
2779 Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp)
2780 {
2781 THIS_DEBUGGER(cx, argc, vp, "findScripts", args, dbg);
2783 ScriptQuery query(cx, dbg);
2784 if (!query.init())
2785 return false;
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 }
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);
2803 if (!query.findScripts(&scripts))
2804 return false;
2806 RootedObject result(cx, NewDenseAllocatedArray(cx, scripts.length()));
2807 if (!result)
2808 return false;
2810 result->ensureDenseInitializedLength(cx, 0, scripts.length());
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 }
2819 args.rval().setObject(*result);
2820 return true;
2821 }
2823 bool
2824 Debugger::findAllGlobals(JSContext *cx, unsigned argc, Value *vp)
2825 {
2826 THIS_DEBUGGER(cx, argc, vp, "findAllGlobals", args, dbg);
2828 RootedObject result(cx, NewDenseEmptyArray(cx));
2829 if (!result)
2830 return false;
2832 for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
2833 if (c->options().invisibleToDebugger())
2834 continue;
2836 c->zone()->scheduledForDestruction = false;
2838 GlobalObject *global = c->maybeGlobal();
2840 if (cx->runtime()->isSelfHostingGlobal(global))
2841 continue;
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);
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 }
2859 args.rval().setObject(*result);
2860 return true;
2861 }
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);
2869 Rooted<GlobalObject *> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
2870 if (!global)
2871 return false;
2873 args.rval().setObject(*global);
2874 return dbg->wrapDebuggeeValue(cx, args.rval());
2875 }
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 };
2907 /*** Debugger.Script *****************************************************************************/
2909 static inline JSScript *
2910 GetScriptReferent(JSObject *obj)
2911 {
2912 JS_ASSERT(obj->getClass() == &DebuggerScript_class);
2913 return static_cast<JSScript *>(obj->getPrivate());
2914 }
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 }
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 };
2938 JSObject *
2939 Debugger::newDebuggerScript(JSContext *cx, HandleScript script)
2940 {
2941 assertSameCompartment(cx, object.get());
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);
2951 return scriptobj;
2952 }
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;
2965 if (!p.add(cx, scripts, script, scriptobj)) {
2966 js_ReportOutOfMemory(cx);
2967 return nullptr;
2968 }
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 }
2978 JS_ASSERT(GetScriptReferent(p->value()) == script);
2979 return p->value();
2980 }
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 }
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 }
3007 return thisobj;
3008 }
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 }
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))
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);
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 }
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 }
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);
3056 unsigned maxLine = js_GetScriptLineExtent(script);
3057 args.rval().setNumber(double(maxLine));
3058 return true;
3059 }
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);
3067 RootedScriptSource source(cx, &UncheckedUnwrap(script->sourceObject())->as<ScriptSourceObject>());
3068 RootedObject sourceObject(cx, dbg->wrapSource(cx, source));
3069 if (!sourceObject)
3070 return false;
3072 args.rval().setObject(*sourceObject);
3073 return true;
3074 }
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 }
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 }
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 }
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);
3105 ScriptSource *source = script->scriptSource();
3106 JS_ASSERT(source);
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 }
3117 return true;
3118 }
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);
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 }
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);
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 }
3170 static bool
3171 ScriptOffset(JSContext *cx, JSScript *script, const Value &v, size_t *offsetp)
3172 {
3173 double d;
3174 size_t off;
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 }
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 }
3202 namespace {
3204 class BytecodeRangeWithPosition : private BytecodeRange
3205 {
3206 public:
3207 using BytecodeRange::empty;
3208 using BytecodeRange::frontPC;
3209 using BytecodeRange::frontOpcode;
3210 using BytecodeRange::frontOffset;
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 }
3223 void popFront() {
3224 BytecodeRange::popFront();
3225 if (!empty())
3226 updatePosition();
3227 }
3229 size_t frontLineNumber() const { return lineno; }
3230 size_t frontColumnNumber() const { return column; }
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);
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 }
3255 sn = SN_NEXT(sn);
3256 snpc += SN_DELTA(sn);
3257 }
3258 }
3260 size_t lineno;
3261 size_t column;
3262 jssrcnote *sn;
3263 jsbytecode *snpc;
3264 };
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 }
3311 static Entry createWithSingleEdge(size_t lineno, size_t column) {
3312 return Entry(lineno, column);
3313 }
3315 static Entry createWithMultipleEdgesFromSingleLine(size_t lineno) {
3316 return Entry(lineno, SIZE_MAX);
3317 }
3319 static Entry createWithMultipleEdgesFromMultipleLines() {
3320 return Entry(SIZE_MAX, SIZE_MAX);
3321 }
3323 Entry() {}
3325 bool hasNoEdges() const {
3326 return lineno_ == SIZE_MAX && column_ != SIZE_MAX;
3327 }
3329 bool hasSingleEdge() const {
3330 return lineno_ != SIZE_MAX && column_ != SIZE_MAX;
3331 }
3333 bool hasMultipleEdgesFromSingleLine() const {
3334 return lineno_ != SIZE_MAX && column_ == SIZE_MAX;
3335 }
3337 bool hasMultipleEdgesFromMultipleLines() const {
3338 return lineno_ == SIZE_MAX && column_ == SIZE_MAX;
3339 }
3341 bool operator==(const Entry &other) const {
3342 return lineno_ == other.lineno_ && column_ == other.column_;
3343 }
3345 bool operator!=(const Entry &other) const {
3346 return lineno_ != other.lineno_ || column_ != other.column_;
3347 }
3349 size_t lineno() const {
3350 return lineno_;
3351 }
3353 size_t column() const {
3354 return column_;
3355 }
3357 private:
3358 Entry(size_t lineno, size_t column) : lineno_(lineno), column_(column) {}
3360 size_t lineno_;
3361 size_t column_;
3362 };
3364 FlowGraphSummary(JSContext *cx) : entries_(cx) {}
3366 Entry &operator[](size_t index) {
3367 return entries_[index];
3368 }
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();
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();
3386 if (FlowsIntoNext(prevOp))
3387 addEdge(prevLineno, prevColumn, r.frontOffset());
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);
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;
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 }
3411 prevLineno = lineno;
3412 prevColumn = column;
3413 prevOp = op;
3414 }
3416 return true;
3417 }
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 }
3429 Vector<Entry> entries_;
3430 };
3432 } /* anonymous namespace */
3434 static bool
3435 DebuggerScript_getAllOffsets(JSContext *cx, unsigned argc, Value *vp)
3436 {
3437 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script);
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;
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();
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);
3461 RootedId id(cx, INT_TO_JSID(lineno));
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;
3469 if (offsetsv.isObject()) {
3470 offsets = &offsetsv.toObject();
3471 } else {
3472 JS_ASSERT(offsetsv.isUndefined());
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 }
3487 RootedValue value(cx, ObjectValue(*offsets));
3488 if (!JSObject::defineGeneric(cx, result, id, value))
3489 return false;
3490 }
3492 /* Append the current offset to the offsets array. */
3493 if (!NewbornArrayPush(cx, offsets, NumberValue(offset)))
3494 return false;
3495 }
3496 }
3498 args.rval().setObject(*result);
3499 return true;
3500 }
3502 static bool
3503 DebuggerScript_getAllColumnOffsets(JSContext *cx, unsigned argc, Value *vp)
3504 {
3505 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllColumnOffsets", args, obj, script);
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;
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();
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;
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;
3537 value = NumberValue(column);
3538 if (!JSObject::defineProperty(cx, entry, cx->names().columnNumber, value))
3539 return false;
3541 id = NameToId(cx->names().offset);
3542 value = NumberValue(offset);
3543 if (!JSObject::defineGeneric(cx, entry, id, value))
3544 return false;
3546 if (!NewbornArrayPush(cx, result, ObjectValue(*entry)))
3547 return false;
3548 }
3549 }
3551 args.rval().setObject(*result);
3552 return true;
3553 }
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);
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 }
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;
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();
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 }
3600 args.rval().setObject(*result);
3601 return true;
3602 }
3604 bool
3605 Debugger::observesFrame(AbstractFramePtr frame) const
3606 {
3607 return observesScript(frame.script());
3608 }
3610 bool
3611 Debugger::observesFrame(const ScriptFrameIter &iter) const
3612 {
3613 return observesScript(iter.script());
3614 }
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 }
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));
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);
3641 // Remove the old entry before mutating the HashMap.
3642 r.removeFrontFrame();
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 }
3651 return true;
3652 }
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 }
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 }
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);
3686 if (!dbg->observesScript(script)) {
3687 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGING);
3688 return false;
3689 }
3691 size_t offset;
3692 if (!ScriptOffset(cx, script, args[0], &offset))
3693 return false;
3695 JSObject *handler = NonNullObject(cx, args[1]);
3696 if (!handler)
3697 return false;
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 }
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);
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 }
3729 RootedObject arr(cx, NewDenseEmptyArray(cx));
3730 if (!arr)
3731 return false;
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 }
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);
3756 JSObject *handler = NonNullObject(cx, args[0]);
3757 if (!handler)
3758 return false;
3760 script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, handler);
3761 args.rval().setUndefined();
3762 return true;
3763 }
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 }
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);
3781 size_t offset;
3782 if (!ScriptOffset(cx, script, args[0], &offset))
3783 return false;
3785 /*
3786 * Try note ranges are relative to the mainOffset of the script, so adjust
3787 * offset accordingly.
3788 */
3789 offset -= script->mainOffset();
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 }
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 }
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 };
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 };
3845 /*** Debugger.Source *****************************************************************************/
3847 static inline ScriptSourceObject *
3848 GetSourceReferent(JSObject *obj)
3849 {
3850 JS_ASSERT(obj->getClass() == &DebuggerSource_class);
3851 return static_cast<ScriptSourceObject *>(obj->getPrivate());
3852 }
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 }
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 };
3879 JSObject *
3880 Debugger::newDebuggerSource(JSContext *cx, HandleScriptSource source)
3881 {
3882 assertSameCompartment(cx, object.get());
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);
3892 return sourceobj;
3893 }
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;
3906 if (!p.add(cx, sources, source, sourceobj)) {
3907 js_ReportOutOfMemory(cx);
3908 return nullptr;
3909 }
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 }
3919 JS_ASSERT(GetSourceReferent(p->value()) == source);
3920 return p->value();
3921 }
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 }
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 }
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 }
3946 if (!GetSourceReferent(thisobj)) {
3947 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
3948 "Debugger.Frame", fnname, "prototype object");
3949 return nullptr;
3950 }
3952 return thisobj;
3953 }
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;
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);
3969 ScriptSource *ss = sourceObject->source();
3970 bool hasSourceData = ss->hasSourceData();
3971 if (!ss->hasSourceData() && !JSScript::loadSource(cx, ss, &hasSourceData))
3972 return false;
3974 JSString *str = hasSourceData ? ss->substring(cx, 0, ss->length())
3975 : js_NewStringCopyZ<CanGC>(cx, "[no source]");
3976 if (!str)
3977 return false;
3979 args.rval().setString(str);
3980 return true;
3981 }
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);
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 }
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);
4005 ScriptSource *ss = sourceObject->source();
4006 JS_ASSERT(ss);
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 }
4017 return true;
4018 }
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);
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 }
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 }
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);
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 }
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);
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 }
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);
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 }
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 };
4105 static const JSFunctionSpec DebuggerSource_methods[] = {
4106 JS_FS_END
4107 };
4110 /*** Debugger.Frame ******************************************************************************/
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();
4129 ActivationIterator activationIter(activation->cx()->runtime());
4130 while (activationIter.activation() != activation)
4131 ++activationIter;
4133 jit::JitFrameIterator jitIter(activationIter);
4134 while (!jitIter.isIonJS() || jitIter.jsFrame() != jsFrame)
4135 ++jitIter;
4137 jit::InlineFrameIterator ionInlineIter(activation->cx(), &jitIter);
4138 while (ionInlineIter.frameNo() != frame->frameNo())
4139 ++ionInlineIter;
4141 MOZ_ASSERT(ionInlineIter.pc() == iter.pc());
4142 #endif
4143 return;
4144 }
4146 iter.updatePcQuadratic();
4147 }
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 }
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 }
4167 static void
4168 DebuggerFrame_finalize(FreeOp *fop, JSObject *obj)
4169 {
4170 DebuggerFrame_freeScriptFrameIterData(fop, obj);
4171 }
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 };
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 }
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 }
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 */
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
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 }
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()
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)
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)
4274 static bool
4275 DebuggerFrame_getType(JSContext *cx, unsigned argc, Value *vp)
4276 {
4277 THIS_FRAME(cx, argc, vp, "get type", args, thisobj, frame);
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 }
4291 static bool
4292 DebuggerFrame_getImplementation(JSContext *cx, unsigned argc, Value *vp)
4293 {
4294 THIS_FRAME(cx, argc, vp, "get implementation", args, thisobj, frame);
4296 const char *s;
4297 if (frame.isBaselineFrame())
4298 s = "baseline";
4299 else if (frame.isRematerializedFrame())
4300 s = "ion";
4301 else
4302 s = "interpreter";
4304 JSAtom *str = Atomize(cx, s, strlen(s));
4305 if (!str)
4306 return false;
4308 args.rval().setString(str);
4309 return true;
4310 }
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);
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 }
4326 return dbg->wrapEnvironment(cx, env, args.rval());
4327 }
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 }
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 }
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 }
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 }
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);
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 }
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 };
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();
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 }
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);
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 }
4447 if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &arg))
4448 return false;
4449 args.rval().set(arg);
4450 return true;
4451 }
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 }
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));
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 }
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 }
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);
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 }
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 }
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 }
4565 static bool
4566 IsValidHook(const Value &v)
4567 {
4568 return v.isUndefined() || (v.isObject() && v.toObject().isCallable());
4569 }
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 }
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 }
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 }
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 }
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 }
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 }
4631 thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER, args[0]);
4632 args.rval().setUndefined();
4633 return true;
4634 }
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());
4654 JS_ASSERT(!IsPoisonedPtr(chars.get()));
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;
4677 script->setActiveEval();
4678 ExecuteType type = !frame ? EXECUTE_DEBUG_GLOBAL : EXECUTE_DEBUG;
4679 return ExecuteKernel(cx, script, *env, thisv, type, frame, rval.address());
4680 }
4682 enum EvalBindings { EvalHasExtraBindings = true, EvalWithDefaultBindings = false };
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>());
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;
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 }
4729 /* Set options from object if provided. */
4730 JSAutoByteString url_bytes;
4731 char *url = nullptr;
4732 unsigned lineNumber = 1;
4734 if (options.isObject()) {
4735 RootedObject opts(cx, &options.toObject());
4736 RootedValue v(cx);
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 }
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 }
4759 Maybe<AutoCompartment> ac;
4760 if (iter)
4761 ac.construct(cx, iter->scopeChain());
4762 else
4763 ac.construct(cx, scope);
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 }
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 }
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 }
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 }
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 }
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 }
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 };
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 };
4873 /*** Debugger.Object *****************************************************************************/
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 }
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 };
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 }
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 }
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)
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)
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 }
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 }
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 }
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 }
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 }
5002 JSString *name = obj->as<JSFunction>().atom();
5003 if (!name) {
5004 args.rval().setUndefined();
5005 return true;
5006 }
5008 RootedValue namev(cx, StringValue(name));
5009 if (!dbg->wrapDebuggeeValue(cx, &namev))
5010 return false;
5011 args.rval().set(namev);
5012 return true;
5013 }
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 }
5024 JSString *name = obj->as<JSFunction>().displayAtom();
5025 if (!name) {
5026 args.rval().setUndefined();
5027 return true;
5028 }
5030 RootedValue namev(cx, StringValue(name));
5031 if (!dbg->wrapDebuggeeValue(cx, &namev))
5032 return false;
5033 args.rval().set(namev);
5034 return true;
5035 }
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 }
5046 RootedFunction fun(cx, &obj->as<JSFunction>());
5048 /* Only hand out parameter info for debuggee functions. */
5049 if (!dbg->observesGlobal(&fun->global())) {
5050 args.rval().setUndefined();
5051 return true;
5052 }
5054 RootedObject result(cx, NewDenseAllocatedArray(cx, fun->nargs()));
5055 if (!result)
5056 return false;
5057 result->ensureDenseInitializedLength(cx, 0, fun->nargs());
5059 if (fun->isInterpreted()) {
5060 RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
5061 if (!script)
5062 return false;
5064 JS_ASSERT(fun->nargs() == script->bindings.numArgs());
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 }
5084 args.rval().setObject(*result);
5085 return true;
5086 }
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);
5093 if (!obj->is<JSFunction>()) {
5094 args.rval().setUndefined();
5095 return true;
5096 }
5098 RootedFunction fun(cx, &obj->as<JSFunction>());
5099 if (fun->isBuiltin()) {
5100 args.rval().setUndefined();
5101 return true;
5102 }
5104 RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
5105 if (!script)
5106 return false;
5108 /* Only hand out debuggee scripts. */
5109 if (!dbg->observesScript(script)) {
5110 args.rval().setNull();
5111 return true;
5112 }
5114 RootedObject scriptObject(cx, dbg->wrapScript(cx, script));
5115 if (!scriptObject)
5116 return false;
5118 args.rval().setObject(*scriptObject);
5119 return true;
5120 }
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);
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 }
5133 /* Only hand out environments of debuggee functions. */
5134 if (!dbg->observesGlobal(&obj->global())) {
5135 args.rval().setNull();
5136 return true;
5137 }
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 }
5148 return dbg->wrapEnvironment(cx, env, args.rval());
5149 }
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);
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 }
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);
5168 RootedId id(cx);
5169 if (!ValueToId<CanGC>(cx, args.get(0), &id))
5170 return false;
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;
5180 ErrorCopier ec(ac, dbg->toJSObject());
5181 if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
5182 return false;
5183 }
5185 if (desc.object()) {
5186 /* Rewrap the debuggee values in desc for the debugger. */
5187 if (!dbg->wrapDebuggeeValue(cx, desc.value()))
5188 return false;
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 }
5204 return NewPropertyDescriptorObject(cx, desc, args.rval());
5205 }
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);
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 }
5221 AutoValueVector vals(cx);
5222 if (!vals.resize(keys.length()))
5223 return false;
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 }
5243 JSObject *aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin());
5244 if (!aobj)
5245 return false;
5246 args.rval().setObject(*aobj);
5247 return true;
5248 }
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);
5256 RootedId id(cx);
5257 if (!ValueToId<CanGC>(cx, args[0], &id))
5258 return false;
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();
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;
5274 {
5275 PropDesc *rewrappedDesc = descs.append();
5276 if (!rewrappedDesc)
5277 return false;
5278 RootedId wrappedId(cx);
5280 Maybe<AutoCompartment> ac;
5281 ac.construct(cx, obj);
5282 if (!unwrappedDesc->wrapInto(cx, obj, id, wrappedId.address(), rewrappedDesc))
5283 return false;
5285 ErrorCopier ec(ac, dbg->toJSObject());
5286 bool dummy;
5287 if (!DefineProperty(cx, obj, wrappedId, *rewrappedDesc, true, &dummy))
5288 return false;
5289 }
5291 args.rval().setUndefined();
5292 return true;
5293 }
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);
5301 RootedValue arg(cx, args[0]);
5302 RootedObject props(cx, ToObject(cx, arg));
5303 if (!props)
5304 return false;
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();
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 }
5322 {
5323 AutoIdVector rewrappedIds(cx);
5324 AutoPropDescArrayRooter rewrappedDescs(cx);
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 }
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 }
5348 args.rval().setUndefined();
5349 return true;
5350 }
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));
5362 Maybe<AutoCompartment> ac;
5363 ac.construct(cx, obj);
5364 if (!cx->compartment()->wrap(cx, &nameArg))
5365 return false;
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 }
5375 enum SealHelperOp { Seal, Freeze, PreventExtensions };
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);
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 }
5407 static bool
5408 DebuggerObject_seal(JSContext *cx, unsigned argc, Value *vp)
5409 {
5410 return DebuggerObject_sealHelper(cx, argc, vp, Seal, "seal");
5411 }
5413 static bool
5414 DebuggerObject_freeze(JSContext *cx, unsigned argc, Value *vp)
5415 {
5416 return DebuggerObject_sealHelper(cx, argc, vp, Freeze, "freeze");
5417 }
5419 static bool
5420 DebuggerObject_preventExtensions(JSContext *cx, unsigned argc, Value *vp)
5421 {
5422 return DebuggerObject_sealHelper(cx, argc, vp, PreventExtensions, "preventExtensions");
5423 }
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);
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 }
5449 static bool
5450 DebuggerObject_isSealed(JSContext *cx, unsigned argc, Value *vp)
5451 {
5452 return DebuggerObject_isSealedHelper(cx, argc, vp, Seal, "isSealed");
5453 }
5455 static bool
5456 DebuggerObject_isFrozen(JSContext *cx, unsigned argc, Value *vp)
5457 {
5458 return DebuggerObject_isSealedHelper(cx, argc, vp, Freeze, "isFrozen");
5459 }
5461 static bool
5462 DebuggerObject_isExtensible(JSContext *cx, unsigned argc, Value *vp)
5463 {
5464 return DebuggerObject_isSealedHelper(cx, argc, vp, PreventExtensions, "isExtensible");
5465 }
5467 enum ApplyOrCallMode { ApplyMode, CallMode };
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);
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 }
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 }
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 }
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;
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 }
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 }
5545 static bool
5546 DebuggerObject_apply(JSContext *cx, unsigned argc, Value *vp)
5547 {
5548 return ApplyOrCall(cx, argc, vp, ApplyMode);
5549 }
5551 static bool
5552 DebuggerObject_call(JSContext *cx, unsigned argc, Value *vp)
5553 {
5554 return ApplyOrCall(cx, argc, vp, CallMode);
5555 }
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);
5563 RootedValue arg0(cx, args[0]);
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 }
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 }
5581 args.rval().set(arg0);
5582 return true;
5583 }
5585 static bool
5586 RequireGlobalObject(JSContext *cx, HandleValue dbgobj, HandleObject referent)
5587 {
5588 RootedObject obj(cx, referent);
5590 if (!obj->is<GlobalObject>()) {
5591 const char *isWrapper = "";
5592 const char *isWindowProxy = "";
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 }
5600 /* ... and WindowProxies around Windows. */
5601 if (IsOuterObject(obj)) {
5602 obj = JS_ObjectToInnerObject(cx, obj);
5603 isWindowProxy = "a WindowProxy referring to ";
5604 }
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 }
5618 return true;
5619 }
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;
5629 return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobal",
5630 args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue,
5631 args.get(1), args.rval(), dbg, referent, nullptr);
5632 }
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;
5642 return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobalWithBindings",
5643 args[0], EvalHasExtraBindings, args[1], args.get(2),
5644 args.rval(), dbg, referent, nullptr);
5645 }
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 }
5657 args.rval().setObject(*unwrapped);
5658 if (!dbg->wrapDebuggeeValue(cx, args.rval()))
5659 return false;
5660 return true;
5661 }
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;
5671 // Wrapping should outerize inner objects.
5672 JS_ASSERT(!IsInnerObject(&args.rval().toObject()));
5674 return true;
5675 }
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 };
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 };
5713 /*** Debugger.Environment ************************************************************************/
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 }
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 };
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 }
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 }
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 }
5779 return thisobj;
5780 }
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>())
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)
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 }
5803 static bool
5804 IsDeclarative(Env *env)
5805 {
5806 return env->is<DebugScopeObject>() && env->as<DebugScopeObject>().isForDeclarative();
5807 }
5809 static bool
5810 IsWith(Env *env)
5811 {
5812 return env->is<DebugScopeObject>() &&
5813 env->as<DebugScopeObject>().scope().is<DynamicWithObject>();
5814 }
5816 static bool
5817 DebuggerEnv_getType(JSContext *cx, unsigned argc, Value *vp)
5818 {
5819 THIS_DEBUGENV(cx, argc, vp, "get type", args, envobj, env);
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";
5830 JSAtom *str = Atomize(cx, s, strlen(s), InternAtom);
5831 if (!str)
5832 return false;
5833 args.rval().setString(str);
5834 return true;
5835 }
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);
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 }
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);
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 }
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 }
5869 args.rval().setObject(*obj);
5870 if (!dbg->wrapDebuggeeValue(cx, args.rval()))
5871 return false;
5872 return true;
5873 }
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);
5880 args.rval().setNull();
5882 if (!env->is<DebugScopeObject>())
5883 return true;
5885 JSObject &scope = env->as<DebugScopeObject>().scope();
5886 if (!scope.is<CallObject>())
5887 return true;
5889 CallObject &callobj = scope.as<CallObject>();
5890 if (callobj.isForEval())
5891 return true;
5893 args.rval().setObject(callobj.callee());
5894 if (!dbg->wrapDebuggeeValue(cx, args.rval()))
5895 return false;
5896 return true;
5897 }
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>());
5910 Debugger *dbg = Debugger::fromChildJSObject(envobj);
5912 args.rval().setBoolean(dbg->observesGlobal(&env->global()));
5913 return true;
5914 }
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);
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 }
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 }
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);
5953 RootedId id(cx);
5954 if (!ValueToIdentifier(cx, args[0], &id))
5955 return false;
5957 {
5958 Maybe<AutoCompartment> ac;
5959 ac.construct(cx, env);
5960 if (!cx->compartment()->wrapId(cx, id.address()))
5961 return false;
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 }
5975 return dbg->wrapEnvironment(cx, env, args.rval());
5976 }
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);
5984 RootedId id(cx);
5985 if (!ValueToIdentifier(cx, args[0], &id))
5986 return false;
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;
5995 /* This can trigger getters. */
5996 ErrorCopier ec(ac, dbg->toJSObject());
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 }
6011 if (!dbg->wrapDebuggeeValue(cx, &v))
6012 return false;
6013 args.rval().set(v);
6014 return true;
6015 }
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);
6023 RootedId id(cx);
6024 if (!ValueToIdentifier(cx, args[0], &id))
6025 return false;
6027 RootedValue v(cx, args[1]);
6028 if (!dbg->unwrapDebuggeeValue(cx, &v))
6029 return false;
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;
6037 /* This can trigger setters. */
6038 ErrorCopier ec(ac, dbg->toJSObject());
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 }
6049 /* Just set the property. */
6050 if (!JSObject::setGeneric(cx, env, env, id, &v, true))
6051 return false;
6052 }
6054 args.rval().setUndefined();
6055 return true;
6056 }
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 };
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 };
6077 /*** Glue ****************************************************************************************/
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;
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;
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;
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;
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;
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 }