1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "vm/Debugger-inl.h"
8
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/ScopeExit.h"
11 #include "mozilla/TypeTraits.h"
12
13 #include "jscntxt.h"
14 #include "jscompartment.h"
15 #include "jsfriendapi.h"
16 #include "jshashutil.h"
17 #include "jsnum.h"
18 #include "jsobj.h"
19 #include "jswrapper.h"
20
21 #include "frontend/BytecodeCompiler.h"
22 #include "gc/Marking.h"
23 #include "jit/BaselineDebugModeOSR.h"
24 #include "jit/BaselineJIT.h"
25 #include "jit/JSONSpewer.h"
26 #include "jit/MIRGraph.h"
27 #include "js/GCAPI.h"
28 #include "js/UbiNodeBreadthFirst.h"
29 #include "js/Vector.h"
30 #include "vm/ArgumentsObject.h"
31 #include "vm/DebuggerMemory.h"
32 #include "vm/SPSProfiler.h"
33 #include "vm/TraceLogging.h"
34 #include "vm/WrapperObject.h"
35
36 #include "jsgcinlines.h"
37 #include "jsobjinlines.h"
38 #include "jsopcodeinlines.h"
39 #include "jsscriptinlines.h"
40
41 #include "vm/NativeObject-inl.h"
42 #include "vm/Stack-inl.h"
43
44 using namespace js;
45
46 using JS::dbg::AutoEntryMonitor;
47 using JS::dbg::Builder;
48 using js::frontend::IsIdentifier;
49 using mozilla::ArrayLength;
50 using mozilla::DebugOnly;
51 using mozilla::MakeScopeExit;
52 using mozilla::Maybe;
53 using mozilla::UniquePtr;
54
55
56 /*** Forward declarations ************************************************************************/
57
58 extern const Class DebuggerFrame_class;
59
60 enum {
61 JSSLOT_DEBUGFRAME_OWNER,
62 JSSLOT_DEBUGFRAME_ARGUMENTS,
63 JSSLOT_DEBUGFRAME_ONSTEP_HANDLER,
64 JSSLOT_DEBUGFRAME_ONPOP_HANDLER,
65 JSSLOT_DEBUGFRAME_COUNT
66 };
67
68 extern const Class DebuggerArguments_class;
69
70 enum {
71 JSSLOT_DEBUGARGUMENTS_FRAME,
72 JSSLOT_DEBUGARGUMENTS_COUNT
73 };
74
75 extern const Class DebuggerEnv_class;
76
77 enum {
78 JSSLOT_DEBUGENV_OWNER,
79 JSSLOT_DEBUGENV_COUNT
80 };
81
82 extern const Class DebuggerObject_class;
83
84 enum {
85 JSSLOT_DEBUGOBJECT_OWNER,
86 JSSLOT_DEBUGOBJECT_COUNT
87 };
88
89 extern const Class DebuggerScript_class;
90
91 enum {
92 JSSLOT_DEBUGSCRIPT_OWNER,
93 JSSLOT_DEBUGSCRIPT_COUNT
94 };
95
96 extern const Class DebuggerSource_class;
97
98 enum {
99 JSSLOT_DEBUGSOURCE_OWNER,
100 JSSLOT_DEBUGSOURCE_TEXT,
101 JSSLOT_DEBUGSOURCE_COUNT
102 };
103
104 void DebuggerObject_trace(JSTracer* trc, JSObject* obj);
105 void DebuggerEnv_trace(JSTracer* trc, JSObject* obj);
106 void DebuggerScript_trace(JSTracer* trc, JSObject* obj);
107 void DebuggerSource_trace(JSTracer* trc, JSObject* obj);
108
109
110 /*** Utils ***************************************************************************************/
111
112 static inline bool
EnsureFunctionHasScript(JSContext * cx,HandleFunction fun)113 EnsureFunctionHasScript(JSContext* cx, HandleFunction fun)
114 {
115 if (fun->isInterpretedLazy()) {
116 AutoCompartment ac(cx, fun);
117 return !!fun->getOrCreateScript(cx);
118 }
119 return true;
120 }
121
122 static inline JSScript*
GetOrCreateFunctionScript(JSContext * cx,HandleFunction fun)123 GetOrCreateFunctionScript(JSContext* cx, HandleFunction fun)
124 {
125 MOZ_ASSERT(fun->isInterpreted());
126 if (!EnsureFunctionHasScript(cx, fun))
127 return nullptr;
128 return fun->nonLazyScript();
129 }
130
131 static bool
ValueToIdentifier(JSContext * cx,HandleValue v,MutableHandleId id)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 ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
139 JSDVG_SEARCH_STACK, val, nullptr, "not an identifier",
140 nullptr);
141 return false;
142 }
143 return true;
144 }
145
146 /*
147 * A range of all the Debugger.Frame objects for a particular AbstractFramePtr.
148 *
149 * FIXME This checks only current debuggers, so it relies on a hack in
150 * Debugger::removeDebuggeeGlobal to make sure only current debuggers
151 * have Frame objects with .live === true.
152 */
153 class Debugger::FrameRange
154 {
155 AbstractFramePtr frame;
156
157 /* The debuggers in |fp|'s compartment, or nullptr if there are none. */
158 GlobalObject::DebuggerVector* debuggers;
159
160 /*
161 * The index of the front Debugger.Frame's debugger in debuggers.
162 * nextDebugger < debuggerCount if and only if the range is not empty.
163 */
164 size_t debuggerCount, nextDebugger;
165
166 /*
167 * If the range is not empty, this is front Debugger.Frame's entry in its
168 * debugger's frame table.
169 */
170 FrameMap::Ptr entry;
171
172 public:
173 /*
174 * Return a range containing all Debugger.Frame instances referring to
175 * |fp|. |global| is |fp|'s global object; if nullptr or omitted, we
176 * compute it ourselves from |fp|.
177 *
178 * We keep an index into the compartment's debugger list, and a
179 * FrameMap::Ptr into the current debugger's frame map. Thus, if the set of
180 * debuggers in |fp|'s compartment changes, this range becomes invalid.
181 * Similarly, if stack frames are added to or removed from frontDebugger(),
182 * then the range's front is invalid until popFront is called.
183 */
FrameRange(AbstractFramePtr frame,GlobalObject * global=nullptr)184 explicit FrameRange(AbstractFramePtr frame, GlobalObject* global = nullptr)
185 : frame(frame)
186 {
187 nextDebugger = 0;
188
189 /* Find our global, if we were not given one. */
190 if (!global)
191 global = &frame.script()->global();
192
193 /* The frame and global must match. */
194 MOZ_ASSERT(&frame.script()->global() == global);
195
196 /* Find the list of debuggers we'll iterate over. There may be none. */
197 debuggers = global->getDebuggers();
198 if (debuggers) {
199 debuggerCount = debuggers->length();
200 findNext();
201 } else {
202 debuggerCount = 0;
203 }
204 }
205
empty() const206 bool empty() const {
207 return nextDebugger >= debuggerCount;
208 }
209
frontFrame() const210 NativeObject* frontFrame() const {
211 MOZ_ASSERT(!empty());
212 return entry->value();
213 }
214
frontDebugger() const215 Debugger* frontDebugger() const {
216 MOZ_ASSERT(!empty());
217 return (*debuggers)[nextDebugger];
218 }
219
220 /*
221 * Delete the front frame from its Debugger's frame map. After this call,
222 * the range's front is invalid until popFront is called.
223 */
removeFrontFrame() const224 void removeFrontFrame() const {
225 MOZ_ASSERT(!empty());
226 frontDebugger()->frames.remove(entry);
227 }
228
popFront()229 void popFront() {
230 MOZ_ASSERT(!empty());
231 nextDebugger++;
232 findNext();
233 }
234
235 private:
236 /*
237 * Either make this range refer to the first appropriate Debugger.Frame at
238 * or after nextDebugger, or make it empty.
239 */
findNext()240 void findNext() {
241 while (!empty()) {
242 Debugger* dbg = (*debuggers)[nextDebugger];
243 entry = dbg->frames.lookup(frame);
244 if (entry)
245 break;
246 nextDebugger++;
247 }
248 }
249 };
250
251
252 /*** Breakpoints *********************************************************************************/
253
BreakpointSite(JSScript * script,jsbytecode * pc)254 BreakpointSite::BreakpointSite(JSScript* script, jsbytecode* pc)
255 : script(script), pc(pc), enabledCount(0)
256 {
257 MOZ_ASSERT(!script->hasBreakpointsAt(pc));
258 JS_INIT_CLIST(&breakpoints);
259 }
260
261 void
recompile(FreeOp * fop)262 BreakpointSite::recompile(FreeOp* fop)
263 {
264 if (script->hasBaselineScript())
265 script->baselineScript()->toggleDebugTraps(script, pc);
266 }
267
268 void
inc(FreeOp * fop)269 BreakpointSite::inc(FreeOp* fop)
270 {
271 enabledCount++;
272 if (enabledCount == 1)
273 recompile(fop);
274 }
275
276 void
dec(FreeOp * fop)277 BreakpointSite::dec(FreeOp* fop)
278 {
279 MOZ_ASSERT(enabledCount > 0);
280 enabledCount--;
281 if (enabledCount == 0)
282 recompile(fop);
283 }
284
285 void
destroyIfEmpty(FreeOp * fop)286 BreakpointSite::destroyIfEmpty(FreeOp* fop)
287 {
288 if (JS_CLIST_IS_EMPTY(&breakpoints))
289 script->destroyBreakpointSite(fop, pc);
290 }
291
292 Breakpoint*
firstBreakpoint() const293 BreakpointSite::firstBreakpoint() const
294 {
295 if (JS_CLIST_IS_EMPTY(&breakpoints))
296 return nullptr;
297 return Breakpoint::fromSiteLinks(JS_NEXT_LINK(&breakpoints));
298 }
299
300 bool
hasBreakpoint(Breakpoint * bp)301 BreakpointSite::hasBreakpoint(Breakpoint* bp)
302 {
303 for (Breakpoint* p = firstBreakpoint(); p; p = p->nextInSite())
304 if (p == bp)
305 return true;
306 return false;
307 }
308
Breakpoint(Debugger * debugger,BreakpointSite * site,JSObject * handler)309 Breakpoint::Breakpoint(Debugger* debugger, BreakpointSite* site, JSObject* handler)
310 : debugger(debugger), site(site), handler(handler)
311 {
312 MOZ_ASSERT(handler->compartment() == debugger->object->compartment());
313 JS_APPEND_LINK(&debuggerLinks, &debugger->breakpoints);
314 JS_APPEND_LINK(&siteLinks, &site->breakpoints);
315 }
316
317 Breakpoint*
fromDebuggerLinks(JSCList * links)318 Breakpoint::fromDebuggerLinks(JSCList* links)
319 {
320 return (Breakpoint*) ((unsigned char*) links - offsetof(Breakpoint, debuggerLinks));
321 }
322
323 Breakpoint*
fromSiteLinks(JSCList * links)324 Breakpoint::fromSiteLinks(JSCList* links)
325 {
326 return (Breakpoint*) ((unsigned char*) links - offsetof(Breakpoint, siteLinks));
327 }
328
329 void
destroy(FreeOp * fop)330 Breakpoint::destroy(FreeOp* fop)
331 {
332 if (debugger->enabled)
333 site->dec(fop);
334 JS_REMOVE_LINK(&debuggerLinks);
335 JS_REMOVE_LINK(&siteLinks);
336 site->destroyIfEmpty(fop);
337 fop->delete_(this);
338 }
339
340 Breakpoint*
nextInDebugger()341 Breakpoint::nextInDebugger()
342 {
343 JSCList* link = JS_NEXT_LINK(&debuggerLinks);
344 return (link == &debugger->breakpoints) ? nullptr : fromDebuggerLinks(link);
345 }
346
347 Breakpoint*
nextInSite()348 Breakpoint::nextInSite()
349 {
350 JSCList* link = JS_NEXT_LINK(&siteLinks);
351 return (link == &site->breakpoints) ? nullptr : fromSiteLinks(link);
352 }
353
354
355 /*** Debugger hook dispatch **********************************************************************/
356
Debugger(JSContext * cx,NativeObject * dbg)357 Debugger::Debugger(JSContext* cx, NativeObject* dbg)
358 : object(dbg),
359 uncaughtExceptionHook(nullptr),
360 enabled(true),
361 allowUnobservedAsmJS(false),
362 collectCoverageInfo(false),
363 observedGCs(cx),
364 tenurePromotionsLog(cx),
365 trackingTenurePromotions(false),
366 maxTenurePromotionsLogLength(DEFAULT_MAX_LOG_LENGTH),
367 tenurePromotionsLogOverflowed(false),
368 allocationsLog(cx),
369 trackingAllocationSites(false),
370 allocationSamplingProbability(1.0),
371 maxAllocationsLogLength(DEFAULT_MAX_LOG_LENGTH),
372 allocationsLogOverflowed(false),
373 frames(cx->runtime()),
374 scripts(cx),
375 sources(cx),
376 objects(cx),
377 environments(cx),
378 #ifdef NIGHTLY_BUILD
379 traceLoggerLastDrainedSize(0),
380 traceLoggerLastDrainedIteration(0),
381 #endif
382 traceLoggerScriptedCallsLastDrainedSize(0),
383 traceLoggerScriptedCallsLastDrainedIteration(0)
384 {
385 assertSameCompartment(cx, dbg);
386
387 JS_INIT_CLIST(&breakpoints);
388 JS_INIT_CLIST(&onNewGlobalObjectWatchersLink);
389 }
390
~Debugger()391 Debugger::~Debugger()
392 {
393 MOZ_ASSERT_IF(debuggees.initialized(), debuggees.empty());
394 allocationsLog.clear();
395 tenurePromotionsLog.clear();
396
397 /*
398 * Since the inactive state for this link is a singleton cycle, it's always
399 * safe to apply JS_REMOVE_LINK to it, regardless of whether we're in the list or not.
400 *
401 * We don't have to worry about locking here since Debugger is not
402 * background finalized.
403 */
404 JS_REMOVE_LINK(&onNewGlobalObjectWatchersLink);
405 }
406
407 bool
init(JSContext * cx)408 Debugger::init(JSContext* cx)
409 {
410 if (!debuggees.init() ||
411 !debuggeeZones.init() ||
412 !frames.init() ||
413 !scripts.init() ||
414 !sources.init() ||
415 !objects.init() ||
416 !observedGCs.init() ||
417 !environments.init())
418 {
419 ReportOutOfMemory(cx);
420 return false;
421 }
422
423 cx->runtime()->debuggerList.insertBack(this);
424 return true;
425 }
426
427 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSCRIPT_OWNER));
428 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSOURCE_OWNER));
429 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGOBJECT_OWNER));
430 JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGENV_OWNER));
431
432 /* static */ Debugger*
fromChildJSObject(JSObject * obj)433 Debugger::fromChildJSObject(JSObject* obj)
434 {
435 MOZ_ASSERT(obj->getClass() == &DebuggerFrame_class ||
436 obj->getClass() == &DebuggerScript_class ||
437 obj->getClass() == &DebuggerSource_class ||
438 obj->getClass() == &DebuggerObject_class ||
439 obj->getClass() == &DebuggerEnv_class);
440 JSObject* dbgobj = &obj->as<NativeObject>().getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER).toObject();
441 return fromJSObject(dbgobj);
442 }
443
444 bool
hasMemory() const445 Debugger::hasMemory() const
446 {
447 return object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE).isObject();
448 }
449
450 DebuggerMemory&
memory() const451 Debugger::memory() const
452 {
453 MOZ_ASSERT(hasMemory());
454 return object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE).toObject().as<DebuggerMemory>();
455 }
456
457 bool
getScriptFrameWithIter(JSContext * cx,AbstractFramePtr frame,const ScriptFrameIter * maybeIter,MutableHandleValue vp)458 Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
459 const ScriptFrameIter* maybeIter, MutableHandleValue vp)
460 {
461 MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == frame);
462 MOZ_ASSERT(!frame.script()->selfHosted());
463
464 FrameMap::AddPtr p = frames.lookupForAdd(frame);
465 if (!p) {
466 /* Create and populate the Debugger.Frame object. */
467 RootedObject proto(cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
468 RootedNativeObject frameobj(cx, NewNativeObjectWithGivenProto(cx, &DebuggerFrame_class,
469 proto));
470 if (!frameobj)
471 return false;
472
473 // Eagerly copy ScriptFrameIter data if we've already walked the
474 // stack.
475 if (maybeIter) {
476 AbstractFramePtr data = maybeIter->copyDataAsAbstractFramePtr();
477 if (!data)
478 return false;
479 frameobj->setPrivate(data.raw());
480 } else {
481 frameobj->setPrivate(frame.raw());
482 }
483
484 frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*object));
485
486 if (!ensureExecutionObservabilityOfFrame(cx, frame))
487 return false;
488
489 if (!frames.add(p, frame, frameobj)) {
490 ReportOutOfMemory(cx);
491 return false;
492 }
493 }
494 vp.setObject(*p->value());
495 return true;
496 }
497
498 /* static */ bool
hasLiveHook(GlobalObject * global,Hook which)499 Debugger::hasLiveHook(GlobalObject* global, Hook which)
500 {
501 if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
502 for (Debugger** p = debuggers->begin(); p != debuggers->end(); p++) {
503 Debugger* dbg = *p;
504 if (dbg->enabled && dbg->getHook(which))
505 return true;
506 }
507 }
508 return false;
509 }
510
511 JSObject*
getHook(Hook hook) const512 Debugger::getHook(Hook hook) const
513 {
514 MOZ_ASSERT(hook >= 0 && hook < HookCount);
515 const Value& v = object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + hook);
516 return v.isUndefined() ? nullptr : &v.toObject();
517 }
518
519 bool
hasAnyLiveHooks(JSRuntime * rt) const520 Debugger::hasAnyLiveHooks(JSRuntime* rt) const
521 {
522 if (!enabled)
523 return false;
524
525 if (getHook(OnDebuggerStatement) ||
526 getHook(OnExceptionUnwind) ||
527 getHook(OnNewScript) ||
528 getHook(OnEnterFrame))
529 {
530 return true;
531 }
532
533 /* If any breakpoints are in live scripts, return true. */
534 for (Breakpoint* bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
535 if (IsMarkedUnbarriered(rt, &bp->site->script))
536 return true;
537 }
538
539 for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
540 NativeObject* frameObj = r.front().value();
541 if (!frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() ||
542 !frameObj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined())
543 return true;
544 }
545
546 return false;
547 }
548
549 /* static */ JSTrapStatus
slowPathOnEnterFrame(JSContext * cx,AbstractFramePtr frame)550 Debugger::slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame)
551 {
552 RootedValue rval(cx);
553 JSTrapStatus status = dispatchHook(
554 cx,
555 [frame](Debugger* dbg) -> bool {
556 return dbg->observesFrame(frame) && dbg->observesEnterFrame();
557 },
558 [&](Debugger* dbg) -> JSTrapStatus {
559 return dbg->fireEnterFrame(cx, frame, &rval);
560 });
561
562 switch (status) {
563 case JSTRAP_CONTINUE:
564 break;
565
566 case JSTRAP_THROW:
567 cx->setPendingException(rval);
568 break;
569
570 case JSTRAP_ERROR:
571 cx->clearPendingException();
572 break;
573
574 case JSTRAP_RETURN:
575 frame.setReturnValue(rval);
576 break;
577
578 default:
579 MOZ_CRASH("bad Debugger::onEnterFrame JSTrapStatus value");
580 }
581
582 return status;
583 }
584
585 static void
586 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp* fop, AbstractFramePtr frame,
587 NativeObject* frameobj);
588
589 static void
590 DebuggerFrame_freeScriptFrameIterData(FreeOp* fop, JSObject* obj);
591
592 /*
593 * Handle leaving a frame with debuggers watching. |frameOk| indicates whether
594 * the frame is exiting normally or abruptly. Set |cx|'s exception and/or
595 * |cx->fp()|'s return value, and return a new success value.
596 */
597 /* static */ bool
slowPathOnLeaveFrame(JSContext * cx,AbstractFramePtr frame,bool frameOk)598 Debugger::slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, bool frameOk)
599 {
600 Handle<GlobalObject*> global = cx->global();
601
602 // The onPop handler and associated clean up logic should not run multiple
603 // times on the same frame. If slowPathOnLeaveFrame has already been
604 // called, the frame will not be present in the Debugger frame maps.
605 FrameRange frameRange(frame, global);
606 if (frameRange.empty())
607 return frameOk;
608
609 auto frameMapsGuard = MakeScopeExit([&] {
610 // Clean up all Debugger.Frame instances. This call creates a fresh
611 // FrameRange, as one debugger's onPop handler could have caused another
612 // debugger to create its own Debugger.Frame instance.
613 removeFromFrameMapsAndClearBreakpointsIn(cx, frame);
614 });
615
616 /* Save the frame's completion value. */
617 JSTrapStatus status;
618 RootedValue value(cx);
619 Debugger::resultToCompletion(cx, frameOk, frame.returnValue(), &status, &value);
620
621 // This path can be hit via unwinding the stack due to over-recursion or
622 // OOM. In those cases, don't fire the frames' onPop handlers, because
623 // invoking JS will only trigger the same condition. See
624 // slowPathOnExceptionUnwind.
625 if (!cx->isThrowingOverRecursed() && !cx->isThrowingOutOfMemory()) {
626 /* Build a list of the recipients. */
627 AutoObjectVector frames(cx);
628 for (; !frameRange.empty(); frameRange.popFront()) {
629 if (!frames.append(frameRange.frontFrame())) {
630 cx->clearPendingException();
631 return false;
632 }
633 }
634
635 /* For each Debugger.Frame, fire its onPop handler, if any. */
636 for (JSObject** p = frames.begin(); p != frames.end(); p++) {
637 RootedNativeObject frameobj(cx, &(*p)->as<NativeObject>());
638 Debugger* dbg = Debugger::fromChildJSObject(frameobj);
639
640 if (dbg->enabled &&
641 !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined()) {
642 RootedValue handler(cx, frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER));
643
644 Maybe<AutoCompartment> ac;
645 ac.emplace(cx, dbg->object);
646
647 RootedValue completion(cx);
648 if (!dbg->newCompletionValue(cx, status, value, &completion)) {
649 status = dbg->handleUncaughtException(ac, false);
650 break;
651 }
652
653 /* Call the onPop handler. */
654 RootedValue rval(cx);
655 bool hookOk = Invoke(cx, ObjectValue(*frameobj), handler, 1, completion.address(),
656 &rval);
657 RootedValue nextValue(cx);
658 JSTrapStatus nextStatus = dbg->parseResumptionValue(ac, hookOk, rval, &nextValue);
659
660 /*
661 * At this point, we are back in the debuggee compartment, and any error has
662 * been wrapped up as a completion value.
663 */
664 MOZ_ASSERT(cx->compartment() == global->compartment());
665 MOZ_ASSERT(!cx->isExceptionPending());
666
667 /* JSTRAP_CONTINUE means "make no change". */
668 if (nextStatus != JSTRAP_CONTINUE) {
669 status = nextStatus;
670 value = nextValue;
671 }
672 }
673 }
674 }
675
676 /* Establish (status, value) as our resumption value. */
677 switch (status) {
678 case JSTRAP_RETURN:
679 frame.setReturnValue(value);
680 return true;
681
682 case JSTRAP_THROW:
683 cx->setPendingException(value);
684 return false;
685
686 case JSTRAP_ERROR:
687 MOZ_ASSERT(!cx->isExceptionPending());
688 return false;
689
690 default:
691 MOZ_CRASH("bad final trap status");
692 }
693 }
694
695 /* static */ JSTrapStatus
slowPathOnDebuggerStatement(JSContext * cx,AbstractFramePtr frame)696 Debugger::slowPathOnDebuggerStatement(JSContext* cx, AbstractFramePtr frame)
697 {
698 RootedValue rval(cx);
699 JSTrapStatus status = dispatchHook(
700 cx,
701 [](Debugger* dbg) -> bool { return dbg->getHook(OnDebuggerStatement); },
702 [&](Debugger* dbg) -> JSTrapStatus {
703 return dbg->fireDebuggerStatement(cx, &rval);
704 });
705
706 switch (status) {
707 case JSTRAP_CONTINUE:
708 case JSTRAP_ERROR:
709 break;
710
711 case JSTRAP_RETURN:
712 frame.setReturnValue(rval);
713 break;
714
715 case JSTRAP_THROW:
716 cx->setPendingException(rval);
717 break;
718
719 default:
720 MOZ_CRASH("Invalid onDebuggerStatement trap status");
721 }
722
723 return status;
724 }
725
726 /* static */ JSTrapStatus
slowPathOnExceptionUnwind(JSContext * cx,AbstractFramePtr frame)727 Debugger::slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame)
728 {
729 // Invoking more JS on an over-recursed stack or after OOM is only going
730 // to result in more of the same error.
731 if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory())
732 return JSTRAP_CONTINUE;
733
734 // The Debugger API mustn't muck with frames from self-hosted scripts.
735 if (frame.script()->selfHosted())
736 return JSTRAP_CONTINUE;
737
738 RootedValue rval(cx);
739 JSTrapStatus status = dispatchHook(
740 cx,
741 [](Debugger* dbg) -> bool { return dbg->getHook(OnExceptionUnwind); },
742 [&](Debugger* dbg) -> JSTrapStatus {
743 return dbg->fireExceptionUnwind(cx, &rval);
744 });
745
746 switch (status) {
747 case JSTRAP_CONTINUE:
748 break;
749
750 case JSTRAP_THROW:
751 cx->setPendingException(rval);
752 break;
753
754 case JSTRAP_ERROR:
755 cx->clearPendingException();
756 break;
757
758 case JSTRAP_RETURN:
759 cx->clearPendingException();
760 frame.setReturnValue(rval);
761 break;
762
763 default:
764 MOZ_CRASH("Invalid onExceptionUnwind trap status");
765 }
766
767 return status;
768 }
769
770 bool
wrapEnvironment(JSContext * cx,Handle<Env * > env,MutableHandleValue rval)771 Debugger::wrapEnvironment(JSContext* cx, Handle<Env*> env, MutableHandleValue rval)
772 {
773 if (!env) {
774 rval.setNull();
775 return true;
776 }
777
778 /*
779 * DebuggerEnv should only wrap a debug scope chain obtained (transitively)
780 * from GetDebugScopeFor(Frame|Function).
781 */
782 MOZ_ASSERT(!IsSyntacticScope(env));
783
784 NativeObject* envobj;
785 DependentAddPtr<ObjectWeakMap> p(cx, environments, env);
786 if (p) {
787 envobj = &p->value()->as<NativeObject>();
788 } else {
789 /* Create a new Debugger.Environment for env. */
790 RootedObject proto(cx, &object->getReservedSlot(JSSLOT_DEBUG_ENV_PROTO).toObject());
791 envobj = NewNativeObjectWithGivenProto(cx, &DebuggerEnv_class, proto,
792 TenuredObject);
793 if (!envobj)
794 return false;
795 envobj->setPrivateGCThing(env);
796 envobj->setReservedSlot(JSSLOT_DEBUGENV_OWNER, ObjectValue(*object));
797 if (!p.add(cx, environments, env, envobj))
798 return false;
799
800 CrossCompartmentKey key(CrossCompartmentKey::DebuggerEnvironment, object, env);
801 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) {
802 environments.remove(env);
803 ReportOutOfMemory(cx);
804 return false;
805 }
806 }
807 rval.setObject(*envobj);
808 return true;
809 }
810
811 bool
wrapDebuggeeValue(JSContext * cx,MutableHandleValue vp)812 Debugger::wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp)
813 {
814 assertSameCompartment(cx, object.get());
815
816 if (vp.isObject()) {
817 RootedObject obj(cx, &vp.toObject());
818
819 if (obj->is<JSFunction>()) {
820 MOZ_ASSERT(!IsInternalFunctionObject(*obj));
821 RootedFunction fun(cx, &obj->as<JSFunction>());
822 if (!EnsureFunctionHasScript(cx, fun))
823 return false;
824 }
825
826 DependentAddPtr<ObjectWeakMap> p(cx, objects, obj);
827 if (p) {
828 vp.setObject(*p->value());
829 } else {
830 /* Create a new Debugger.Object for obj. */
831 RootedObject proto(cx, &object->getReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO).toObject());
832 NativeObject* dobj =
833 NewNativeObjectWithGivenProto(cx, &DebuggerObject_class, proto,
834 TenuredObject);
835 if (!dobj)
836 return false;
837 dobj->setPrivateGCThing(obj);
838 dobj->setReservedSlot(JSSLOT_DEBUGOBJECT_OWNER, ObjectValue(*object));
839
840 if (!p.add(cx, objects, obj, dobj))
841 return false;
842
843 if (obj->compartment() != object->compartment()) {
844 CrossCompartmentKey key(CrossCompartmentKey::DebuggerObject, object, obj);
845 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) {
846 objects.remove(obj);
847 ReportOutOfMemory(cx);
848 return false;
849 }
850 }
851
852 vp.setObject(*dobj);
853 }
854 } else if (vp.isMagic()) {
855 RootedPlainObject optObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
856 if (!optObj)
857 return false;
858
859 // We handle three sentinel values: missing arguments (overloading
860 // JS_OPTIMIZED_ARGUMENTS), optimized out slots (JS_OPTIMIZED_OUT),
861 // and uninitialized bindings (JS_UNINITIALIZED_LEXICAL).
862 //
863 // Other magic values should not have escaped.
864 PropertyName* name;
865 switch (vp.whyMagic()) {
866 case JS_OPTIMIZED_ARGUMENTS: name = cx->names().missingArguments; break;
867 case JS_OPTIMIZED_OUT: name = cx->names().optimizedOut; break;
868 case JS_UNINITIALIZED_LEXICAL: name = cx->names().uninitialized; break;
869 default: MOZ_CRASH("Unsupported magic value escaped to Debugger");
870 }
871
872 RootedValue trueVal(cx, BooleanValue(true));
873 if (!DefineProperty(cx, optObj, name, trueVal))
874 return false;
875
876 vp.setObject(*optObj);
877 } else if (!cx->compartment()->wrap(cx, vp)) {
878 vp.setUndefined();
879 return false;
880 }
881
882 return true;
883 }
884
885 bool
unwrapDebuggeeObject(JSContext * cx,MutableHandleObject obj)886 Debugger::unwrapDebuggeeObject(JSContext* cx, MutableHandleObject obj)
887 {
888 if (obj->getClass() != &DebuggerObject_class) {
889 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
890 "Debugger", "Debugger.Object", obj->getClass()->name);
891 return false;
892 }
893 NativeObject* ndobj = &obj->as<NativeObject>();
894
895 Value owner = ndobj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER);
896 if (owner.isUndefined() || &owner.toObject() != object) {
897 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
898 owner.isUndefined()
899 ? JSMSG_DEBUG_OBJECT_PROTO
900 : JSMSG_DEBUG_OBJECT_WRONG_OWNER);
901 return false;
902 }
903
904 obj.set(static_cast<JSObject*>(ndobj->getPrivate()));
905 return true;
906 }
907
908 bool
unwrapDebuggeeValue(JSContext * cx,MutableHandleValue vp)909 Debugger::unwrapDebuggeeValue(JSContext* cx, MutableHandleValue vp)
910 {
911 assertSameCompartment(cx, object.get(), vp);
912 if (vp.isObject()) {
913 RootedObject dobj(cx, &vp.toObject());
914 if (!unwrapDebuggeeObject(cx, &dobj))
915 return false;
916 vp.setObject(*dobj);
917 }
918 return true;
919 }
920
921 static bool
CheckArgCompartment(JSContext * cx,JSObject * obj,JSObject * arg,const char * methodname,const char * propname)922 CheckArgCompartment(JSContext* cx, JSObject* obj, JSObject* arg,
923 const char* methodname, const char* propname)
924 {
925 if (arg->compartment() != obj->compartment()) {
926 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_COMPARTMENT_MISMATCH,
927 methodname, propname);
928 return false;
929 }
930 return true;
931 }
932
933 static bool
CheckArgCompartment(JSContext * cx,JSObject * obj,HandleValue v,const char * methodname,const char * propname)934 CheckArgCompartment(JSContext* cx, JSObject* obj, HandleValue v,
935 const char* methodname, const char* propname)
936 {
937 if (v.isObject())
938 return CheckArgCompartment(cx, obj, &v.toObject(), methodname, propname);
939 return true;
940 }
941
942 bool
unwrapPropertyDescriptor(JSContext * cx,HandleObject obj,MutableHandle<PropertyDescriptor> desc)943 Debugger::unwrapPropertyDescriptor(JSContext* cx, HandleObject obj,
944 MutableHandle<PropertyDescriptor> desc)
945 {
946 if (desc.hasValue()) {
947 RootedValue value(cx, desc.value());
948 if (!unwrapDebuggeeValue(cx, &value) ||
949 !CheckArgCompartment(cx, obj, value, "defineProperty", "value"))
950 {
951 return false;
952 }
953 desc.setValue(value);
954 }
955
956 if (desc.hasGetterObject()) {
957 RootedObject get(cx, desc.getterObject());
958 if (get) {
959 if (!unwrapDebuggeeObject(cx, &get))
960 return false;
961 if (!CheckArgCompartment(cx, obj, get, "defineProperty", "get"))
962 return false;
963 }
964 desc.setGetterObject(get);
965 }
966
967 if (desc.hasSetterObject()) {
968 RootedObject set(cx, desc.setterObject());
969 if (set) {
970 if (!unwrapDebuggeeObject(cx, &set))
971 return false;
972 if (!CheckArgCompartment(cx, obj, set, "defineProperty", "set"))
973 return false;
974 }
975 desc.setSetterObject(set);
976 }
977
978 return true;
979 }
980
981 namespace {
982 class MOZ_STACK_CLASS ReportExceptionClosure : public ScriptEnvironmentPreparer::Closure
983 {
984 public:
ReportExceptionClosure(RootedValue & exn)985 explicit ReportExceptionClosure(RootedValue& exn)
986 : exn_(exn)
987 {
988 }
989
operator ()(JSContext * cx)990 bool operator()(JSContext* cx) override
991 {
992 cx->setPendingException(exn_);
993 return false;
994 }
995
996 private:
997 RootedValue& exn_;
998 };
999 } // anonymous namespace
1000
1001 JSTrapStatus
handleUncaughtExceptionHelper(Maybe<AutoCompartment> & ac,MutableHandleValue * vp,bool callHook)1002 Debugger::handleUncaughtExceptionHelper(Maybe<AutoCompartment>& ac,
1003 MutableHandleValue* vp, bool callHook)
1004 {
1005 JSContext* cx = ac->context()->asJSContext();
1006 if (cx->isExceptionPending()) {
1007 if (callHook && uncaughtExceptionHook) {
1008 RootedValue exc(cx);
1009 if (!cx->getPendingException(&exc))
1010 return JSTRAP_ERROR;
1011 cx->clearPendingException();
1012 RootedValue fval(cx, ObjectValue(*uncaughtExceptionHook));
1013 RootedValue rv(cx);
1014 if (Invoke(cx, ObjectValue(*object), fval, 1, exc.address(), &rv))
1015 return vp ? parseResumptionValue(ac, true, rv, *vp, false) : JSTRAP_CONTINUE;
1016 }
1017
1018 if (cx->isExceptionPending()) {
1019 /*
1020 * We want to report the pending exception, but we want to let the
1021 * embedding handle it however it wants to. So pretend like we're
1022 * starting a new script execution on our current compartment (which
1023 * is the debugger compartment, so reported errors won't get
1024 * reported to various onerror handlers in debuggees) and as part of
1025 * that "execution" simply throw our exception so the embedding can
1026 * deal.
1027 */
1028 RootedValue exn(cx);
1029 if (cx->getPendingException(&exn)) {
1030 /*
1031 * Clear the exception, because
1032 * PrepareScriptEnvironmentAndInvoke will assert that we don't
1033 * have one.
1034 */
1035 cx->clearPendingException();
1036 ReportExceptionClosure reportExn(exn);
1037 PrepareScriptEnvironmentAndInvoke(cx, cx->global(), reportExn);
1038 }
1039 /*
1040 * And if not, or if PrepareScriptEnvironmentAndInvoke somehow left
1041 * an exception on cx (which it totally shouldn't do), just give
1042 * up.
1043 */
1044 cx->clearPendingException();
1045 }
1046 }
1047 ac.reset();
1048 return JSTRAP_ERROR;
1049 }
1050
1051 JSTrapStatus
handleUncaughtException(Maybe<AutoCompartment> & ac,MutableHandleValue vp,bool callHook)1052 Debugger::handleUncaughtException(Maybe<AutoCompartment>& ac, MutableHandleValue vp, bool callHook)
1053 {
1054 return handleUncaughtExceptionHelper(ac, &vp, callHook);
1055 }
1056
1057 JSTrapStatus
handleUncaughtException(Maybe<AutoCompartment> & ac,bool callHook)1058 Debugger::handleUncaughtException(Maybe<AutoCompartment>& ac, bool callHook)
1059 {
1060 return handleUncaughtExceptionHelper(ac, nullptr, callHook);
1061 }
1062
1063 /* static */ void
resultToCompletion(JSContext * cx,bool ok,const Value & rv,JSTrapStatus * status,MutableHandleValue value)1064 Debugger::resultToCompletion(JSContext* cx, bool ok, const Value& rv,
1065 JSTrapStatus* status, MutableHandleValue value)
1066 {
1067 MOZ_ASSERT_IF(ok, !cx->isExceptionPending());
1068
1069 if (ok) {
1070 *status = JSTRAP_RETURN;
1071 value.set(rv);
1072 } else if (cx->isExceptionPending()) {
1073 *status = JSTRAP_THROW;
1074 if (!cx->getPendingException(value))
1075 *status = JSTRAP_ERROR;
1076 cx->clearPendingException();
1077 } else {
1078 *status = JSTRAP_ERROR;
1079 value.setUndefined();
1080 }
1081 }
1082
1083 bool
newCompletionValue(JSContext * cx,JSTrapStatus status,Value value_,MutableHandleValue result)1084 Debugger::newCompletionValue(JSContext* cx, JSTrapStatus status, Value value_,
1085 MutableHandleValue result)
1086 {
1087 /*
1088 * We must be in the debugger's compartment, since that's where we want
1089 * to construct the completion value.
1090 */
1091 assertSameCompartment(cx, object.get());
1092
1093 RootedId key(cx);
1094 RootedValue value(cx, value_);
1095
1096 switch (status) {
1097 case JSTRAP_RETURN:
1098 key = NameToId(cx->names().return_);
1099 break;
1100
1101 case JSTRAP_THROW:
1102 key = NameToId(cx->names().throw_);
1103 break;
1104
1105 case JSTRAP_ERROR:
1106 result.setNull();
1107 return true;
1108
1109 default:
1110 MOZ_CRASH("bad status passed to Debugger::newCompletionValue");
1111 }
1112
1113 /* Common tail for JSTRAP_RETURN and JSTRAP_THROW. */
1114 RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
1115 if (!obj ||
1116 !wrapDebuggeeValue(cx, &value) ||
1117 !NativeDefineProperty(cx, obj, key, value, nullptr, nullptr, JSPROP_ENUMERATE))
1118 {
1119 return false;
1120 }
1121
1122 result.setObject(*obj);
1123 return true;
1124 }
1125
1126 bool
receiveCompletionValue(Maybe<AutoCompartment> & ac,bool ok,HandleValue val,MutableHandleValue vp)1127 Debugger::receiveCompletionValue(Maybe<AutoCompartment>& ac, bool ok,
1128 HandleValue val,
1129 MutableHandleValue vp)
1130 {
1131 JSContext* cx = ac->context()->asJSContext();
1132
1133 JSTrapStatus status;
1134 RootedValue value(cx);
1135 resultToCompletion(cx, ok, val, &status, &value);
1136 ac.reset();
1137 return newCompletionValue(cx, status, value, vp);
1138 }
1139
1140 static bool
GetStatusProperty(JSContext * cx,HandleObject obj,HandlePropertyName name,JSTrapStatus status,JSTrapStatus * statusOut,MutableHandleValue vp,int * hits)1141 GetStatusProperty(JSContext* cx, HandleObject obj, HandlePropertyName name, JSTrapStatus status,
1142 JSTrapStatus* statusOut, MutableHandleValue vp, int* hits)
1143 {
1144 bool found;
1145 if (!HasProperty(cx, obj, name, &found))
1146 return false;
1147 if (found) {
1148 ++*hits;
1149 *statusOut = status;
1150 if (!GetProperty(cx, obj, obj, name, vp))
1151 return false;
1152 }
1153 return true;
1154 }
1155
1156 static bool
ParseResumptionValueAsObject(JSContext * cx,HandleValue rv,JSTrapStatus * statusp,MutableHandleValue vp)1157 ParseResumptionValueAsObject(JSContext* cx, HandleValue rv, JSTrapStatus* statusp,
1158 MutableHandleValue vp)
1159 {
1160 int hits = 0;
1161 if (rv.isObject()) {
1162 RootedObject obj(cx, &rv.toObject());
1163 if (!GetStatusProperty(cx, obj, cx->names().return_, JSTRAP_RETURN, statusp, vp, &hits))
1164 return false;
1165 if (!GetStatusProperty(cx, obj, cx->names().throw_, JSTRAP_THROW, statusp, vp, &hits))
1166 return false;
1167 }
1168
1169 if (hits != 1) {
1170 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_RESUMPTION);
1171 return false;
1172 }
1173 return true;
1174 }
1175
1176 JSTrapStatus
parseResumptionValue(Maybe<AutoCompartment> & ac,bool ok,const Value & rv,MutableHandleValue vp,bool callHook)1177 Debugger::parseResumptionValue(Maybe<AutoCompartment>& ac, bool ok, const Value& rv, MutableHandleValue vp,
1178 bool callHook)
1179 {
1180 vp.setUndefined();
1181 if (!ok)
1182 return handleUncaughtException(ac, vp, callHook);
1183 if (rv.isUndefined()) {
1184 ac.reset();
1185 return JSTRAP_CONTINUE;
1186 }
1187 if (rv.isNull()) {
1188 ac.reset();
1189 return JSTRAP_ERROR;
1190 }
1191
1192 JSContext* cx = ac->context()->asJSContext();
1193 JSTrapStatus status = JSTRAP_CONTINUE;
1194 RootedValue v(cx);
1195 RootedValue rvRoot(cx, rv);
1196 if (!ParseResumptionValueAsObject(cx, rvRoot, &status, &v) ||
1197 !unwrapDebuggeeValue(cx, &v))
1198 {
1199 return handleUncaughtException(ac, vp, callHook);
1200 }
1201
1202 ac.reset();
1203 if (!cx->compartment()->wrap(cx, &v)) {
1204 vp.setUndefined();
1205 return JSTRAP_ERROR;
1206 }
1207 vp.set(v);
1208
1209 return status;
1210 }
1211
1212 static bool
CallMethodIfPresent(JSContext * cx,HandleObject obj,const char * name,int argc,Value * argv,MutableHandleValue rval)1213 CallMethodIfPresent(JSContext* cx, HandleObject obj, const char* name, int argc, Value* argv,
1214 MutableHandleValue rval)
1215 {
1216 rval.setUndefined();
1217 JSAtom* atom = Atomize(cx, name, strlen(name));
1218 if (!atom)
1219 return false;
1220
1221 RootedId id(cx, AtomToId(atom));
1222 RootedValue fval(cx);
1223 return GetProperty(cx, obj, obj, id, &fval) &&
1224 (!IsCallable(fval) || Invoke(cx, ObjectValue(*obj), fval, argc, argv, rval));
1225 }
1226
1227 JSTrapStatus
fireDebuggerStatement(JSContext * cx,MutableHandleValue vp)1228 Debugger::fireDebuggerStatement(JSContext* cx, MutableHandleValue vp)
1229 {
1230 RootedObject hook(cx, getHook(OnDebuggerStatement));
1231 MOZ_ASSERT(hook);
1232 MOZ_ASSERT(hook->isCallable());
1233
1234 Maybe<AutoCompartment> ac;
1235 ac.emplace(cx, object);
1236
1237 ScriptFrameIter iter(cx);
1238 RootedValue scriptFrame(cx);
1239 if (!getScriptFrame(cx, iter, &scriptFrame))
1240 return handleUncaughtException(ac, false);
1241
1242 RootedValue rv(cx);
1243 bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv);
1244 return parseResumptionValue(ac, ok, rv, vp);
1245 }
1246
1247 JSTrapStatus
fireExceptionUnwind(JSContext * cx,MutableHandleValue vp)1248 Debugger::fireExceptionUnwind(JSContext* cx, MutableHandleValue vp)
1249 {
1250 RootedObject hook(cx, getHook(OnExceptionUnwind));
1251 MOZ_ASSERT(hook);
1252 MOZ_ASSERT(hook->isCallable());
1253
1254 RootedValue exc(cx);
1255 if (!cx->getPendingException(&exc))
1256 return JSTRAP_ERROR;
1257 cx->clearPendingException();
1258
1259 Maybe<AutoCompartment> ac;
1260 ac.emplace(cx, object);
1261
1262 JS::AutoValueArray<2> argv(cx);
1263 argv[0].setUndefined();
1264 argv[1].set(exc);
1265
1266 ScriptFrameIter iter(cx);
1267 if (!getScriptFrame(cx, iter, argv[0]) || !wrapDebuggeeValue(cx, argv[1]))
1268 return handleUncaughtException(ac, false);
1269
1270 RootedValue rv(cx);
1271 bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 2, argv.begin(), &rv);
1272 JSTrapStatus st = parseResumptionValue(ac, ok, rv, vp);
1273 if (st == JSTRAP_CONTINUE)
1274 cx->setPendingException(exc);
1275 return st;
1276 }
1277
1278 JSTrapStatus
fireEnterFrame(JSContext * cx,AbstractFramePtr frame,MutableHandleValue vp)1279 Debugger::fireEnterFrame(JSContext* cx, AbstractFramePtr frame, MutableHandleValue vp)
1280 {
1281 RootedObject hook(cx, getHook(OnEnterFrame));
1282 MOZ_ASSERT(hook);
1283 MOZ_ASSERT(hook->isCallable());
1284
1285 Maybe<AutoCompartment> ac;
1286 ac.emplace(cx, object);
1287
1288 RootedValue scriptFrame(cx);
1289 if (!getScriptFrame(cx, frame, &scriptFrame))
1290 return handleUncaughtException(ac, false);
1291
1292 RootedValue rv(cx);
1293 bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv);
1294 return parseResumptionValue(ac, ok, rv, vp);
1295 }
1296
1297 void
fireNewScript(JSContext * cx,HandleScript script)1298 Debugger::fireNewScript(JSContext* cx, HandleScript script)
1299 {
1300 RootedObject hook(cx, getHook(OnNewScript));
1301 MOZ_ASSERT(hook);
1302 MOZ_ASSERT(hook->isCallable());
1303
1304 Maybe<AutoCompartment> ac;
1305 ac.emplace(cx, object);
1306
1307 JSObject* dsobj = wrapScript(cx, script);
1308 if (!dsobj) {
1309 handleUncaughtException(ac, false);
1310 return;
1311 }
1312
1313 RootedValue scriptObject(cx, ObjectValue(*dsobj));
1314 RootedValue rv(cx);
1315 if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptObject.address(), &rv))
1316 handleUncaughtException(ac, true);
1317 }
1318
1319 void
fireOnGarbageCollectionHook(JSContext * cx,const JS::dbg::GarbageCollectionEvent::Ptr & gcData)1320 Debugger::fireOnGarbageCollectionHook(JSContext* cx,
1321 const JS::dbg::GarbageCollectionEvent::Ptr& gcData)
1322 {
1323 MOZ_ASSERT(observedGC(gcData->majorGCNumber()));
1324 observedGCs.remove(gcData->majorGCNumber());
1325
1326 RootedObject hook(cx, getHook(OnGarbageCollection));
1327 MOZ_ASSERT(hook);
1328 MOZ_ASSERT(hook->isCallable());
1329
1330 Maybe<AutoCompartment> ac;
1331 ac.emplace(cx, object);
1332
1333 JSObject* dataObj = gcData->toJSObject(cx);
1334 if (!dataObj) {
1335 handleUncaughtException(ac, false);
1336 return;
1337 }
1338
1339 RootedValue dataVal(cx, ObjectValue(*dataObj));
1340 RootedValue rv(cx);
1341 if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, dataVal.address(), &rv))
1342 handleUncaughtException(ac, true);
1343 }
1344
1345 JSTrapStatus
fireOnIonCompilationHook(JSContext * cx,Handle<ScriptVector> scripts,LSprinter & graph)1346 Debugger::fireOnIonCompilationHook(JSContext* cx, Handle<ScriptVector> scripts, LSprinter& graph)
1347 {
1348 RootedObject hook(cx, getHook(OnIonCompilation));
1349 MOZ_ASSERT(hook);
1350 MOZ_ASSERT(hook->isCallable());
1351
1352 Maybe<AutoCompartment> ac;
1353 ac.emplace(cx, object);
1354
1355 // Copy the vector of scripts to a JS Array of Debugger.Script
1356 RootedObject tmpObj(cx);
1357 RootedValue tmpVal(cx);
1358 AutoValueVector dbgScripts(cx);
1359 for (size_t i = 0; i < scripts.length(); i++) {
1360 tmpObj = wrapScript(cx, scripts[i]);
1361 if (!tmpObj)
1362 return handleUncaughtException(ac, false);
1363
1364 tmpVal.setObject(*tmpObj);
1365 if (!dbgScripts.append(tmpVal))
1366 return handleUncaughtException(ac, false);
1367 }
1368
1369 RootedObject dbgScriptsArray(cx, JS_NewArrayObject(cx, dbgScripts));
1370 if (!dbgScriptsArray)
1371 return handleUncaughtException(ac, false);
1372
1373 // Copy the JSON compilation graph to a JS String which is allocated as part
1374 // of the Debugger compartment.
1375 Sprinter jsonPrinter(cx);
1376 if (!jsonPrinter.init())
1377 return handleUncaughtException(ac, false);
1378
1379 graph.exportInto(jsonPrinter);
1380 if (jsonPrinter.hadOutOfMemory())
1381 return handleUncaughtException(ac, false);
1382
1383 RootedString json(cx, JS_NewStringCopyZ(cx, jsonPrinter.string()));
1384 if (!json)
1385 return handleUncaughtException(ac, false);
1386
1387 // Create a JS Object which has the array of scripts, and the string of the
1388 // JSON graph.
1389 const char* names[] = { "scripts", "json" };
1390 JS::AutoValueArray<2> values(cx);
1391 values[0].setObject(*dbgScriptsArray);
1392 values[1].setString(json);
1393
1394 RootedObject obj(cx, JS_NewObject(cx, nullptr));
1395 if (!obj)
1396 return handleUncaughtException(ac, false);
1397
1398 MOZ_ASSERT(mozilla::ArrayLength(names) == values.length());
1399 for (size_t i = 0; i < mozilla::ArrayLength(names); i++) {
1400 if (!JS_DefineProperty(cx, obj, names[i], values[i], JSPROP_ENUMERATE, nullptr, nullptr))
1401 return handleUncaughtException(ac, false);
1402 }
1403
1404 // Call Debugger.onIonCompilation hook.
1405 JS::AutoValueArray<1> argv(cx);
1406 argv[0].setObject(*obj);
1407
1408 RootedValue rv(cx);
1409 if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, argv.begin(), &rv))
1410 return handleUncaughtException(ac, true);
1411 return JSTRAP_CONTINUE;
1412 }
1413
1414 template <typename HookIsEnabledFun /* bool (Debugger*) */,
1415 typename FireHookFun /* JSTrapStatus (Debugger*) */>
1416 /* static */ JSTrapStatus
dispatchHook(JSContext * cx,HookIsEnabledFun hookIsEnabled,FireHookFun fireHook)1417 Debugger::dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled, FireHookFun fireHook)
1418 {
1419 /*
1420 * Determine which debuggers will receive this event, and in what order.
1421 * Make a copy of the list, since the original is mutable and we will be
1422 * calling into arbitrary JS.
1423 *
1424 * Note: In the general case, 'triggered' contains references to objects in
1425 * different compartments--every compartment *except* this one.
1426 */
1427 AutoValueVector triggered(cx);
1428 Handle<GlobalObject*> global = cx->global();
1429 if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
1430 for (Debugger** p = debuggers->begin(); p != debuggers->end(); p++) {
1431 Debugger* dbg = *p;
1432 if (dbg->enabled && hookIsEnabled(dbg)) {
1433 if (!triggered.append(ObjectValue(*dbg->toJSObject())))
1434 return JSTRAP_ERROR;
1435 }
1436 }
1437 }
1438
1439 /*
1440 * Deliver the event to each debugger, checking again to make sure it
1441 * should still be delivered.
1442 */
1443 for (Value* p = triggered.begin(); p != triggered.end(); p++) {
1444 Debugger* dbg = Debugger::fromJSObject(&p->toObject());
1445 if (dbg->debuggees.has(global) && dbg->enabled && hookIsEnabled(dbg)) {
1446 JSTrapStatus st = fireHook(dbg);
1447 if (st != JSTRAP_CONTINUE)
1448 return st;
1449 }
1450 }
1451 return JSTRAP_CONTINUE;
1452 }
1453
1454 void
slowPathOnNewScript(JSContext * cx,HandleScript script)1455 Debugger::slowPathOnNewScript(JSContext* cx, HandleScript script)
1456 {
1457 JSTrapStatus status = dispatchHook(
1458 cx,
1459 [script](Debugger* dbg) -> bool {
1460 return dbg->observesNewScript() && dbg->observesScript(script);
1461 },
1462 [&](Debugger* dbg) -> JSTrapStatus {
1463 dbg->fireNewScript(cx, script);
1464 return JSTRAP_CONTINUE;
1465 });
1466
1467 if (status == JSTRAP_ERROR) {
1468 ReportOutOfMemory(cx);
1469 return;
1470 }
1471
1472 MOZ_ASSERT(status == JSTRAP_CONTINUE);
1473 }
1474
1475 /* static */ JSTrapStatus
onTrap(JSContext * cx,MutableHandleValue vp)1476 Debugger::onTrap(JSContext* cx, MutableHandleValue vp)
1477 {
1478 ScriptFrameIter iter(cx);
1479 RootedScript script(cx, iter.script());
1480 MOZ_ASSERT(script->isDebuggee());
1481 Rooted<GlobalObject*> scriptGlobal(cx, &script->global());
1482 jsbytecode* pc = iter.pc();
1483 BreakpointSite* site = script->getBreakpointSite(pc);
1484 JSOp op = JSOp(*pc);
1485
1486 /* Build list of breakpoint handlers. */
1487 Vector<Breakpoint*> triggered(cx);
1488 for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
1489 if (!triggered.append(bp))
1490 return JSTRAP_ERROR;
1491 }
1492
1493 for (Breakpoint** p = triggered.begin(); p != triggered.end(); p++) {
1494 Breakpoint* bp = *p;
1495
1496 /* Handlers can clear breakpoints. Check that bp still exists. */
1497 if (!site || !site->hasBreakpoint(bp))
1498 continue;
1499
1500 /*
1501 * There are two reasons we have to check whether dbg is enabled and
1502 * debugging scriptGlobal.
1503 *
1504 * One is just that one breakpoint handler can disable other Debuggers
1505 * or remove debuggees.
1506 *
1507 * The other has to do with non-compile-and-go scripts, which have no
1508 * specific global--until they are executed. Only now do we know which
1509 * global the script is running against.
1510 */
1511 Debugger* dbg = bp->debugger;
1512 bool hasDebuggee = dbg->enabled && dbg->debuggees.has(scriptGlobal);
1513 if (hasDebuggee) {
1514 Maybe<AutoCompartment> ac;
1515 ac.emplace(cx, dbg->object);
1516
1517 RootedValue scriptFrame(cx);
1518 if (!dbg->getScriptFrame(cx, iter, &scriptFrame))
1519 return dbg->handleUncaughtException(ac, false);
1520 RootedValue rv(cx);
1521 Rooted<JSObject*> handler(cx, bp->handler);
1522 bool ok = CallMethodIfPresent(cx, handler, "hit", 1, scriptFrame.address(), &rv);
1523 JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rv, vp, true);
1524 if (st != JSTRAP_CONTINUE)
1525 return st;
1526
1527 /* Calling JS code invalidates site. Reload it. */
1528 site = script->getBreakpointSite(pc);
1529 }
1530 }
1531
1532 /* By convention, return the true op to the interpreter in vp. */
1533 vp.setInt32(op);
1534 return JSTRAP_CONTINUE;
1535 }
1536
1537 /* static */ JSTrapStatus
onSingleStep(JSContext * cx,MutableHandleValue vp)1538 Debugger::onSingleStep(JSContext* cx, MutableHandleValue vp)
1539 {
1540 ScriptFrameIter iter(cx);
1541
1542 /*
1543 * We may be stepping over a JSOP_EXCEPTION, that pushes the context's
1544 * pending exception for a 'catch' clause to handle. Don't let the
1545 * onStep handlers mess with that (other than by returning a resumption
1546 * value).
1547 */
1548 RootedValue exception(cx, UndefinedValue());
1549 bool exceptionPending = cx->isExceptionPending();
1550 if (exceptionPending) {
1551 if (!cx->getPendingException(&exception))
1552 return JSTRAP_ERROR;
1553 cx->clearPendingException();
1554 }
1555
1556 /*
1557 * Build list of Debugger.Frame instances referring to this frame with
1558 * onStep handlers.
1559 */
1560 AutoObjectVector frames(cx);
1561 for (FrameRange r(iter.abstractFramePtr()); !r.empty(); r.popFront()) {
1562 NativeObject* frame = r.frontFrame();
1563 if (!frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() &&
1564 !frames.append(frame))
1565 {
1566 return JSTRAP_ERROR;
1567 }
1568 }
1569
1570 #ifdef DEBUG
1571 /*
1572 * Validate the single-step count on this frame's script, to ensure that
1573 * we're not receiving traps we didn't ask for. Even when frames is
1574 * non-empty (and thus we know this trap was requested), do the check
1575 * anyway, to make sure the count has the correct non-zero value.
1576 *
1577 * The converse --- ensuring that we do receive traps when we should --- can
1578 * be done with unit tests.
1579 */
1580 {
1581 uint32_t stepperCount = 0;
1582 JSScript* trappingScript = iter.script();
1583 GlobalObject* global = cx->global();
1584 if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
1585 for (Debugger** p = debuggers->begin(); p != debuggers->end(); p++) {
1586 Debugger* dbg = *p;
1587 for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) {
1588 AbstractFramePtr frame = r.front().key();
1589 NativeObject* frameobj = r.front().value();
1590 if (frame.script() == trappingScript &&
1591 !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
1592 {
1593 stepperCount++;
1594 }
1595 }
1596 }
1597 }
1598 MOZ_ASSERT(stepperCount == trappingScript->stepModeCount());
1599 }
1600 #endif
1601
1602 /* Call all the onStep handlers we found. */
1603 for (JSObject** p = frames.begin(); p != frames.end(); p++) {
1604 RootedNativeObject frame(cx, &(*p)->as<NativeObject>());
1605 Debugger* dbg = Debugger::fromChildJSObject(frame);
1606
1607 Maybe<AutoCompartment> ac;
1608 ac.emplace(cx, dbg->object);
1609
1610 const Value& handler = frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
1611 RootedValue rval(cx);
1612 bool ok = Invoke(cx, ObjectValue(*frame), handler, 0, nullptr, &rval);
1613 JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rval, vp);
1614 if (st != JSTRAP_CONTINUE)
1615 return st;
1616 }
1617
1618 vp.setUndefined();
1619 if (exceptionPending)
1620 cx->setPendingException(exception);
1621 return JSTRAP_CONTINUE;
1622 }
1623
1624 JSTrapStatus
fireNewGlobalObject(JSContext * cx,Handle<GlobalObject * > global,MutableHandleValue vp)1625 Debugger::fireNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global, MutableHandleValue vp)
1626 {
1627 RootedObject hook(cx, getHook(OnNewGlobalObject));
1628 MOZ_ASSERT(hook);
1629 MOZ_ASSERT(hook->isCallable());
1630
1631 Maybe<AutoCompartment> ac;
1632 ac.emplace(cx, object);
1633
1634 RootedValue wrappedGlobal(cx, ObjectValue(*global));
1635 if (!wrapDebuggeeValue(cx, &wrappedGlobal))
1636 return handleUncaughtException(ac, false);
1637
1638 RootedValue rv(cx);
1639
1640 // onNewGlobalObject is infallible, and thus is only allowed to return
1641 // undefined as a resumption value. If it returns anything else, we throw.
1642 // And if that happens, or if the hook itself throws, we invoke the
1643 // uncaughtExceptionHook so that we never leave an exception pending on the
1644 // cx. This allows JS_NewGlobalObject to avoid handling failures from debugger
1645 // hooks.
1646 bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, wrappedGlobal.address(), &rv);
1647 if (ok && !rv.isUndefined()) {
1648 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
1649 JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED);
1650 ok = false;
1651 }
1652 // NB: Even though we don't care about what goes into it, we have to pass vp
1653 // to handleUncaughtException so that it parses resumption values from the
1654 // uncaughtExceptionHook and tells the caller whether we should execute the
1655 // rest of the onNewGlobalObject hooks or not.
1656 JSTrapStatus status = ok ? JSTRAP_CONTINUE
1657 : handleUncaughtException(ac, vp, true);
1658 MOZ_ASSERT(!cx->isExceptionPending());
1659 return status;
1660 }
1661
1662 void
slowPathOnNewGlobalObject(JSContext * cx,Handle<GlobalObject * > global)1663 Debugger::slowPathOnNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global)
1664 {
1665 MOZ_ASSERT(!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers));
1666 if (global->compartment()->options().invisibleToDebugger())
1667 return;
1668
1669 /*
1670 * Make a copy of the runtime's onNewGlobalObjectWatchers before running the
1671 * handlers. Since one Debugger's handler can disable another's, the list
1672 * can be mutated while we're walking it.
1673 */
1674 AutoObjectVector watchers(cx);
1675 for (JSCList* link = JS_LIST_HEAD(&cx->runtime()->onNewGlobalObjectWatchers);
1676 link != &cx->runtime()->onNewGlobalObjectWatchers;
1677 link = JS_NEXT_LINK(link))
1678 {
1679 Debugger* dbg = fromOnNewGlobalObjectWatchersLink(link);
1680 MOZ_ASSERT(dbg->observesNewGlobalObject());
1681 JSObject* obj = dbg->object;
1682 JS::ExposeObjectToActiveJS(obj);
1683 if (!watchers.append(obj))
1684 return;
1685 }
1686
1687 JSTrapStatus status = JSTRAP_CONTINUE;
1688 RootedValue value(cx);
1689
1690 for (size_t i = 0; i < watchers.length(); i++) {
1691 Debugger* dbg = fromJSObject(watchers[i]);
1692
1693 // We disallow resumption values from onNewGlobalObject hooks, because we
1694 // want the debugger hooks for global object creation to be infallible.
1695 // But if an onNewGlobalObject hook throws, and the uncaughtExceptionHook
1696 // decides to raise an error, we want to at least avoid invoking the rest
1697 // of the onNewGlobalObject handlers in the list (not for any super
1698 // compelling reason, just because it seems like the right thing to do).
1699 // So we ignore whatever comes out in |value|, but break out of the loop
1700 // if a non-success trap status is returned.
1701 if (dbg->observesNewGlobalObject()) {
1702 status = dbg->fireNewGlobalObject(cx, global, &value);
1703 if (status != JSTRAP_CONTINUE && status != JSTRAP_RETURN)
1704 break;
1705 }
1706 }
1707 MOZ_ASSERT(!cx->isExceptionPending());
1708 }
1709
1710 /* static */ bool
slowPathOnLogAllocationSite(JSContext * cx,HandleObject obj,HandleSavedFrame frame,double when,GlobalObject::DebuggerVector & dbgs)1711 Debugger::slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
1712 double when, GlobalObject::DebuggerVector& dbgs)
1713 {
1714 MOZ_ASSERT(!dbgs.empty());
1715 mozilla::DebugOnly<Debugger**> begin = dbgs.begin();
1716
1717 for (Debugger** dbgp = dbgs.begin(); dbgp < dbgs.end(); dbgp++) {
1718 // The set of debuggers had better not change while we're iterating,
1719 // such that the vector gets reallocated.
1720 MOZ_ASSERT(dbgs.begin() == begin);
1721
1722 if ((*dbgp)->trackingAllocationSites &&
1723 (*dbgp)->enabled &&
1724 !(*dbgp)->appendAllocationSite(cx, obj, frame, when))
1725 {
1726 return false;
1727 }
1728 }
1729
1730 return true;
1731 }
1732
1733 /* static */ void
slowPathOnIonCompilation(JSContext * cx,Handle<ScriptVector> scripts,LSprinter & graph)1734 Debugger::slowPathOnIonCompilation(JSContext* cx, Handle<ScriptVector> scripts, LSprinter& graph)
1735 {
1736 JSTrapStatus status = dispatchHook(
1737 cx,
1738 [](Debugger* dbg) -> bool { return dbg->getHook(OnIonCompilation); },
1739 [&](Debugger* dbg) -> JSTrapStatus {
1740 (void) dbg->fireOnIonCompilationHook(cx, scripts, graph);
1741 return JSTRAP_CONTINUE;
1742 });
1743
1744 if (status == JSTRAP_ERROR) {
1745 cx->clearPendingException();
1746 return;
1747 }
1748
1749 MOZ_ASSERT(status == JSTRAP_CONTINUE);
1750 }
1751
1752 bool
isDebuggeeUnbarriered(const JSCompartment * compartment) const1753 Debugger::isDebuggeeUnbarriered(const JSCompartment* compartment) const
1754 {
1755 MOZ_ASSERT(compartment);
1756 return compartment->isDebuggee() && debuggees.has(compartment->unsafeUnbarrieredMaybeGlobal());
1757 }
1758
TenurePromotionsLogEntry(JSRuntime * rt,JSObject & obj,double when)1759 Debugger::TenurePromotionsLogEntry::TenurePromotionsLogEntry(JSRuntime* rt, JSObject& obj, double when)
1760 : className(obj.getClass()->name),
1761 when(when),
1762 frame(getObjectAllocationSite(obj)),
1763 size(JS::ubi::Node(&obj).size(rt->debuggerMallocSizeOf))
1764 { }
1765
1766
1767 void
logTenurePromotion(JSRuntime * rt,JSObject & obj,double when)1768 Debugger::logTenurePromotion(JSRuntime* rt, JSObject& obj, double when)
1769 {
1770 AutoEnterOOMUnsafeRegion oomUnsafe;
1771
1772 if (!tenurePromotionsLog.emplaceBack(rt, obj, when))
1773 oomUnsafe.crash("Debugger::logTenurePromotion");
1774
1775 if (tenurePromotionsLog.length() > maxTenurePromotionsLogLength) {
1776 if (!tenurePromotionsLog.popFront())
1777 oomUnsafe.crash("Debugger::logTenurePromotion");
1778 MOZ_ASSERT(tenurePromotionsLog.length() == maxTenurePromotionsLogLength);
1779 tenurePromotionsLogOverflowed = true;
1780 }
1781 }
1782
1783 bool
appendAllocationSite(JSContext * cx,HandleObject obj,HandleSavedFrame frame,double when)1784 Debugger::appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
1785 double when)
1786 {
1787 MOZ_ASSERT(trackingAllocationSites && enabled);
1788
1789 AutoCompartment ac(cx, object);
1790 RootedObject wrappedFrame(cx, frame);
1791 if (!cx->compartment()->wrap(cx, &wrappedFrame))
1792 return false;
1793
1794 RootedAtom ctorName(cx);
1795 {
1796 AutoCompartment ac(cx, obj);
1797 if (!obj->constructorDisplayAtom(cx, &ctorName))
1798 return false;
1799 }
1800
1801 auto className = obj->getClass()->name;
1802 auto size = JS::ubi::Node(obj.get()).size(cx->runtime()->debuggerMallocSizeOf);
1803 auto inNursery = gc::IsInsideNursery(obj);
1804
1805 if (!allocationsLog.emplaceBack(wrappedFrame, when, className, ctorName, size, inNursery)) {
1806 ReportOutOfMemory(cx);
1807 return false;
1808 }
1809
1810 if (allocationsLog.length() > maxAllocationsLogLength) {
1811 if (!allocationsLog.popFront()) {
1812 ReportOutOfMemory(cx);
1813 return false;
1814 }
1815 MOZ_ASSERT(allocationsLog.length() == maxAllocationsLogLength);
1816 allocationsLogOverflowed = true;
1817 }
1818
1819 return true;
1820 }
1821
1822 JSTrapStatus
firePromiseHook(JSContext * cx,Hook hook,HandleObject promise,MutableHandleValue vp)1823 Debugger::firePromiseHook(JSContext* cx, Hook hook, HandleObject promise, MutableHandleValue vp)
1824 {
1825 MOZ_ASSERT(hook == OnNewPromise || hook == OnPromiseSettled);
1826
1827 RootedObject hookObj(cx, getHook(hook));
1828 MOZ_ASSERT(hookObj);
1829 MOZ_ASSERT(hookObj->isCallable());
1830
1831 Maybe<AutoCompartment> ac;
1832 ac.emplace(cx, object);
1833
1834 RootedValue dbgObj(cx, ObjectValue(*promise));
1835 if (!wrapDebuggeeValue(cx, &dbgObj))
1836 return handleUncaughtException(ac, false);
1837
1838 // Like onNewGlobalObject, the Promise hooks are infallible and the comments
1839 // in |Debugger::fireNewGlobalObject| apply here as well.
1840
1841 RootedValue rv(cx);
1842 bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hookObj), 1, dbgObj.address(), &rv);
1843 if (ok && !rv.isUndefined()) {
1844 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
1845 JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED);
1846 ok = false;
1847 }
1848
1849 JSTrapStatus status = ok ? JSTRAP_CONTINUE
1850 : handleUncaughtException(ac, vp, true);
1851 MOZ_ASSERT(!cx->isExceptionPending());
1852 return status;
1853 }
1854
1855 /* static */ void
slowPathPromiseHook(JSContext * cx,Hook hook,HandleObject promise)1856 Debugger::slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise)
1857 {
1858 MOZ_ASSERT(hook == OnNewPromise || hook == OnPromiseSettled);
1859 RootedValue rval(cx);
1860
1861 JSTrapStatus status = dispatchHook(
1862 cx,
1863 [hook](Debugger* dbg) -> bool { return dbg->getHook(hook); },
1864 [&](Debugger* dbg) -> JSTrapStatus {
1865 (void) dbg->firePromiseHook(cx, hook, promise, &rval);
1866 return JSTRAP_CONTINUE;
1867 });
1868
1869 if (status == JSTRAP_ERROR) {
1870 // The dispatch hook function might fail to append into the list of
1871 // Debuggers which are watching for the hook.
1872 cx->clearPendingException();
1873 return;
1874 }
1875
1876 // Promise hooks are infallible and we ignore errors from uncaught
1877 // exceptions by design.
1878 MOZ_ASSERT(status == JSTRAP_CONTINUE);
1879 }
1880
1881
1882 /*** Debugger code invalidation for observing execution ******************************************/
1883
1884 class MOZ_RAII ExecutionObservableCompartments : public Debugger::ExecutionObservableSet
1885 {
1886 HashSet<JSCompartment*> compartments_;
1887 HashSet<Zone*> zones_;
1888
1889 public:
ExecutionObservableCompartments(JSContext * cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)1890 explicit ExecutionObservableCompartments(JSContext* cx
1891 MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
1892 : compartments_(cx),
1893 zones_(cx)
1894 {
1895 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
1896 }
1897
init()1898 bool init() { return compartments_.init() && zones_.init(); }
add(JSCompartment * comp)1899 bool add(JSCompartment* comp) { return compartments_.put(comp) && zones_.put(comp->zone()); }
1900
1901 typedef HashSet<JSCompartment*>::Range CompartmentRange;
compartments() const1902 const HashSet<JSCompartment*>* compartments() const { return &compartments_; }
1903
zones() const1904 const HashSet<Zone*>* zones() const { return &zones_; }
shouldRecompileOrInvalidate(JSScript * script) const1905 bool shouldRecompileOrInvalidate(JSScript* script) const {
1906 return script->hasBaselineScript() && compartments_.has(script->compartment());
1907 }
shouldMarkAsDebuggee(ScriptFrameIter & iter) const1908 bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
1909 // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
1910 // iter refers to one such, we know we don't match.
1911 return iter.hasUsableAbstractFramePtr() && compartments_.has(iter.compartment());
1912 }
1913
1914 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
1915 };
1916
1917 // Given a particular AbstractFramePtr F that has become observable, this
1918 // represents the stack frames that need to be bailed out or marked as
1919 // debuggees, and the scripts that need to be recompiled, taking inlining into
1920 // account.
1921 class MOZ_RAII ExecutionObservableFrame : public Debugger::ExecutionObservableSet
1922 {
1923 AbstractFramePtr frame_;
1924
1925 public:
ExecutionObservableFrame(AbstractFramePtr frame MOZ_GUARD_OBJECT_NOTIFIER_PARAM)1926 explicit ExecutionObservableFrame(AbstractFramePtr frame
1927 MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
1928 : frame_(frame)
1929 {
1930 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
1931 }
1932
singleZone() const1933 Zone* singleZone() const {
1934 // We never inline across compartments, let alone across zones, so
1935 // frames_'s script's zone is the only one of interest.
1936 return frame_.script()->compartment()->zone();
1937 }
1938
singleScriptForZoneInvalidation() const1939 JSScript* singleScriptForZoneInvalidation() const {
1940 MOZ_CRASH("ExecutionObservableFrame shouldn't need zone-wide invalidation.");
1941 return nullptr;
1942 }
1943
shouldRecompileOrInvalidate(JSScript * script) const1944 bool shouldRecompileOrInvalidate(JSScript* script) const {
1945 // Normally, *this represents exactly one script: the one frame_ is
1946 // running.
1947 //
1948 // However, debug-mode OSR uses *this for both invalidating Ion frames,
1949 // and recompiling the Baseline scripts that those Ion frames will bail
1950 // out into. Suppose frame_ is an inline frame, executing a copy of its
1951 // JSScript, S_inner, that has been inlined into the IonScript of some
1952 // other JSScript, S_outer. We must match S_outer, to decide which Ion
1953 // frame to invalidate; and we must match S_inner, to decide which
1954 // Baseline script to recompile.
1955 //
1956 // Note that this does not, by design, invalidate *all* inliners of
1957 // frame_.script(), as only frame_ is made observable, not
1958 // frame_.script().
1959 if (!script->hasBaselineScript())
1960 return false;
1961
1962 if (script == frame_.script())
1963 return true;
1964
1965 return frame_.isRematerializedFrame() &&
1966 script == frame_.asRematerializedFrame()->outerScript();
1967 }
1968
shouldMarkAsDebuggee(ScriptFrameIter & iter) const1969 bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
1970 // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
1971 // iter refers to one such, we know we don't match.
1972 //
1973 // We never use this 'has' overload for frame invalidation, only for
1974 // frame debuggee marking; so this overload doesn't need a parallel to
1975 // the just-so inlining logic above.
1976 return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr() == frame_;
1977 }
1978
1979 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
1980 };
1981
1982 class MOZ_RAII ExecutionObservableScript : public Debugger::ExecutionObservableSet
1983 {
1984 RootedScript script_;
1985
1986 public:
ExecutionObservableScript(JSContext * cx,JSScript * script MOZ_GUARD_OBJECT_NOTIFIER_PARAM)1987 ExecutionObservableScript(JSContext* cx, JSScript* script
1988 MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
1989 : script_(cx, script)
1990 {
1991 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
1992 }
1993
singleZone() const1994 Zone* singleZone() const { return script_->compartment()->zone(); }
singleScriptForZoneInvalidation() const1995 JSScript* singleScriptForZoneInvalidation() const { return script_; }
shouldRecompileOrInvalidate(JSScript * script) const1996 bool shouldRecompileOrInvalidate(JSScript* script) const {
1997 return script->hasBaselineScript() && script == script_;
1998 }
shouldMarkAsDebuggee(ScriptFrameIter & iter) const1999 bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
2000 // AbstractFramePtr can't refer to non-remateralized Ion frames, and
2001 // while a non-rematerialized Ion frame may indeed be running script_,
2002 // we cannot mark them as debuggees until they bail out.
2003 //
2004 // Upon bailing out, any newly constructed Baseline frames that came
2005 // from Ion frames with scripts that are isDebuggee() is marked as
2006 // debuggee. This is correct in that the only other way a frame may be
2007 // marked as debuggee is via Debugger.Frame reflection, which would
2008 // have rematerialized any Ion frames.
2009 return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr().script() == script_;
2010 }
2011
2012 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
2013 };
2014
2015 /* static */ bool
updateExecutionObservabilityOfFrames(JSContext * cx,const ExecutionObservableSet & obs,IsObserving observing)2016 Debugger::updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObservableSet& obs,
2017 IsObserving observing)
2018 {
2019 AutoSuppressProfilerSampling suppressProfilerSampling(cx);
2020
2021 {
2022 jit::JitContext jctx(cx, nullptr);
2023 if (!jit::RecompileOnStackBaselineScriptsForDebugMode(cx, obs, observing)) {
2024 ReportOutOfMemory(cx);
2025 return false;
2026 }
2027 }
2028
2029 AbstractFramePtr oldestEnabledFrame;
2030 for (ScriptFrameIter iter(cx, ScriptFrameIter::ALL_CONTEXTS,
2031 ScriptFrameIter::GO_THROUGH_SAVED);
2032 !iter.done();
2033 ++iter)
2034 {
2035 if (obs.shouldMarkAsDebuggee(iter)) {
2036 if (observing) {
2037 if (!iter.abstractFramePtr().isDebuggee()) {
2038 oldestEnabledFrame = iter.abstractFramePtr();
2039 oldestEnabledFrame.setIsDebuggee();
2040 }
2041 } else {
2042 #ifdef DEBUG
2043 // Debugger.Frame lifetimes are managed by the debug epilogue,
2044 // so in general it's unsafe to unmark a frame if it has a
2045 // Debugger.Frame associated with it.
2046 FrameRange r(iter.abstractFramePtr());
2047 MOZ_ASSERT(r.empty());
2048 #endif
2049 iter.abstractFramePtr().unsetIsDebuggee();
2050 }
2051 }
2052 }
2053
2054 // See comment in unsetPrevUpToDateUntil.
2055 if (oldestEnabledFrame) {
2056 AutoCompartment ac(cx, oldestEnabledFrame.compartment());
2057 DebugScopes::unsetPrevUpToDateUntil(cx, oldestEnabledFrame);
2058 }
2059
2060 return true;
2061 }
2062
2063 static inline void
MarkBaselineScriptActiveIfObservable(JSScript * script,const Debugger::ExecutionObservableSet & obs)2064 MarkBaselineScriptActiveIfObservable(JSScript* script, const Debugger::ExecutionObservableSet& obs)
2065 {
2066 if (obs.shouldRecompileOrInvalidate(script))
2067 script->baselineScript()->setActive();
2068 }
2069
2070 static bool
AppendAndInvalidateScript(JSContext * cx,Zone * zone,JSScript * script,Vector<JSScript * > & scripts)2071 AppendAndInvalidateScript(JSContext* cx, Zone* zone, JSScript* script, Vector<JSScript*>& scripts)
2072 {
2073 // Enter the script's compartment as addPendingRecompile attempts to
2074 // cancel off-thread compilations, whose books are kept on the
2075 // script's compartment.
2076 MOZ_ASSERT(script->compartment()->zone() == zone);
2077 AutoCompartment ac(cx, script->compartment());
2078 zone->types.addPendingRecompile(cx, script);
2079 return scripts.append(script);
2080 }
2081
2082 static bool
UpdateExecutionObservabilityOfScriptsInZone(JSContext * cx,Zone * zone,const Debugger::ExecutionObservableSet & obs,Debugger::IsObserving observing)2083 UpdateExecutionObservabilityOfScriptsInZone(JSContext* cx, Zone* zone,
2084 const Debugger::ExecutionObservableSet& obs,
2085 Debugger::IsObserving observing)
2086 {
2087 using namespace js::jit;
2088
2089 // See note in js::ReleaseAllJITCode.
2090 cx->runtime()->gc.evictNursery();
2091
2092 AutoSuppressProfilerSampling suppressProfilerSampling(cx);
2093
2094 JSRuntime* rt = cx->runtime();
2095 FreeOp* fop = cx->runtime()->defaultFreeOp();
2096
2097 // Mark active baseline scripts in the observable set so that they don't
2098 // get discarded. They will be recompiled.
2099 for (JitActivationIterator actIter(rt); !actIter.done(); ++actIter) {
2100 if (actIter->compartment()->zone() != zone)
2101 continue;
2102
2103 for (JitFrameIterator iter(actIter); !iter.done(); ++iter) {
2104 switch (iter.type()) {
2105 case JitFrame_BaselineJS:
2106 MarkBaselineScriptActiveIfObservable(iter.script(), obs);
2107 break;
2108 case JitFrame_IonJS:
2109 MarkBaselineScriptActiveIfObservable(iter.script(), obs);
2110 for (InlineFrameIterator inlineIter(rt, &iter); inlineIter.more(); ++inlineIter)
2111 MarkBaselineScriptActiveIfObservable(inlineIter.script(), obs);
2112 break;
2113 default:;
2114 }
2115 }
2116 }
2117
2118 Vector<JSScript*> scripts(cx);
2119
2120 // Iterate through observable scripts, invalidating their Ion scripts and
2121 // appending them to a vector for discarding their baseline scripts later.
2122 {
2123 AutoEnterAnalysis enter(fop, zone);
2124 if (JSScript* script = obs.singleScriptForZoneInvalidation()) {
2125 if (obs.shouldRecompileOrInvalidate(script)) {
2126 if (!AppendAndInvalidateScript(cx, zone, script, scripts))
2127 return false;
2128 }
2129 } else {
2130 for (gc::ZoneCellIter iter(zone, gc::AllocKind::SCRIPT); !iter.done(); iter.next()) {
2131 JSScript* script = iter.get<JSScript>();
2132 if (obs.shouldRecompileOrInvalidate(script) &&
2133 !gc::IsAboutToBeFinalizedUnbarriered(&script))
2134 {
2135 if (!AppendAndInvalidateScript(cx, zone, script, scripts))
2136 return false;
2137 }
2138 }
2139 }
2140 }
2141
2142 // Iterate through the scripts again and finish discarding
2143 // BaselineScripts. This must be done as a separate phase as we can only
2144 // discard the BaselineScript on scripts that have no IonScript.
2145 for (size_t i = 0; i < scripts.length(); i++) {
2146 MOZ_ASSERT_IF(scripts[i]->isDebuggee(), observing);
2147 FinishDiscardBaselineScript(fop, scripts[i]);
2148 }
2149
2150 return true;
2151 }
2152
2153 /* static */ bool
updateExecutionObservabilityOfScripts(JSContext * cx,const ExecutionObservableSet & obs,IsObserving observing)2154 Debugger::updateExecutionObservabilityOfScripts(JSContext* cx, const ExecutionObservableSet& obs,
2155 IsObserving observing)
2156 {
2157 if (Zone* zone = obs.singleZone())
2158 return UpdateExecutionObservabilityOfScriptsInZone(cx, zone, obs, observing);
2159
2160 typedef ExecutionObservableSet::ZoneRange ZoneRange;
2161 for (ZoneRange r = obs.zones()->all(); !r.empty(); r.popFront()) {
2162 if (!UpdateExecutionObservabilityOfScriptsInZone(cx, r.front(), obs, observing))
2163 return false;
2164 }
2165
2166 return true;
2167 }
2168
2169 /* static */ bool
updateExecutionObservability(JSContext * cx,ExecutionObservableSet & obs,IsObserving observing)2170 Debugger::updateExecutionObservability(JSContext* cx, ExecutionObservableSet& obs,
2171 IsObserving observing)
2172 {
2173 if (!obs.singleZone() && obs.zones()->empty())
2174 return true;
2175
2176 // Invalidate scripts first so we can set the needsArgsObj flag on scripts
2177 // before patching frames.
2178 return updateExecutionObservabilityOfScripts(cx, obs, observing) &&
2179 updateExecutionObservabilityOfFrames(cx, obs, observing);
2180 }
2181
2182 /* static */ bool
ensureExecutionObservabilityOfScript(JSContext * cx,JSScript * script)2183 Debugger::ensureExecutionObservabilityOfScript(JSContext* cx, JSScript* script)
2184 {
2185 if (script->isDebuggee())
2186 return true;
2187 ExecutionObservableScript obs(cx, script);
2188 return updateExecutionObservability(cx, obs, Observing);
2189 }
2190
2191 /* static */ bool
ensureExecutionObservabilityOfOsrFrame(JSContext * cx,InterpreterFrame * frame)2192 Debugger::ensureExecutionObservabilityOfOsrFrame(JSContext* cx, InterpreterFrame* frame)
2193 {
2194 MOZ_ASSERT(frame->isDebuggee());
2195 if (frame->script()->hasBaselineScript() &&
2196 frame->script()->baselineScript()->hasDebugInstrumentation())
2197 {
2198 return true;
2199 }
2200 ExecutionObservableFrame obs(frame);
2201 return updateExecutionObservabilityOfFrames(cx, obs, Observing);
2202 }
2203
2204 /* static */ bool
ensureExecutionObservabilityOfFrame(JSContext * cx,AbstractFramePtr frame)2205 Debugger::ensureExecutionObservabilityOfFrame(JSContext* cx, AbstractFramePtr frame)
2206 {
2207 MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
2208 if (frame.isDebuggee())
2209 return true;
2210 ExecutionObservableFrame obs(frame);
2211 return updateExecutionObservabilityOfFrames(cx, obs, Observing);
2212 }
2213
2214 /* static */ bool
ensureExecutionObservabilityOfCompartment(JSContext * cx,JSCompartment * comp)2215 Debugger::ensureExecutionObservabilityOfCompartment(JSContext* cx, JSCompartment* comp)
2216 {
2217 if (comp->debuggerObservesAllExecution())
2218 return true;
2219 ExecutionObservableCompartments obs(cx);
2220 if (!obs.init() || !obs.add(comp))
2221 return false;
2222 comp->updateDebuggerObservesAllExecution();
2223 return updateExecutionObservability(cx, obs, Observing);
2224 }
2225
2226 /* static */ bool
hookObservesAllExecution(Hook which)2227 Debugger::hookObservesAllExecution(Hook which)
2228 {
2229 return which == OnEnterFrame;
2230 }
2231
2232 Debugger::IsObserving
observesAllExecution() const2233 Debugger::observesAllExecution() const
2234 {
2235 if (enabled && !!getHook(OnEnterFrame))
2236 return Observing;
2237 return NotObserving;
2238 }
2239
2240 Debugger::IsObserving
observesAsmJS() const2241 Debugger::observesAsmJS() const
2242 {
2243 if (enabled && !allowUnobservedAsmJS)
2244 return Observing;
2245 return NotObserving;
2246 }
2247
2248 Debugger::IsObserving
observesCoverage() const2249 Debugger::observesCoverage() const
2250 {
2251 if (enabled && collectCoverageInfo)
2252 return Observing;
2253 return NotObserving;
2254 }
2255
2256 // Toggle whether this Debugger's debuggees observe all execution. This is
2257 // called when a hook that observes all execution is set or unset. See
2258 // hookObservesAllExecution.
2259 bool
updateObservesAllExecutionOnDebuggees(JSContext * cx,IsObserving observing)2260 Debugger::updateObservesAllExecutionOnDebuggees(JSContext* cx, IsObserving observing)
2261 {
2262 ExecutionObservableCompartments obs(cx);
2263 if (!obs.init())
2264 return false;
2265
2266 for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
2267 GlobalObject* global = r.front();
2268 JSCompartment* comp = global->compartment();
2269
2270 if (comp->debuggerObservesAllExecution() == observing)
2271 continue;
2272
2273 // It's expensive to eagerly invalidate and recompile a compartment,
2274 // so add the compartment to the set only if we are observing.
2275 if (observing && !obs.add(comp))
2276 return false;
2277
2278 comp->updateDebuggerObservesAllExecution();
2279 }
2280
2281 return updateExecutionObservability(cx, obs, observing);
2282 }
2283
2284 bool
updateObservesCoverageOnDebuggees(JSContext * cx,IsObserving observing)2285 Debugger::updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing)
2286 {
2287 ExecutionObservableCompartments obs(cx);
2288 if (!obs.init())
2289 return false;
2290
2291 for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
2292 GlobalObject* global = r.front();
2293 JSCompartment* comp = global->compartment();
2294
2295 if (comp->debuggerObservesCoverage() == observing)
2296 continue;
2297
2298 // Invalidate and recompile a compartment to add or remove PCCounts
2299 // increments. We have to eagerly invalidate, as otherwise we might have
2300 // dangling pointers to freed PCCounts.
2301 if (!obs.add(comp))
2302 return false;
2303 }
2304
2305 // If any frame on the stack belongs to the debuggee, then we cannot update
2306 // the ScriptCounts, because this would imply to invalidate a Debugger.Frame
2307 // to recompile it with/without ScriptCount support.
2308 for (ScriptFrameIter iter(cx, ScriptFrameIter::ALL_CONTEXTS,
2309 ScriptFrameIter::GO_THROUGH_SAVED);
2310 !iter.done();
2311 ++iter)
2312 {
2313 if (obs.shouldMarkAsDebuggee(iter)) {
2314 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE);
2315 return false;
2316 }
2317 }
2318
2319 if (!updateExecutionObservability(cx, obs, observing))
2320 return false;
2321
2322 // All compartments can safely be toggled, and all scripts will be
2323 // recompiled. Thus we can update each compartment accordingly.
2324 typedef ExecutionObservableCompartments::CompartmentRange CompartmentRange;
2325 for (CompartmentRange r = obs.compartments()->all(); !r.empty(); r.popFront())
2326 r.front()->updateDebuggerObservesCoverage();
2327
2328 return true;
2329 }
2330
2331 void
updateObservesAsmJSOnDebuggees(IsObserving observing)2332 Debugger::updateObservesAsmJSOnDebuggees(IsObserving observing)
2333 {
2334 for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
2335 GlobalObject* global = r.front();
2336 JSCompartment* comp = global->compartment();
2337
2338 if (comp->debuggerObservesAsmJS() == observing)
2339 continue;
2340
2341 comp->updateDebuggerObservesAsmJS();
2342 }
2343 }
2344
2345
2346 /*** Allocations Tracking *************************************************************************/
2347
2348 /* static */ bool
cannotTrackAllocations(const GlobalObject & global)2349 Debugger::cannotTrackAllocations(const GlobalObject& global)
2350 {
2351 auto existingCallback = global.compartment()->getObjectMetadataCallback();
2352 return existingCallback && existingCallback != SavedStacksMetadataCallback;
2353 }
2354
2355 /* static */ bool
isObservedByDebuggerTrackingAllocations(const GlobalObject & debuggee)2356 Debugger::isObservedByDebuggerTrackingAllocations(const GlobalObject& debuggee)
2357 {
2358 if (auto* v = debuggee.getDebuggers()) {
2359 Debugger** p;
2360 for (p = v->begin(); p != v->end(); p++) {
2361 if ((*p)->trackingAllocationSites && (*p)->enabled) {
2362 return true;
2363 }
2364 }
2365 }
2366
2367 return false;
2368 }
2369
2370 /* static */ bool
addAllocationsTracking(JSContext * cx,Handle<GlobalObject * > debuggee)2371 Debugger::addAllocationsTracking(JSContext* cx, Handle<GlobalObject*> debuggee)
2372 {
2373 // Precondition: the given global object is being observed by at least one
2374 // Debugger that is tracking allocations.
2375 MOZ_ASSERT(isObservedByDebuggerTrackingAllocations(*debuggee));
2376
2377 if (Debugger::cannotTrackAllocations(*debuggee)) {
2378 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
2379 JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET);
2380 return false;
2381 }
2382
2383 debuggee->compartment()->setObjectMetadataCallback(SavedStacksMetadataCallback);
2384 debuggee->compartment()->chooseAllocationSamplingProbability();
2385 return true;
2386 }
2387
2388 /* static */ void
removeAllocationsTracking(GlobalObject & global)2389 Debugger::removeAllocationsTracking(GlobalObject& global)
2390 {
2391 // If there are still Debuggers that are observing allocations, we cannot
2392 // remove the metadata callback yet. Recompute the sampling probability
2393 // based on the remaining debuggers' needs.
2394 if (isObservedByDebuggerTrackingAllocations(global)) {
2395 global.compartment()->chooseAllocationSamplingProbability();
2396 return;
2397 }
2398
2399 global.compartment()->forgetObjectMetadataCallback();
2400 }
2401
2402 bool
addAllocationsTrackingForAllDebuggees(JSContext * cx)2403 Debugger::addAllocationsTrackingForAllDebuggees(JSContext* cx)
2404 {
2405 MOZ_ASSERT(trackingAllocationSites);
2406
2407 // We don't want to end up in a state where we added allocations
2408 // tracking to some of our debuggees, but failed to do so for
2409 // others. Before attempting to start tracking allocations in *any* of
2410 // our debuggees, ensure that we will be able to track allocations for
2411 // *all* of our debuggees.
2412 for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
2413 if (Debugger::cannotTrackAllocations(*r.front().get())) {
2414 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
2415 JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET);
2416 return false;
2417 }
2418 }
2419
2420 Rooted<GlobalObject*> g(cx);
2421 for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
2422 // This should always succeed, since we already checked for the
2423 // error case above.
2424 g = r.front().get();
2425 MOZ_ALWAYS_TRUE(Debugger::addAllocationsTracking(cx, g));
2426 }
2427
2428 return true;
2429 }
2430
2431 void
removeAllocationsTrackingForAllDebuggees()2432 Debugger::removeAllocationsTrackingForAllDebuggees()
2433 {
2434 for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront())
2435 Debugger::removeAllocationsTracking(*r.front().get());
2436
2437 allocationsLog.clear();
2438 }
2439
2440
2441
2442 /*** Debugger JSObjects **************************************************************************/
2443
2444 void
markCrossCompartmentEdges(JSTracer * trc)2445 Debugger::markCrossCompartmentEdges(JSTracer* trc)
2446 {
2447 objects.markCrossCompartmentEdges<DebuggerObject_trace>(trc);
2448 environments.markCrossCompartmentEdges<DebuggerEnv_trace>(trc);
2449 scripts.markCrossCompartmentEdges<DebuggerScript_trace>(trc);
2450 sources.markCrossCompartmentEdges<DebuggerSource_trace>(trc);
2451
2452 // Because we don't have access to a `cx` inside
2453 // `Debugger::logTenurePromotion`, we can't hold onto CCWs inside the log,
2454 // and instead have unwrapped cross-compartment edges. We need to be sure to
2455 // mark those here.
2456 TenurePromotionsLog::trace(&tenurePromotionsLog, trc);
2457 }
2458
2459 /*
2460 * Ordinarily, WeakMap keys and values are marked because at some point it was
2461 * discovered that the WeakMap was live; that is, some object containing the
2462 * WeakMap was marked during mark phase.
2463 *
2464 * However, during zone GC, we have to do something about cross-compartment
2465 * edges in non-GC'd compartments. Since the source may be live, we
2466 * conservatively assume it is and mark the edge.
2467 *
2468 * Each Debugger object keeps four cross-compartment WeakMaps: objects, scripts,
2469 * script source objects, and environments. They have the property that all
2470 * their values are in the same compartment as the Debugger object, but we have
2471 * to mark the keys and the private pointer in the wrapper object.
2472 *
2473 * We must scan all Debugger objects regardless of whether they *currently* have
2474 * any debuggees in a compartment being GC'd, because the WeakMap entries
2475 * persist even when debuggees are removed.
2476 *
2477 * This happens during the initial mark phase, not iterative marking, because
2478 * all the edges being reported here are strong references.
2479 *
2480 * This method is also used during compacting GC to update cross compartment
2481 * pointers in zones that are not currently being compacted.
2482 */
2483 /* static */ void
markIncomingCrossCompartmentEdges(JSTracer * trc)2484 Debugger::markIncomingCrossCompartmentEdges(JSTracer* trc)
2485 {
2486 JSRuntime* rt = trc->runtime();
2487 gc::State state = rt->gc.state();
2488 MOZ_ASSERT(state == gc::MARK_ROOTS || state == gc::COMPACT);
2489
2490 for (Debugger* dbg : rt->debuggerList) {
2491 Zone* zone = dbg->object->zone();
2492 if ((state == gc::MARK_ROOTS && !zone->isCollecting()) ||
2493 (state == gc::COMPACT && !zone->isGCCompacting()))
2494 {
2495 dbg->markCrossCompartmentEdges(trc);
2496 }
2497 }
2498 }
2499
2500 /*
2501 * This method has two tasks:
2502 * 1. Mark Debugger objects that are unreachable except for debugger hooks that
2503 * may yet be called.
2504 * 2. Mark breakpoint handlers.
2505 *
2506 * This happens during the iterative part of the GC mark phase. This method
2507 * returns true if it has to mark anything; GC calls it repeatedly until it
2508 * returns false.
2509 */
2510 /* static */ bool
markAllIteratively(GCMarker * trc)2511 Debugger::markAllIteratively(GCMarker* trc)
2512 {
2513 bool markedAny = false;
2514
2515 /*
2516 * Find all Debugger objects in danger of GC. This code is a little
2517 * convoluted since the easiest way to find them is via their debuggees.
2518 */
2519 JSRuntime* rt = trc->runtime();
2520 for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
2521 if (c->isDebuggee()) {
2522 GlobalObject* global = c->unsafeUnbarrieredMaybeGlobal();
2523 if (!IsMarkedUnbarriered(rt, &global))
2524 continue;
2525
2526 /*
2527 * Every debuggee has at least one debugger, so in this case
2528 * getDebuggers can't return nullptr.
2529 */
2530 const GlobalObject::DebuggerVector* debuggers = global->getDebuggers();
2531 MOZ_ASSERT(debuggers);
2532 for (Debugger * const* p = debuggers->begin(); p != debuggers->end(); p++) {
2533 Debugger* dbg = *p;
2534
2535 /*
2536 * dbg is a Debugger with at least one debuggee. Check three things:
2537 * - dbg is actually in a compartment that is being marked
2538 * - it isn't already marked
2539 * - it actually has hooks that might be called
2540 */
2541 HeapPtrNativeObject& dbgobj = dbg->toJSObjectRef();
2542 if (!dbgobj->zone()->isGCMarking())
2543 continue;
2544
2545 bool dbgMarked = IsMarked(rt, &dbgobj);
2546 if (!dbgMarked && dbg->hasAnyLiveHooks(rt)) {
2547 /*
2548 * obj could be reachable only via its live, enabled
2549 * debugger hooks, which may yet be called.
2550 */
2551 TraceEdge(trc, &dbgobj, "enabled Debugger");
2552 markedAny = true;
2553 dbgMarked = true;
2554 }
2555
2556 if (dbgMarked) {
2557 /* Search for breakpoints to mark. */
2558 for (Breakpoint* bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
2559 if (IsMarkedUnbarriered(rt, &bp->site->script)) {
2560 /*
2561 * The debugger and the script are both live.
2562 * Therefore the breakpoint handler is live.
2563 */
2564 if (!IsMarked(rt, &bp->getHandlerRef())) {
2565 TraceEdge(trc, &bp->getHandlerRef(), "breakpoint handler");
2566 markedAny = true;
2567 }
2568 }
2569 }
2570 }
2571 }
2572 }
2573 }
2574 return markedAny;
2575 }
2576
2577 /*
2578 * Mark all debugger-owned GC things unconditionally. This is used by the minor
2579 * GC: the minor GC cannot apply the weak constraints of the full GC because it
2580 * visits only part of the heap.
2581 */
2582 /* static */ void
markAll(JSTracer * trc)2583 Debugger::markAll(JSTracer* trc)
2584 {
2585 JSRuntime* rt = trc->runtime();
2586 for (Debugger* dbg : rt->debuggerList) {
2587 for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
2588 TraceManuallyBarrieredEdge(trc, e.mutableFront().unsafeGet(), "Global Object");
2589
2590 HeapPtrNativeObject& dbgobj = dbg->toJSObjectRef();
2591 TraceEdge(trc, &dbgobj, "Debugger Object");
2592
2593 dbg->scripts.trace(trc);
2594 dbg->sources.trace(trc);
2595 dbg->objects.trace(trc);
2596 dbg->environments.trace(trc);
2597
2598 for (Breakpoint* bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
2599 TraceManuallyBarrieredEdge(trc, &bp->site->script, "breakpoint script");
2600 TraceEdge(trc, &bp->getHandlerRef(), "breakpoint handler");
2601 }
2602 }
2603 }
2604
2605 /* static */ void
traceObject(JSTracer * trc,JSObject * obj)2606 Debugger::traceObject(JSTracer* trc, JSObject* obj)
2607 {
2608 if (Debugger* dbg = Debugger::fromJSObject(obj))
2609 dbg->trace(trc);
2610 }
2611
2612 void
trace(JSTracer * trc)2613 Debugger::trace(JSTracer* trc)
2614 {
2615 if (uncaughtExceptionHook)
2616 TraceEdge(trc, &uncaughtExceptionHook, "hooks");
2617
2618 /*
2619 * Mark Debugger.Frame objects. These are all reachable from JS, because the
2620 * corresponding JS frames are still on the stack.
2621 *
2622 * (Once we support generator frames properly, we will need
2623 * weakly-referenced Debugger.Frame objects as well, for suspended generator
2624 * frames.)
2625 */
2626 for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
2627 RelocatablePtrNativeObject& frameobj = r.front().value();
2628 MOZ_ASSERT(MaybeForwarded(frameobj.get())->getPrivate());
2629 TraceEdge(trc, &frameobj, "live Debugger.Frame");
2630 }
2631
2632 AllocationsLog::trace(&allocationsLog, trc);
2633 TenurePromotionsLog::trace(&tenurePromotionsLog, trc);
2634
2635 /* Trace the weak map from JSScript instances to Debugger.Script objects. */
2636 scripts.trace(trc);
2637
2638 /* Trace the referent ->Debugger.Source weak map */
2639 sources.trace(trc);
2640
2641 /* Trace the referent -> Debugger.Object weak map. */
2642 objects.trace(trc);
2643
2644 /* Trace the referent -> Debugger.Environment weak map. */
2645 environments.trace(trc);
2646 }
2647
2648 /* static */ void
sweepAll(FreeOp * fop)2649 Debugger::sweepAll(FreeOp* fop)
2650 {
2651 JSRuntime* rt = fop->runtime();
2652
2653 for (Debugger* dbg : rt->debuggerList) {
2654 if (IsAboutToBeFinalized(&dbg->object)) {
2655 /*
2656 * dbg is being GC'd. Detach it from its debuggees. The debuggee
2657 * might be GC'd too. Since detaching requires access to both
2658 * objects, this must be done before finalize time.
2659 */
2660 for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
2661 dbg->removeDebuggeeGlobal(fop, e.front().unbarrieredGet(), &e);
2662 }
2663 }
2664 }
2665
2666 /* static */ void
detachAllDebuggersFromGlobal(FreeOp * fop,GlobalObject * global)2667 Debugger::detachAllDebuggersFromGlobal(FreeOp* fop, GlobalObject* global)
2668 {
2669 const GlobalObject::DebuggerVector* debuggers = global->getDebuggers();
2670 MOZ_ASSERT(!debuggers->empty());
2671 while (!debuggers->empty())
2672 debuggers->back()->removeDebuggeeGlobal(fop, global, nullptr);
2673 }
2674
2675 /* static */ void
findZoneEdges(Zone * zone,js::gc::ComponentFinder<Zone> & finder)2676 Debugger::findZoneEdges(Zone* zone, js::gc::ComponentFinder<Zone>& finder)
2677 {
2678 /*
2679 * For debugger cross compartment wrappers, add edges in the opposite
2680 * direction to those already added by JSCompartment::findOutgoingEdges.
2681 * This ensure that debuggers and their debuggees are finalized in the same
2682 * group.
2683 */
2684 for (Debugger* dbg : zone->runtimeFromMainThread()->debuggerList) {
2685 Zone* w = dbg->object->zone();
2686 if (w == zone || !w->isGCMarking())
2687 continue;
2688 if (dbg->debuggeeZones.has(zone) ||
2689 dbg->scripts.hasKeyInZone(zone) ||
2690 dbg->sources.hasKeyInZone(zone) ||
2691 dbg->objects.hasKeyInZone(zone) ||
2692 dbg->environments.hasKeyInZone(zone))
2693 {
2694 finder.addEdgeTo(w);
2695 }
2696 }
2697 }
2698
2699 /* static */ void
finalize(FreeOp * fop,JSObject * obj)2700 Debugger::finalize(FreeOp* fop, JSObject* obj)
2701 {
2702 Debugger* dbg = fromJSObject(obj);
2703 if (!dbg)
2704 return;
2705 fop->delete_(dbg);
2706 }
2707
2708 const Class Debugger::jsclass = {
2709 "Debugger",
2710 JSCLASS_HAS_PRIVATE |
2711 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT),
2712 nullptr, nullptr, nullptr, nullptr,
2713 nullptr, nullptr, nullptr, Debugger::finalize,
2714 nullptr, /* call */
2715 nullptr, /* hasInstance */
2716 nullptr, /* construct */
2717 Debugger::traceObject
2718 };
2719
2720 /* static */ Debugger*
fromThisValue(JSContext * cx,const CallArgs & args,const char * fnname)2721 Debugger::fromThisValue(JSContext* cx, const CallArgs& args, const char* fnname)
2722 {
2723 JSObject* thisobj = NonNullObject(cx, args.thisv());
2724 if (!thisobj)
2725 return nullptr;
2726 if (thisobj->getClass() != &Debugger::jsclass) {
2727 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
2728 "Debugger", fnname, thisobj->getClass()->name);
2729 return nullptr;
2730 }
2731
2732 /*
2733 * Forbid Debugger.prototype, which is of the Debugger JSClass but isn't
2734 * really a Debugger object. The prototype object is distinguished by
2735 * having a nullptr private value.
2736 */
2737 Debugger* dbg = fromJSObject(thisobj);
2738 if (!dbg) {
2739 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
2740 "Debugger", fnname, "prototype object");
2741 }
2742 return dbg;
2743 }
2744
2745 #define THIS_DEBUGGER(cx, argc, vp, fnname, args, dbg) \
2746 CallArgs args = CallArgsFromVp(argc, vp); \
2747 Debugger* dbg = Debugger::fromThisValue(cx, args, fnname); \
2748 if (!dbg) \
2749 return false
2750
2751 /* static */ bool
getEnabled(JSContext * cx,unsigned argc,Value * vp)2752 Debugger::getEnabled(JSContext* cx, unsigned argc, Value* vp)
2753 {
2754 THIS_DEBUGGER(cx, argc, vp, "get enabled", args, dbg);
2755 args.rval().setBoolean(dbg->enabled);
2756 return true;
2757 }
2758
2759 /* static */ bool
setEnabled(JSContext * cx,unsigned argc,Value * vp)2760 Debugger::setEnabled(JSContext* cx, unsigned argc, Value* vp)
2761 {
2762 THIS_DEBUGGER(cx, argc, vp, "set enabled", args, dbg);
2763 if (!args.requireAtLeast(cx, "Debugger.set enabled", 1))
2764 return false;
2765
2766 bool wasEnabled = dbg->enabled;
2767 dbg->enabled = ToBoolean(args[0]);
2768
2769 if (wasEnabled != dbg->enabled) {
2770 if (dbg->trackingAllocationSites) {
2771 if (wasEnabled) {
2772 dbg->removeAllocationsTrackingForAllDebuggees();
2773 } else {
2774 if (!dbg->addAllocationsTrackingForAllDebuggees(cx)) {
2775 dbg->enabled = false;
2776 return false;
2777 }
2778 }
2779 }
2780
2781 for (Breakpoint* bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
2782 if (!wasEnabled)
2783 bp->site->inc(cx->runtime()->defaultFreeOp());
2784 else
2785 bp->site->dec(cx->runtime()->defaultFreeOp());
2786 }
2787
2788 /*
2789 * Add or remove ourselves from the runtime's list of Debuggers
2790 * that care about new globals.
2791 */
2792 if (dbg->getHook(OnNewGlobalObject)) {
2793 if (!wasEnabled) {
2794 /* If we were not enabled, the link should be a singleton list. */
2795 MOZ_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
2796 JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
2797 &cx->runtime()->onNewGlobalObjectWatchers);
2798 } else {
2799 /* If we were enabled, the link should be inserted in the list. */
2800 MOZ_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
2801 JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink);
2802 }
2803 }
2804
2805 // Ensure the compartment is observable if we are re-enabling a
2806 // Debugger with hooks that observe all execution.
2807 if (!dbg->updateObservesAllExecutionOnDebuggees(cx, dbg->observesAllExecution()))
2808 return false;
2809
2810 // Note: To toogle code coverage, we currently need to have no live
2811 // stack frame, thus the coverage does not depend on the enabled flag.
2812
2813 dbg->updateObservesAsmJSOnDebuggees(dbg->observesAsmJS());
2814 }
2815
2816 args.rval().setUndefined();
2817 return true;
2818 }
2819
2820 /* static */ bool
getHookImpl(JSContext * cx,CallArgs & args,Debugger & dbg,Hook which)2821 Debugger::getHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which)
2822 {
2823 MOZ_ASSERT(which >= 0 && which < HookCount);
2824 args.rval().set(dbg.object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + which));
2825 return true;
2826 }
2827
2828 /* static */ bool
setHookImpl(JSContext * cx,CallArgs & args,Debugger & dbg,Hook which)2829 Debugger::setHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which)
2830 {
2831 MOZ_ASSERT(which >= 0 && which < HookCount);
2832 if (!args.requireAtLeast(cx, "Debugger.setHook", 1))
2833 return false;
2834 if (args[0].isObject()) {
2835 if (!args[0].toObject().isCallable())
2836 return ReportIsNotFunction(cx, args[0], args.length() - 1);
2837 } else if (!args[0].isUndefined()) {
2838 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
2839 return false;
2840 }
2841 dbg.object->setReservedSlot(JSSLOT_DEBUG_HOOK_START + which, args[0]);
2842 if (hookObservesAllExecution(which)) {
2843 if (!dbg.updateObservesAllExecutionOnDebuggees(cx, dbg.observesAllExecution()))
2844 return false;
2845 }
2846 args.rval().setUndefined();
2847 return true;
2848 }
2849
2850 /* static */ bool
getOnDebuggerStatement(JSContext * cx,unsigned argc,Value * vp)2851 Debugger::getOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp)
2852 {
2853 THIS_DEBUGGER(cx, argc, vp, "(get onDebuggerStatement)", args, dbg);
2854 return getHookImpl(cx, args, *dbg, OnDebuggerStatement);
2855 }
2856
2857 /* static */ bool
setOnDebuggerStatement(JSContext * cx,unsigned argc,Value * vp)2858 Debugger::setOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp)
2859 {
2860 THIS_DEBUGGER(cx, argc, vp, "(set onDebuggerStatement)", args, dbg);
2861 return setHookImpl(cx, args, *dbg, OnDebuggerStatement);
2862 }
2863
2864 /* static */ bool
getOnExceptionUnwind(JSContext * cx,unsigned argc,Value * vp)2865 Debugger::getOnExceptionUnwind(JSContext* cx, unsigned argc, Value* vp)
2866 {
2867 THIS_DEBUGGER(cx, argc, vp, "(get onExceptionUnwind)", args, dbg);
2868 return getHookImpl(cx, args, *dbg, OnExceptionUnwind);
2869 }
2870
2871 /* static */ bool
setOnExceptionUnwind(JSContext * cx,unsigned argc,Value * vp)2872 Debugger::setOnExceptionUnwind(JSContext* cx, unsigned argc, Value* vp)
2873 {
2874 THIS_DEBUGGER(cx, argc, vp, "(set onExceptionUnwind)", args, dbg);
2875 return setHookImpl(cx, args, *dbg, OnExceptionUnwind);
2876 }
2877
2878 /* static */ bool
getOnNewScript(JSContext * cx,unsigned argc,Value * vp)2879 Debugger::getOnNewScript(JSContext* cx, unsigned argc, Value* vp)
2880 {
2881 THIS_DEBUGGER(cx, argc, vp, "(get onNewScript)", args, dbg);
2882 return getHookImpl(cx, args, *dbg, OnNewScript);
2883 }
2884
2885 /* static */ bool
setOnNewScript(JSContext * cx,unsigned argc,Value * vp)2886 Debugger::setOnNewScript(JSContext* cx, unsigned argc, Value* vp)
2887 {
2888 THIS_DEBUGGER(cx, argc, vp, "(set onNewScript)", args, dbg);
2889 return setHookImpl(cx, args, *dbg, OnNewScript);
2890 }
2891
2892 /* static */ bool
getOnNewPromise(JSContext * cx,unsigned argc,Value * vp)2893 Debugger::getOnNewPromise(JSContext* cx, unsigned argc, Value* vp)
2894 {
2895 THIS_DEBUGGER(cx, argc, vp, "(get onNewPromise)", args, dbg);
2896 return getHookImpl(cx, args, *dbg, OnNewPromise);
2897 }
2898
2899 /* static */ bool
setOnNewPromise(JSContext * cx,unsigned argc,Value * vp)2900 Debugger::setOnNewPromise(JSContext* cx, unsigned argc, Value* vp)
2901 {
2902 THIS_DEBUGGER(cx, argc, vp, "(set onNewPromise)", args, dbg);
2903 return setHookImpl(cx, args, *dbg, OnNewPromise);
2904 }
2905
2906 /* static */ bool
getOnPromiseSettled(JSContext * cx,unsigned argc,Value * vp)2907 Debugger::getOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp)
2908 {
2909 THIS_DEBUGGER(cx, argc, vp, "(get onPromiseSettled)", args, dbg);
2910 return getHookImpl(cx, args, *dbg, OnPromiseSettled);
2911 }
2912
2913 /* static */ bool
setOnPromiseSettled(JSContext * cx,unsigned argc,Value * vp)2914 Debugger::setOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp)
2915 {
2916 THIS_DEBUGGER(cx, argc, vp, "(set onPromiseSettled)", args, dbg);
2917 return setHookImpl(cx, args, *dbg, OnPromiseSettled);
2918 }
2919
2920 /* static */ bool
getOnEnterFrame(JSContext * cx,unsigned argc,Value * vp)2921 Debugger::getOnEnterFrame(JSContext* cx, unsigned argc, Value* vp)
2922 {
2923 THIS_DEBUGGER(cx, argc, vp, "(get onEnterFrame)", args, dbg);
2924 return getHookImpl(cx, args, *dbg, OnEnterFrame);
2925 }
2926
2927 /* static */ bool
setOnEnterFrame(JSContext * cx,unsigned argc,Value * vp)2928 Debugger::setOnEnterFrame(JSContext* cx, unsigned argc, Value* vp)
2929 {
2930 THIS_DEBUGGER(cx, argc, vp, "(set onEnterFrame)", args, dbg);
2931 return setHookImpl(cx, args, *dbg, OnEnterFrame);
2932 }
2933
2934 /* static */ bool
getOnNewGlobalObject(JSContext * cx,unsigned argc,Value * vp)2935 Debugger::getOnNewGlobalObject(JSContext* cx, unsigned argc, Value* vp)
2936 {
2937 THIS_DEBUGGER(cx, argc, vp, "(get onNewGlobalObject)", args, dbg);
2938 return getHookImpl(cx, args, *dbg, OnNewGlobalObject);
2939 }
2940
2941 /* static */ bool
setOnNewGlobalObject(JSContext * cx,unsigned argc,Value * vp)2942 Debugger::setOnNewGlobalObject(JSContext* cx, unsigned argc, Value* vp)
2943 {
2944 THIS_DEBUGGER(cx, argc, vp, "setOnNewGlobalObject", args, dbg);
2945 RootedObject oldHook(cx, dbg->getHook(OnNewGlobalObject));
2946
2947 if (!setHookImpl(cx, args, *dbg, OnNewGlobalObject))
2948 return false;
2949
2950 /*
2951 * Add or remove ourselves from the runtime's list of Debuggers that
2952 * care about new globals.
2953 */
2954 if (dbg->enabled) {
2955 JSObject* newHook = dbg->getHook(OnNewGlobalObject);
2956 if (!oldHook && newHook) {
2957 /* If we didn't have a hook, the link should be a singleton list. */
2958 MOZ_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
2959 JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
2960 &cx->runtime()->onNewGlobalObjectWatchers);
2961 } else if (oldHook && !newHook) {
2962 /* If we did have a hook, the link should be inserted in the list. */
2963 MOZ_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
2964 JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink);
2965 }
2966 }
2967
2968 return true;
2969 }
2970
2971 /* static */ bool
getUncaughtExceptionHook(JSContext * cx,unsigned argc,Value * vp)2972 Debugger::getUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp)
2973 {
2974 THIS_DEBUGGER(cx, argc, vp, "get uncaughtExceptionHook", args, dbg);
2975 args.rval().setObjectOrNull(dbg->uncaughtExceptionHook);
2976 return true;
2977 }
2978
2979 /* static */ bool
setUncaughtExceptionHook(JSContext * cx,unsigned argc,Value * vp)2980 Debugger::setUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp)
2981 {
2982 THIS_DEBUGGER(cx, argc, vp, "set uncaughtExceptionHook", args, dbg);
2983 if (!args.requireAtLeast(cx, "Debugger.set uncaughtExceptionHook", 1))
2984 return false;
2985 if (!args[0].isNull() && (!args[0].isObject() || !args[0].toObject().isCallable())) {
2986 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_ASSIGN_FUNCTION_OR_NULL,
2987 "uncaughtExceptionHook");
2988 return false;
2989 }
2990 dbg->uncaughtExceptionHook = args[0].toObjectOrNull();
2991 args.rval().setUndefined();
2992 return true;
2993 }
2994
2995 /* static */ bool
getAllowUnobservedAsmJS(JSContext * cx,unsigned argc,Value * vp)2996 Debugger::getAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp)
2997 {
2998 THIS_DEBUGGER(cx, argc, vp, "get allowUnobservedAsmJS", args, dbg);
2999 args.rval().setBoolean(dbg->allowUnobservedAsmJS);
3000 return true;
3001 }
3002
3003 /* static */ bool
setAllowUnobservedAsmJS(JSContext * cx,unsigned argc,Value * vp)3004 Debugger::setAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp)
3005 {
3006 THIS_DEBUGGER(cx, argc, vp, "set allowUnobservedAsmJS", args, dbg);
3007 if (!args.requireAtLeast(cx, "Debugger.set allowUnobservedAsmJS", 1))
3008 return false;
3009 dbg->allowUnobservedAsmJS = ToBoolean(args[0]);
3010
3011 for (WeakGlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) {
3012 GlobalObject* global = r.front();
3013 JSCompartment* comp = global->compartment();
3014 comp->updateDebuggerObservesAsmJS();
3015 }
3016
3017 args.rval().setUndefined();
3018 return true;
3019 }
3020
3021 /* static */ bool
getCollectCoverageInfo(JSContext * cx,unsigned argc,Value * vp)3022 Debugger::getCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp)
3023 {
3024 THIS_DEBUGGER(cx, argc, vp, "get collectCoverageInfo", args, dbg);
3025 args.rval().setBoolean(dbg->collectCoverageInfo);
3026 return true;
3027 }
3028
3029 /* static */ bool
setCollectCoverageInfo(JSContext * cx,unsigned argc,Value * vp)3030 Debugger::setCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp)
3031 {
3032 THIS_DEBUGGER(cx, argc, vp, "set collectCoverageInfo", args, dbg);
3033 if (!args.requireAtLeast(cx, "Debugger.set collectCoverageInfo", 1))
3034 return false;
3035 dbg->collectCoverageInfo = ToBoolean(args[0]);
3036
3037 IsObserving observing = dbg->collectCoverageInfo ? Observing : NotObserving;
3038 if (!dbg->updateObservesCoverageOnDebuggees(cx, observing))
3039 return false;
3040
3041 args.rval().setUndefined();
3042 return true;
3043 }
3044
3045 /* static */ bool
getMemory(JSContext * cx,unsigned argc,Value * vp)3046 Debugger::getMemory(JSContext* cx, unsigned argc, Value* vp)
3047 {
3048 THIS_DEBUGGER(cx, argc, vp, "get memory", args, dbg);
3049 Value memoryValue = dbg->object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE);
3050
3051 if (!memoryValue.isObject()) {
3052 RootedObject memory(cx, DebuggerMemory::create(cx, dbg));
3053 if (!memory)
3054 return false;
3055 memoryValue = ObjectValue(*memory);
3056 }
3057
3058 args.rval().set(memoryValue);
3059 return true;
3060 }
3061
3062 /* static */ bool
getOnIonCompilation(JSContext * cx,unsigned argc,Value * vp)3063 Debugger::getOnIonCompilation(JSContext* cx, unsigned argc, Value* vp)
3064 {
3065 THIS_DEBUGGER(cx, argc, vp, "(get onIonCompilation)", args, dbg);
3066 return getHookImpl(cx, args, *dbg, OnIonCompilation);
3067 }
3068
3069 /* static */ bool
setOnIonCompilation(JSContext * cx,unsigned argc,Value * vp)3070 Debugger::setOnIonCompilation(JSContext* cx, unsigned argc, Value* vp)
3071 {
3072 THIS_DEBUGGER(cx, argc, vp, "(set onIonCompilation)", args, dbg);
3073 return setHookImpl(cx, args, *dbg, OnIonCompilation);
3074 }
3075
3076 /*
3077 * Given a value used to designate a global (there's quite a variety; see the
3078 * docs), return the actual designee.
3079 *
3080 * Note that this does not check whether the designee is marked "invisible to
3081 * Debugger" or not; different callers need to handle invisible-to-Debugger
3082 * globals in different ways.
3083 */
3084 GlobalObject*
unwrapDebuggeeArgument(JSContext * cx,const Value & v)3085 Debugger::unwrapDebuggeeArgument(JSContext* cx, const Value& v)
3086 {
3087 if (!v.isObject()) {
3088 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
3089 "argument", "not a global object");
3090 return nullptr;
3091 }
3092
3093 RootedObject obj(cx, &v.toObject());
3094
3095 /* If it's a Debugger.Object belonging to this debugger, dereference that. */
3096 if (obj->getClass() == &DebuggerObject_class) {
3097 RootedValue rv(cx, v);
3098 if (!unwrapDebuggeeValue(cx, &rv))
3099 return nullptr;
3100 obj = &rv.toObject();
3101 }
3102
3103 /* If we have a cross-compartment wrapper, dereference as far as is secure. */
3104 obj = CheckedUnwrap(obj);
3105 if (!obj) {
3106 JS_ReportError(cx, "Permission denied to access object");
3107 return nullptr;
3108 }
3109
3110 /* If that produced a WindowProxy, get the Window (global). */
3111 obj = ToWindowIfWindowProxy(obj);
3112
3113 /* If that didn't produce a global object, it's an error. */
3114 if (!obj->is<GlobalObject>()) {
3115 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
3116 "argument", "not a global object");
3117 return nullptr;
3118 }
3119
3120 return &obj->as<GlobalObject>();
3121 }
3122
3123 /* static */ bool
addDebuggee(JSContext * cx,unsigned argc,Value * vp)3124 Debugger::addDebuggee(JSContext* cx, unsigned argc, Value* vp)
3125 {
3126 THIS_DEBUGGER(cx, argc, vp, "addDebuggee", args, dbg);
3127 if (!args.requireAtLeast(cx, "Debugger.addDebuggee", 1))
3128 return false;
3129 Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
3130 if (!global)
3131 return false;
3132
3133 if (!dbg->addDebuggeeGlobal(cx, global))
3134 return false;
3135
3136 RootedValue v(cx, ObjectValue(*global));
3137 if (!dbg->wrapDebuggeeValue(cx, &v))
3138 return false;
3139 args.rval().set(v);
3140 return true;
3141 }
3142
3143 /* static */ bool
addAllGlobalsAsDebuggees(JSContext * cx,unsigned argc,Value * vp)3144 Debugger::addAllGlobalsAsDebuggees(JSContext* cx, unsigned argc, Value* vp)
3145 {
3146 THIS_DEBUGGER(cx, argc, vp, "addAllGlobalsAsDebuggees", args, dbg);
3147 for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) {
3148 for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
3149 if (c == dbg->object->compartment() || c->options().invisibleToDebugger())
3150 continue;
3151 c->scheduledForDestruction = false;
3152 GlobalObject* global = c->maybeGlobal();
3153 if (global) {
3154 Rooted<GlobalObject*> rg(cx, global);
3155 if (!dbg->addDebuggeeGlobal(cx, rg))
3156 return false;
3157 }
3158 }
3159 }
3160
3161 args.rval().setUndefined();
3162 return true;
3163 }
3164
3165 /* static */ bool
removeDebuggee(JSContext * cx,unsigned argc,Value * vp)3166 Debugger::removeDebuggee(JSContext* cx, unsigned argc, Value* vp)
3167 {
3168 THIS_DEBUGGER(cx, argc, vp, "removeDebuggee", args, dbg);
3169
3170 if (!args.requireAtLeast(cx, "Debugger.removeDebuggee", 1))
3171 return false;
3172 Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
3173 if (!global)
3174 return false;
3175
3176 ExecutionObservableCompartments obs(cx);
3177 if (!obs.init())
3178 return false;
3179
3180 if (dbg->debuggees.has(global)) {
3181 dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), global, nullptr);
3182
3183 // Only update the compartment if there are no Debuggers left, as it's
3184 // expensive to check if no other Debugger has a live script or frame hook
3185 // on any of the current on-stack debuggee frames.
3186 if (global->getDebuggers()->empty() && !obs.add(global->compartment()))
3187 return false;
3188 if (!updateExecutionObservability(cx, obs, NotObserving))
3189 return false;
3190 }
3191
3192 args.rval().setUndefined();
3193 return true;
3194 }
3195
3196 /* static */ bool
removeAllDebuggees(JSContext * cx,unsigned argc,Value * vp)3197 Debugger::removeAllDebuggees(JSContext* cx, unsigned argc, Value* vp)
3198 {
3199 THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg);
3200
3201 ExecutionObservableCompartments obs(cx);
3202 if (!obs.init())
3203 return false;
3204
3205 for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
3206 Rooted<GlobalObject*> global(cx, e.front());
3207 dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), global, &e);
3208
3209 // See note about adding to the observable set in removeDebuggee.
3210 if (global->getDebuggers()->empty() && !obs.add(global->compartment()))
3211 return false;
3212 }
3213
3214 if (!updateExecutionObservability(cx, obs, NotObserving))
3215 return false;
3216
3217 args.rval().setUndefined();
3218 return true;
3219 }
3220
3221 /* static */ bool
hasDebuggee(JSContext * cx,unsigned argc,Value * vp)3222 Debugger::hasDebuggee(JSContext* cx, unsigned argc, Value* vp)
3223 {
3224 THIS_DEBUGGER(cx, argc, vp, "hasDebuggee", args, dbg);
3225 if (!args.requireAtLeast(cx, "Debugger.hasDebuggee", 1))
3226 return false;
3227 GlobalObject* global = dbg->unwrapDebuggeeArgument(cx, args[0]);
3228 if (!global)
3229 return false;
3230 args.rval().setBoolean(!!dbg->debuggees.lookup(global));
3231 return true;
3232 }
3233
3234 /* static */ bool
getDebuggees(JSContext * cx,unsigned argc,Value * vp)3235 Debugger::getDebuggees(JSContext* cx, unsigned argc, Value* vp)
3236 {
3237 THIS_DEBUGGER(cx, argc, vp, "getDebuggees", args, dbg);
3238
3239 // Obtain the list of debuggees before wrapping each debuggee, as a GC could
3240 // update the debuggees set while we are iterating it.
3241 unsigned count = dbg->debuggees.count();
3242 AutoValueVector debuggees(cx);
3243 if (!debuggees.resize(count))
3244 return false;
3245 unsigned i = 0;
3246 {
3247 JS::AutoCheckCannotGC nogc;
3248 for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
3249 debuggees[i++].setObject(*e.front().get());
3250 }
3251
3252 RootedArrayObject arrobj(cx, NewDenseFullyAllocatedArray(cx, count));
3253 if (!arrobj)
3254 return false;
3255 arrobj->ensureDenseInitializedLength(cx, 0, count);
3256 for (i = 0; i < count; i++) {
3257 RootedValue v(cx, debuggees[i]);
3258 if (!dbg->wrapDebuggeeValue(cx, &v))
3259 return false;
3260 arrobj->setDenseElement(i, v);
3261 }
3262
3263 args.rval().setObject(*arrobj);
3264 return true;
3265 }
3266
3267 /* static */ bool
getNewestFrame(JSContext * cx,unsigned argc,Value * vp)3268 Debugger::getNewestFrame(JSContext* cx, unsigned argc, Value* vp)
3269 {
3270 THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
3271
3272 /* Since there may be multiple contexts, use AllFramesIter. */
3273 for (AllFramesIter i(cx); !i.done(); ++i) {
3274 if (dbg->observesFrame(i)) {
3275 // Ensure that Ion frames are rematerialized. Only rematerialized
3276 // Ion frames may be used as AbstractFramePtrs.
3277 if (i.isIon() && !i.ensureHasRematerializedFrame(cx))
3278 return false;
3279 AbstractFramePtr frame = i.abstractFramePtr();
3280 ScriptFrameIter iter(i.activation()->cx(), ScriptFrameIter::GO_THROUGH_SAVED);
3281 while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame)
3282 ++iter;
3283 return dbg->getScriptFrame(cx, iter, args.rval());
3284 }
3285 }
3286 args.rval().setNull();
3287 return true;
3288 }
3289
3290 /* static */ bool
clearAllBreakpoints(JSContext * cx,unsigned argc,Value * vp)3291 Debugger::clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp)
3292 {
3293 THIS_DEBUGGER(cx, argc, vp, "clearAllBreakpoints", args, dbg);
3294 for (WeakGlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront())
3295 r.front()->compartment()->clearBreakpointsIn(cx->runtime()->defaultFreeOp(),
3296 dbg, nullptr);
3297 return true;
3298 }
3299
3300 /* static */ bool
construct(JSContext * cx,unsigned argc,Value * vp)3301 Debugger::construct(JSContext* cx, unsigned argc, Value* vp)
3302 {
3303 CallArgs args = CallArgsFromVp(argc, vp);
3304
3305 /* Check that the arguments, if any, are cross-compartment wrappers. */
3306 for (unsigned i = 0; i < args.length(); i++) {
3307 JSObject* argobj = NonNullObject(cx, args[i]);
3308 if (!argobj)
3309 return false;
3310 if (!argobj->is<CrossCompartmentWrapperObject>()) {
3311 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_CCW_REQUIRED,
3312 "Debugger");
3313 return false;
3314 }
3315 }
3316
3317 /* Get Debugger.prototype. */
3318 RootedValue v(cx);
3319 RootedObject callee(cx, &args.callee());
3320 if (!GetProperty(cx, callee, callee, cx->names().prototype, &v))
3321 return false;
3322 RootedNativeObject proto(cx, &v.toObject().as<NativeObject>());
3323 MOZ_ASSERT(proto->getClass() == &Debugger::jsclass);
3324 /*
3325 * Make the new Debugger object. Each one has a reference to
3326 * Debugger.{Frame,Object,Script,Memory}.prototype in reserved slots. The
3327 * rest of the reserved slots are for hooks; they default to undefined.
3328 */
3329 RootedNativeObject obj(cx, NewNativeObjectWithGivenProto(cx, &Debugger::jsclass, proto));
3330 if (!obj)
3331 return false;
3332 for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP; slot++)
3333 obj->setReservedSlot(slot, proto->getReservedSlot(slot));
3334 obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, NullValue());
3335
3336 Debugger* debugger;
3337 {
3338 /* Construct the underlying C++ object. */
3339 auto dbg = cx->make_unique<Debugger>(cx, obj.get());
3340 if (!dbg || !dbg->init(cx))
3341 return false;
3342
3343 debugger = dbg.release();
3344 obj->setPrivate(debugger); // owns the released pointer
3345 }
3346
3347 /* Add the initial debuggees, if any. */
3348 for (unsigned i = 0; i < args.length(); i++) {
3349 Rooted<GlobalObject*>
3350 debuggee(cx, &args[i].toObject().as<ProxyObject>().private_().toObject().global());
3351 if (!debugger->addDebuggeeGlobal(cx, debuggee))
3352 return false;
3353 }
3354
3355 args.rval().setObject(*obj);
3356 return true;
3357 }
3358
3359 bool
addDebuggeeGlobal(JSContext * cx,Handle<GlobalObject * > global)3360 Debugger::addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> global)
3361 {
3362 if (debuggees.has(global))
3363 return true;
3364
3365 // Callers should generally be unable to get a reference to a debugger-
3366 // invisible global in order to pass it to addDebuggee. But this is possible
3367 // with certain testing aides we expose in the shell, so just make addDebuggee
3368 // throw in that case.
3369 JSCompartment* debuggeeCompartment = global->compartment();
3370 if (debuggeeCompartment->options().invisibleToDebugger()) {
3371 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
3372 JSMSG_DEBUG_CANT_DEBUG_GLOBAL);
3373 return false;
3374 }
3375
3376 /*
3377 * Check for cycles. If global's compartment is reachable from this
3378 * Debugger object's compartment by following debuggee-to-debugger links,
3379 * then adding global would create a cycle. (Typically nobody is debugging
3380 * the debugger, in which case we zip through this code without looping.)
3381 */
3382 Vector<JSCompartment*> visited(cx);
3383 if (!visited.append(object->compartment()))
3384 return false;
3385 for (size_t i = 0; i < visited.length(); i++) {
3386 JSCompartment* c = visited[i];
3387 if (c == debuggeeCompartment) {
3388 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_LOOP);
3389 return false;
3390 }
3391
3392 /*
3393 * Find all compartments containing debuggers debugging c's global
3394 * object. Add those compartments to visited.
3395 */
3396 if (c->isDebuggee()) {
3397 GlobalObject::DebuggerVector* v = c->maybeGlobal()->getDebuggers();
3398 for (Debugger** p = v->begin(); p != v->end(); p++) {
3399 JSCompartment* next = (*p)->object->compartment();
3400 if (Find(visited, next) == visited.end() && !visited.append(next))
3401 return false;
3402 }
3403 }
3404 }
3405
3406 /*
3407 * For global to become this js::Debugger's debuggee:
3408 *
3409 * 1. this js::Debugger must be in global->getDebuggers(),
3410 * 2. global must be in this->debuggees,
3411 * 3. it must be in zone->getDebuggers(),
3412 * 4. the debuggee's zone must be in this->debuggeeZones,
3413 * 5. if we are tracking allocations, the SavedStacksMetadataCallback must be
3414 * installed for this compartment, and
3415 * 6. JSCompartment::isDebuggee()'s bit must be set.
3416 *
3417 * All six indications must be kept consistent.
3418 */
3419
3420 AutoCompartment ac(cx, global);
3421 Zone* zone = global->zone();
3422
3423 // (1)
3424 auto* globalDebuggers = GlobalObject::getOrCreateDebuggers(cx, global);
3425 if (!globalDebuggers)
3426 return false;
3427 if (!globalDebuggers->append(this)) {
3428 ReportOutOfMemory(cx);
3429 return false;
3430 }
3431 auto globalDebuggersGuard = MakeScopeExit([&] {
3432 globalDebuggers->popBack();
3433 });
3434
3435 // (2)
3436 if (!debuggees.put(global)) {
3437 ReportOutOfMemory(cx);
3438 return false;
3439 }
3440 auto debuggeesGuard = MakeScopeExit([&] {
3441 debuggees.remove(global);
3442 });
3443
3444 bool addingZoneRelation = !debuggeeZones.has(zone);
3445
3446 // (3)
3447 auto* zoneDebuggers = zone->getOrCreateDebuggers(cx);
3448 if (!zoneDebuggers)
3449 return false;
3450 if (addingZoneRelation && !zoneDebuggers->append(this)) {
3451 ReportOutOfMemory(cx);
3452 return false;
3453 }
3454 auto zoneDebuggersGuard = MakeScopeExit([&] {
3455 if (addingZoneRelation)
3456 zoneDebuggers->popBack();
3457 });
3458
3459 // (4)
3460 if (addingZoneRelation && !debuggeeZones.put(zone)) {
3461 ReportOutOfMemory(cx);
3462 return false;
3463 }
3464 auto debuggeeZonesGuard = MakeScopeExit([&] {
3465 if (addingZoneRelation)
3466 debuggeeZones.remove(zone);
3467 });
3468
3469 // (5)
3470 if (trackingAllocationSites && enabled && !Debugger::addAllocationsTracking(cx, global))
3471 return false;
3472
3473 auto allocationsTrackingGuard = MakeScopeExit([&] {
3474 if (trackingAllocationSites && enabled)
3475 Debugger::removeAllocationsTracking(*global);
3476 });
3477
3478 // (6)
3479 debuggeeCompartment->setIsDebuggee();
3480 debuggeeCompartment->updateDebuggerObservesAsmJS();
3481 debuggeeCompartment->updateDebuggerObservesCoverage();
3482 if (observesAllExecution() && !ensureExecutionObservabilityOfCompartment(cx, debuggeeCompartment))
3483 return false;
3484
3485 globalDebuggersGuard.release();
3486 debuggeesGuard.release();
3487 zoneDebuggersGuard.release();
3488 debuggeeZonesGuard.release();
3489 allocationsTrackingGuard.release();
3490 return true;
3491 }
3492
3493 void
recomputeDebuggeeZoneSet()3494 Debugger::recomputeDebuggeeZoneSet()
3495 {
3496 AutoEnterOOMUnsafeRegion oomUnsafe;
3497 debuggeeZones.clear();
3498 for (auto range = debuggees.all(); !range.empty(); range.popFront()) {
3499 if (!debuggeeZones.put(range.front().unbarrieredGet()->zone()))
3500 oomUnsafe.crash("Debugger::removeDebuggeeGlobal");
3501 }
3502 }
3503
3504 template<typename V>
3505 static Debugger**
findDebuggerInVector(Debugger * dbg,V * vec)3506 findDebuggerInVector(Debugger* dbg, V* vec)
3507 {
3508 Debugger** p;
3509 for (p = vec->begin(); p != vec->end(); p++) {
3510 if (*p == dbg)
3511 break;
3512 }
3513 MOZ_ASSERT(p != vec->end());
3514 return p;
3515 }
3516
3517 void
removeDebuggeeGlobal(FreeOp * fop,GlobalObject * global,WeakGlobalObjectSet::Enum * debugEnum)3518 Debugger::removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global,
3519 WeakGlobalObjectSet::Enum* debugEnum)
3520 {
3521 /*
3522 * The caller might have found global by enumerating this->debuggees; if
3523 * so, use HashSet::Enum::removeFront rather than HashSet::remove below,
3524 * to avoid invalidating the live enumerator.
3525 */
3526 MOZ_ASSERT(debuggees.has(global));
3527 MOZ_ASSERT(debuggeeZones.has(global->zone()));
3528 MOZ_ASSERT_IF(debugEnum, debugEnum->front().unbarrieredGet() == global);
3529
3530 /*
3531 * FIXME Debugger::slowPathOnLeaveFrame needs to kill all Debugger.Frame
3532 * objects referring to a particular JS stack frame. This is hard if
3533 * Debugger objects that are no longer debugging the relevant global might
3534 * have live Frame objects. So we take the easy way out and kill them here.
3535 * This is a bug, since it's observable and contrary to the spec. One
3536 * possible fix would be to put such objects into a compartment-wide bag
3537 * which slowPathOnLeaveFrame would have to examine.
3538 */
3539 for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
3540 AbstractFramePtr frame = e.front().key();
3541 NativeObject* frameobj = e.front().value();
3542 if (&frame.script()->global() == global) {
3543 DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
3544 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
3545 e.removeFront();
3546 }
3547 }
3548
3549 auto *globalDebuggersVector = global->getDebuggers();
3550 auto *zoneDebuggersVector = global->zone()->getDebuggers();
3551
3552 /*
3553 * The relation must be removed from up to three places:
3554 * globalDebuggersVector and debuggees for sure, and possibly the
3555 * compartment's debuggee set.
3556 *
3557 * The debuggee zone set is recomputed on demand. This avoids refcounting
3558 * and in practice we have relatively few debuggees that tend to all be in
3559 * the same zone. If after recomputing the debuggee zone set, this global's
3560 * zone is not in the set, then we must remove ourselves from the zone's
3561 * vector of observing debuggers.
3562 */
3563 globalDebuggersVector->erase(findDebuggerInVector(this, globalDebuggersVector));
3564
3565 if (debugEnum)
3566 debugEnum->removeFront();
3567 else
3568 debuggees.remove(global);
3569
3570 recomputeDebuggeeZoneSet();
3571
3572 if (!debuggeeZones.has(global->zone()))
3573 zoneDebuggersVector->erase(findDebuggerInVector(this, zoneDebuggersVector));
3574
3575 /* Remove all breakpoints for the debuggee. */
3576 Breakpoint* nextbp;
3577 for (Breakpoint* bp = firstBreakpoint(); bp; bp = nextbp) {
3578 nextbp = bp->nextInDebugger();
3579 if (bp->site->script->compartment() == global->compartment())
3580 bp->destroy(fop);
3581 }
3582 MOZ_ASSERT_IF(debuggees.empty(), !firstBreakpoint());
3583
3584 /*
3585 * If we are tracking allocation sites, we need to remove the object
3586 * metadata callback from this global's compartment.
3587 */
3588 if (trackingAllocationSites)
3589 Debugger::removeAllocationsTracking(*global);
3590
3591 if (global->getDebuggers()->empty()) {
3592 global->compartment()->unsetIsDebuggee();
3593 } else {
3594 global->compartment()->updateDebuggerObservesAllExecution();
3595 global->compartment()->updateDebuggerObservesAsmJS();
3596 global->compartment()->updateDebuggerObservesCoverage();
3597 }
3598 }
3599
3600
3601 static inline ScriptSourceObject* GetSourceReferent(JSObject* obj);
3602
3603 /*
3604 * A class for parsing 'findScripts' query arguments and searching for
3605 * scripts that match the criteria they represent.
3606 */
3607 class MOZ_STACK_CLASS Debugger::ScriptQuery
3608 {
3609 public:
3610 /* Construct a ScriptQuery to use matching scripts for |dbg|. */
ScriptQuery(JSContext * cx,Debugger * dbg)3611 ScriptQuery(JSContext* cx, Debugger* dbg):
3612 cx(cx), debugger(dbg), compartments(cx->runtime()), url(cx), displayURLString(cx),
3613 source(cx), innermostForCompartment(cx->runtime()), vector(cx, ScriptVector(cx))
3614 {}
3615
3616 /*
3617 * Initialize this ScriptQuery. Raise an error and return false if we
3618 * haven't enough memory.
3619 */
init()3620 bool init() {
3621 if (!compartments.init() ||
3622 !innermostForCompartment.init())
3623 {
3624 ReportOutOfMemory(cx);
3625 return false;
3626 }
3627
3628 return true;
3629 }
3630
3631 /*
3632 * Parse the query object |query|, and prepare to match only the scripts
3633 * it specifies.
3634 */
parseQuery(HandleObject query)3635 bool parseQuery(HandleObject query) {
3636 /*
3637 * Check for a 'global' property, which limits the results to those
3638 * scripts scoped to a particular global object.
3639 */
3640 RootedValue global(cx);
3641 if (!GetProperty(cx, query, query, cx->names().global, &global))
3642 return false;
3643 if (global.isUndefined()) {
3644 matchAllDebuggeeGlobals();
3645 } else {
3646 GlobalObject* globalObject = debugger->unwrapDebuggeeArgument(cx, global);
3647 if (!globalObject)
3648 return false;
3649
3650 /*
3651 * If the given global isn't a debuggee, just leave the set of
3652 * acceptable globals empty; we'll return no scripts.
3653 */
3654 if (debugger->debuggees.has(globalObject)) {
3655 if (!matchSingleGlobal(globalObject))
3656 return false;
3657 }
3658 }
3659
3660 /* Check for a 'url' property. */
3661 if (!GetProperty(cx, query, query, cx->names().url, &url))
3662 return false;
3663 if (!url.isUndefined() && !url.isString()) {
3664 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
3665 "query object's 'url' property", "neither undefined nor a string");
3666 return false;
3667 }
3668
3669 /* Check for a 'source' property */
3670 RootedValue debuggerSource(cx);
3671 if (!GetProperty(cx, query, query, cx->names().source, &debuggerSource))
3672 return false;
3673 if (!debuggerSource.isUndefined()) {
3674 if (!debuggerSource.isObject() ||
3675 debuggerSource.toObject().getClass() != &DebuggerSource_class) {
3676 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
3677 "query object's 'source' property",
3678 "not undefined nor a Debugger.Source object");
3679 return false;
3680 }
3681
3682 source = GetSourceReferent(&debuggerSource.toObject());
3683 }
3684
3685 /* Check for a 'displayURL' property. */
3686 RootedValue displayURL(cx);
3687 if (!GetProperty(cx, query, query, cx->names().displayURL, &displayURL))
3688 return false;
3689 if (!displayURL.isUndefined() && !displayURL.isString()) {
3690 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
3691 "query object's 'displayURL' property",
3692 "neither undefined nor a string");
3693 return false;
3694 }
3695
3696 if (displayURL.isString()) {
3697 displayURLString = displayURL.toString()->ensureLinear(cx);
3698 if (!displayURLString)
3699 return false;
3700 }
3701
3702 /* Check for a 'line' property. */
3703 RootedValue lineProperty(cx);
3704 if (!GetProperty(cx, query, query, cx->names().line, &lineProperty))
3705 return false;
3706 if (lineProperty.isUndefined()) {
3707 hasLine = false;
3708 } else if (lineProperty.isNumber()) {
3709 if (displayURL.isUndefined() && url.isUndefined() && !source) {
3710 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
3711 JSMSG_QUERY_LINE_WITHOUT_URL);
3712 return false;
3713 }
3714 double doubleLine = lineProperty.toNumber();
3715 if (doubleLine <= 0 || (unsigned int) doubleLine != doubleLine) {
3716 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE);
3717 return false;
3718 }
3719 hasLine = true;
3720 line = doubleLine;
3721 } else {
3722 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
3723 "query object's 'line' property",
3724 "neither undefined nor an integer");
3725 return false;
3726 }
3727
3728 /* Check for an 'innermost' property. */
3729 PropertyName* innermostName = cx->names().innermost;
3730 RootedValue innermostProperty(cx);
3731 if (!GetProperty(cx, query, query, innermostName, &innermostProperty))
3732 return false;
3733 innermost = ToBoolean(innermostProperty);
3734 if (innermost) {
3735 /* Technically, we need only check hasLine, but this is clearer. */
3736 if ((displayURL.isUndefined() && url.isUndefined() && !source) || !hasLine) {
3737 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
3738 JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL);
3739 return false;
3740 }
3741 }
3742
3743 return true;
3744 }
3745
3746 /* Set up this ScriptQuery appropriately for a missing query argument. */
omittedQuery()3747 bool omittedQuery() {
3748 url.setUndefined();
3749 hasLine = false;
3750 innermost = false;
3751 displayURLString = nullptr;
3752 return matchAllDebuggeeGlobals();
3753 }
3754
3755 /*
3756 * Search all relevant compartments and the stack for scripts matching
3757 * this query, and append the matching scripts to |vector|.
3758 */
findScripts()3759 bool findScripts() {
3760 if (!prepareQuery())
3761 return false;
3762
3763 JSCompartment* singletonComp = nullptr;
3764 if (compartments.count() == 1)
3765 singletonComp = compartments.all().front();
3766
3767 /* Search each compartment for debuggee scripts. */
3768 MOZ_ASSERT(vector.empty());
3769 oom = false;
3770 IterateScripts(cx->runtime(), singletonComp, this, considerScript);
3771 if (oom) {
3772 ReportOutOfMemory(cx);
3773 return false;
3774 }
3775
3776 /* We cannot touch the gray bits while isHeapBusy, so do this now. */
3777 for (JSScript** i = vector.begin(); i != vector.end(); ++i)
3778 JS::ExposeScriptToActiveJS(*i);
3779
3780 /*
3781 * For most queries, we just accumulate results in 'vector' as we find
3782 * them. But if this is an 'innermost' query, then we've accumulated the
3783 * results in the 'innermostForCompartment' map. In that case, we now need to
3784 * walk that map and populate 'vector'.
3785 */
3786 if (innermost) {
3787 for (CompartmentToScriptMap::Range r = innermostForCompartment.all();
3788 !r.empty();
3789 r.popFront())
3790 {
3791 JS::ExposeScriptToActiveJS(r.front().value());
3792 if (!vector.append(r.front().value())) {
3793 ReportOutOfMemory(cx);
3794 return false;
3795 }
3796 }
3797 }
3798
3799 return true;
3800 }
3801
foundScripts() const3802 Handle<ScriptVector> foundScripts() const {
3803 return vector;
3804 }
3805
3806 private:
3807 /* The context in which we should do our work. */
3808 JSContext* cx;
3809
3810 /* The debugger for which we conduct queries. */
3811 Debugger* debugger;
3812
3813 typedef HashSet<JSCompartment*, DefaultHasher<JSCompartment*>, RuntimeAllocPolicy>
3814 CompartmentSet;
3815
3816 /* A script must be in one of these compartments to match the query. */
3817 CompartmentSet compartments;
3818
3819 /* If this is a string, matching scripts have urls equal to it. */
3820 RootedValue url;
3821
3822 /* url as a C string. */
3823 JSAutoByteString urlCString;
3824
3825 /* If this is a string, matching scripts' sources have displayURLs equal to
3826 * it. */
3827 RootedLinearString displayURLString;
3828
3829 /*
3830 * If this is a source object, matching scripts will have sources
3831 * equal to this instance.
3832 */
3833 RootedScriptSource source;
3834
3835 /* True if the query contained a 'line' property. */
3836 bool hasLine;
3837
3838 /* The line matching scripts must cover. */
3839 unsigned int line;
3840
3841 /* True if the query has an 'innermost' property whose value is true. */
3842 bool innermost;
3843
3844 typedef HashMap<JSCompartment*, JSScript*, DefaultHasher<JSCompartment*>, RuntimeAllocPolicy>
3845 CompartmentToScriptMap;
3846
3847 /*
3848 * For 'innermost' queries, a map from compartments to the innermost script
3849 * we've seen so far in that compartment. (Template instantiation code size
3850 * explosion ho!)
3851 */
3852 CompartmentToScriptMap innermostForCompartment;
3853
3854 /*
3855 * Accumulate the scripts in an Rooted<ScriptVector>, instead of creating
3856 * the JS array as we go, because we mustn't allocate JS objects or GC
3857 * while we use the CellIter.
3858 */
3859 Rooted<ScriptVector> vector;
3860
3861 /* Indicates whether OOM has occurred while matching. */
3862 bool oom;
3863
addCompartment(JSCompartment * comp)3864 bool addCompartment(JSCompartment* comp) {
3865 {
3866 // All scripts in the debuggee compartment must be visible, so
3867 // delazify everything.
3868 AutoCompartment ac(cx, comp);
3869 if (!comp->ensureDelazifyScriptsForDebugger(cx))
3870 return false;
3871 }
3872 return compartments.put(comp);
3873 }
3874
3875 /* Arrange for this ScriptQuery to match only scripts that run in |global|. */
matchSingleGlobal(GlobalObject * global)3876 bool matchSingleGlobal(GlobalObject* global) {
3877 MOZ_ASSERT(compartments.count() == 0);
3878 if (!addCompartment(global->compartment())) {
3879 ReportOutOfMemory(cx);
3880 return false;
3881 }
3882 return true;
3883 }
3884
3885 /*
3886 * Arrange for this ScriptQuery to match all scripts running in debuggee
3887 * globals.
3888 */
matchAllDebuggeeGlobals()3889 bool matchAllDebuggeeGlobals() {
3890 MOZ_ASSERT(compartments.count() == 0);
3891 /* Build our compartment set from the debugger's set of debuggee globals. */
3892 for (WeakGlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) {
3893 if (!addCompartment(r.front()->compartment())) {
3894 ReportOutOfMemory(cx);
3895 return false;
3896 }
3897 }
3898 return true;
3899 }
3900
3901 /*
3902 * Given that parseQuery or omittedQuery has been called, prepare to match
3903 * scripts. Set urlCString and displayURLChars as appropriate.
3904 */
prepareQuery()3905 bool prepareQuery() {
3906 /* Compute urlCString and displayURLChars, if a url or displayURL was
3907 * given respectively. */
3908 if (url.isString()) {
3909 if (!urlCString.encodeLatin1(cx, url.toString()))
3910 return false;
3911 }
3912
3913 return true;
3914 }
3915
considerScript(JSRuntime * rt,void * data,JSScript * script)3916 static void considerScript(JSRuntime* rt, void* data, JSScript* script) {
3917 ScriptQuery* self = static_cast<ScriptQuery*>(data);
3918 self->consider(script);
3919 }
3920
3921 /*
3922 * If |script| matches this query, append it to |vector| or place it in
3923 * |innermostForCompartment|, as appropriate. Set |oom| if an out of memory
3924 * condition occurred.
3925 */
consider(JSScript * script)3926 void consider(JSScript* script) {
3927 // We check for presence of script->code() because it is possible that
3928 // the script was created and thus exposed to GC, but *not* fully
3929 // initialized from fullyInit{FromEmitter,Trivial} due to errors.
3930 if (oom || script->selfHosted() || !script->code())
3931 return;
3932 JSCompartment* compartment = script->compartment();
3933 if (!compartments.has(compartment))
3934 return;
3935 if (urlCString.ptr()) {
3936 bool gotFilename = false;
3937 if (script->filename() && strcmp(script->filename(), urlCString.ptr()) == 0)
3938 gotFilename = true;
3939
3940 bool gotSourceURL = false;
3941 if (!gotFilename && script->scriptSource()->introducerFilename() &&
3942 strcmp(script->scriptSource()->introducerFilename(), urlCString.ptr()) == 0)
3943 {
3944 gotSourceURL = true;
3945 }
3946 if (!gotFilename && !gotSourceURL)
3947 return;
3948 }
3949 if (hasLine) {
3950 if (line < script->lineno() || script->lineno() + GetScriptLineExtent(script) < line)
3951 return;
3952 }
3953 if (displayURLString) {
3954 if (!script->scriptSource() || !script->scriptSource()->hasDisplayURL())
3955 return;
3956
3957 const char16_t* s = script->scriptSource()->displayURL();
3958 if (CompareChars(s, js_strlen(s), displayURLString) != 0)
3959 return;
3960 }
3961 if (source && source != script->sourceObject())
3962 return;
3963
3964 if (innermost) {
3965 /*
3966 * For 'innermost' queries, we don't place scripts in |vector| right
3967 * away; we may later find another script that is nested inside this
3968 * one. Instead, we record the innermost script we've found so far
3969 * for each compartment in innermostForCompartment, and only
3970 * populate |vector| at the bottom of findScripts, when we've
3971 * traversed all the scripts.
3972 *
3973 * So: check this script against the innermost one we've found so
3974 * far (if any), as recorded in innermostForCompartment, and replace
3975 * that if it's better.
3976 */
3977 CompartmentToScriptMap::AddPtr p = innermostForCompartment.lookupForAdd(compartment);
3978 if (p) {
3979 /* Is our newly found script deeper than the last one we found? */
3980 JSScript* incumbent = p->value();
3981 if (StaticScopeChainLength(script->innermostStaticScope()) >
3982 StaticScopeChainLength(incumbent->innermostStaticScope()))
3983 {
3984 p->value() = script;
3985 }
3986 } else {
3987 /*
3988 * This is the first matching script we've encountered for this
3989 * compartment, so it is thus the innermost such script.
3990 */
3991 if (!innermostForCompartment.add(p, compartment, script)) {
3992 oom = true;
3993 return;
3994 }
3995 }
3996 } else {
3997 /* Record this matching script in the results vector. */
3998 if (!vector.append(script)) {
3999 oom = true;
4000 return;
4001 }
4002 }
4003
4004 return;
4005 }
4006 };
4007
4008 /* static */ bool
findScripts(JSContext * cx,unsigned argc,Value * vp)4009 Debugger::findScripts(JSContext* cx, unsigned argc, Value* vp)
4010 {
4011 THIS_DEBUGGER(cx, argc, vp, "findScripts", args, dbg);
4012
4013 ScriptQuery query(cx, dbg);
4014 if (!query.init())
4015 return false;
4016
4017 if (args.length() >= 1) {
4018 RootedObject queryObject(cx, NonNullObject(cx, args[0]));
4019 if (!queryObject || !query.parseQuery(queryObject))
4020 return false;
4021 } else {
4022 if (!query.omittedQuery())
4023 return false;
4024 }
4025
4026 if (!query.findScripts())
4027 return false;
4028
4029 Handle<ScriptVector> scripts(query.foundScripts());
4030 RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, scripts.length()));
4031 if (!result)
4032 return false;
4033
4034 result->ensureDenseInitializedLength(cx, 0, scripts.length());
4035
4036 for (size_t i = 0; i < scripts.length(); i++) {
4037 JSObject* scriptObject = dbg->wrapScript(cx, scripts[i]);
4038 if (!scriptObject)
4039 return false;
4040 result->setDenseElement(i, ObjectValue(*scriptObject));
4041 }
4042
4043 args.rval().setObject(*result);
4044 return true;
4045 }
4046
4047 /*
4048 * A class for parsing 'findObjects' query arguments and searching for objects
4049 * that match the criteria they represent.
4050 */
4051 class MOZ_STACK_CLASS Debugger::ObjectQuery
4052 {
4053 public:
4054 /* Construct an ObjectQuery to use matching scripts for |dbg|. */
ObjectQuery(JSContext * cx,Debugger * dbg)4055 ObjectQuery(JSContext* cx, Debugger* dbg) :
4056 objects(cx), cx(cx), dbg(dbg), className(cx)
4057 { }
4058
4059 /* The vector that we are accumulating results in. */
4060 AutoObjectVector objects;
4061
4062 /*
4063 * Parse the query object |query|, and prepare to match only the objects it
4064 * specifies.
4065 */
parseQuery(HandleObject query)4066 bool parseQuery(HandleObject query) {
4067 /* Check for the 'class' property */
4068 RootedValue cls(cx);
4069 if (!GetProperty(cx, query, query, cx->names().class_, &cls))
4070 return false;
4071 if (!cls.isUndefined()) {
4072 if (!cls.isString()) {
4073 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
4074 "query object's 'class' property",
4075 "neither undefined nor a string");
4076 return false;
4077 }
4078 className = cls;
4079 }
4080 return true;
4081 }
4082
4083 /* Set up this ObjectQuery appropriately for a missing query argument. */
omittedQuery()4084 void omittedQuery() {
4085 className.setUndefined();
4086 }
4087
4088 /*
4089 * Traverse the heap to find all relevant objects and add them to the
4090 * provided vector.
4091 */
findObjects()4092 bool findObjects() {
4093 if (!prepareQuery())
4094 return false;
4095
4096 {
4097 /*
4098 * We can't tolerate the GC moving things around while we're
4099 * searching the heap. Check that nothing we do causes a GC.
4100 */
4101 Maybe<JS::AutoCheckCannotGC> maybeNoGC;
4102 RootedObject dbgObj(cx, dbg->object);
4103 JS::ubi::RootList rootList(cx->runtime(), maybeNoGC);
4104 if (!rootList.init(dbgObj)) {
4105 ReportOutOfMemory(cx);
4106 return false;
4107 }
4108
4109 Traversal traversal(cx->runtime(), *this, maybeNoGC.ref());
4110 if (!traversal.init()) {
4111 ReportOutOfMemory(cx);
4112 return false;
4113 }
4114 traversal.wantNames = false;
4115
4116 return traversal.addStart(JS::ubi::Node(&rootList)) &&
4117 traversal.traverse();
4118 }
4119 }
4120
4121 /*
4122 * |ubi::Node::BreadthFirst| interface.
4123 */
4124 class NodeData {};
4125 typedef JS::ubi::BreadthFirst<ObjectQuery> Traversal;
operator ()(Traversal & traversal,JS::ubi::Node origin,const JS::ubi::Edge & edge,NodeData *,bool first)4126 bool operator() (Traversal& traversal, JS::ubi::Node origin, const JS::ubi::Edge& edge,
4127 NodeData*, bool first)
4128 {
4129 if (!first)
4130 return true;
4131
4132 JS::ubi::Node referent = edge.referent;
4133 /*
4134 * Only follow edges within our set of debuggee compartments; we don't
4135 * care about the heap's subgraphs outside of our debuggee compartments,
4136 * so we abandon the referent. Either (1) there is not a path from this
4137 * non-debuggee node back to a node in our debuggee compartments, and we
4138 * don't need to follow edges to or from this node, or (2) there does
4139 * exist some path from this non-debuggee node back to a node in our
4140 * debuggee compartments. However, if that were true, then the incoming
4141 * cross compartment edge back into a debuggee compartment is already
4142 * listed as an edge in the RootList we started traversal with, and
4143 * therefore we don't need to follow edges to or from this non-debuggee
4144 * node.
4145 */
4146 JSCompartment* comp = referent.compartment();
4147 if (comp && !dbg->isDebuggeeUnbarriered(comp)) {
4148 traversal.abandonReferent();
4149 return true;
4150 }
4151
4152 /*
4153 * If the referent is an object and matches our query's restrictions,
4154 * add it to the vector accumulating results. Skip objects that should
4155 * never be exposed to JS, like ScopeObjects and internal functions.
4156 */
4157
4158 if (!referent.is<JSObject>() || referent.exposeToJS().isUndefined())
4159 return true;
4160
4161 JSObject* obj = referent.as<JSObject>();
4162
4163 if (!className.isUndefined()) {
4164 const char* objClassName = obj->getClass()->name;
4165 if (strcmp(objClassName, classNameCString.ptr()) != 0)
4166 return true;
4167 }
4168
4169 return objects.append(obj);
4170 }
4171
4172 private:
4173 /* The context in which we should do our work. */
4174 JSContext* cx;
4175
4176 /* The debugger for which we conduct queries. */
4177 Debugger* dbg;
4178
4179 /*
4180 * If this is non-null, matching objects will have a class whose name is
4181 * this property.
4182 */
4183 RootedValue className;
4184
4185 /* The className member, as a C string. */
4186 JSAutoByteString classNameCString;
4187
4188 /*
4189 * Given that either omittedQuery or parseQuery has been called, prepare the
4190 * query for matching objects.
4191 */
prepareQuery()4192 bool prepareQuery() {
4193 if (className.isString()) {
4194 if (!classNameCString.encodeLatin1(cx, className.toString()))
4195 return false;
4196 }
4197
4198 return true;
4199 }
4200 };
4201
4202 bool
findObjects(JSContext * cx,unsigned argc,Value * vp)4203 Debugger::findObjects(JSContext* cx, unsigned argc, Value* vp)
4204 {
4205 THIS_DEBUGGER(cx, argc, vp, "findObjects", args, dbg);
4206
4207 ObjectQuery query(cx, dbg);
4208
4209 if (args.length() >= 1) {
4210 RootedObject queryObject(cx, NonNullObject(cx, args[0]));
4211 if (!queryObject || !query.parseQuery(queryObject))
4212 return false;
4213 } else {
4214 query.omittedQuery();
4215 }
4216
4217 if (!query.findObjects())
4218 return false;
4219
4220 size_t length = query.objects.length();
4221 RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
4222 if (!result)
4223 return false;
4224
4225 result->ensureDenseInitializedLength(cx, 0, length);
4226
4227 for (size_t i = 0; i < length; i++) {
4228 RootedValue debuggeeVal(cx, ObjectValue(*query.objects[i]));
4229 if (!dbg->wrapDebuggeeValue(cx, &debuggeeVal))
4230 return false;
4231 result->setDenseElement(i, debuggeeVal);
4232 }
4233
4234 args.rval().setObject(*result);
4235 return true;
4236 }
4237
4238 /* static */ bool
findAllGlobals(JSContext * cx,unsigned argc,Value * vp)4239 Debugger::findAllGlobals(JSContext* cx, unsigned argc, Value* vp)
4240 {
4241 THIS_DEBUGGER(cx, argc, vp, "findAllGlobals", args, dbg);
4242
4243 AutoObjectVector globals(cx);
4244
4245 {
4246 // Accumulate the list of globals before wrapping them, because
4247 // wrapping can GC and collect compartments from under us, while
4248 // iterating.
4249 JS::AutoCheckCannotGC nogc;
4250
4251 for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
4252 if (c->options().invisibleToDebugger())
4253 continue;
4254
4255 c->scheduledForDestruction = false;
4256
4257 GlobalObject* global = c->maybeGlobal();
4258
4259 if (cx->runtime()->isSelfHostingGlobal(global))
4260 continue;
4261
4262 if (global) {
4263 /*
4264 * We pulled |global| out of nowhere, so it's possible that it was
4265 * marked gray by XPConnect. Since we're now exposing it to JS code,
4266 * we need to mark it black.
4267 */
4268 JS::ExposeObjectToActiveJS(global);
4269 if (!globals.append(global))
4270 return false;
4271 }
4272 }
4273 }
4274
4275 RootedObject result(cx, NewDenseEmptyArray(cx));
4276 if (!result)
4277 return false;
4278
4279 for (size_t i = 0; i < globals.length(); i++) {
4280 RootedValue globalValue(cx, ObjectValue(*globals[i]));
4281 if (!dbg->wrapDebuggeeValue(cx, &globalValue))
4282 return false;
4283 if (!NewbornArrayPush(cx, result, globalValue))
4284 return false;
4285 }
4286
4287 args.rval().setObject(*result);
4288 return true;
4289 }
4290
4291 /* static */ bool
makeGlobalObjectReference(JSContext * cx,unsigned argc,Value * vp)4292 Debugger::makeGlobalObjectReference(JSContext* cx, unsigned argc, Value* vp)
4293 {
4294 THIS_DEBUGGER(cx, argc, vp, "makeGlobalObjectReference", args, dbg);
4295 if (!args.requireAtLeast(cx, "Debugger.makeGlobalObjectReference", 1))
4296 return false;
4297
4298 Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
4299 if (!global)
4300 return false;
4301
4302 // If we create a D.O referring to a global in an invisible compartment,
4303 // then from it we can reach function objects, scripts, environments, etc.,
4304 // none of which we're ever supposed to see.
4305 JSCompartment* globalCompartment = global->compartment();
4306 if (globalCompartment->options().invisibleToDebugger()) {
4307 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
4308 JSMSG_DEBUG_INVISIBLE_COMPARTMENT);
4309 return false;
4310 }
4311
4312 args.rval().setObject(*global);
4313 return dbg->wrapDebuggeeValue(cx, args.rval());
4314 }
4315
4316 static bool
DefineProperty(JSContext * cx,HandleObject obj,HandleId id,const char * value,size_t n)4317 DefineProperty(JSContext* cx, HandleObject obj, HandleId id, const char* value, size_t n)
4318 {
4319 JSString* text = JS_NewStringCopyN(cx, value, n);
4320 if (!text)
4321 return false;
4322
4323 RootedValue str(cx, StringValue(text));
4324 return JS_DefinePropertyById(cx, obj, id, str, JSPROP_ENUMERATE);
4325 }
4326
4327 #ifdef JS_TRACE_LOGGING
4328 # ifdef NIGHTLY_BUILD
4329 bool
setupTraceLogger(JSContext * cx,unsigned argc,Value * vp)4330 Debugger::setupTraceLogger(JSContext* cx, unsigned argc, Value* vp)
4331 {
4332 THIS_DEBUGGER(cx, argc, vp, "setupTraceLogger", args, dbg);
4333 if (!args.requireAtLeast(cx, "Debugger.setupTraceLogger", 1))
4334 return false;
4335
4336 RootedObject obj(cx, ToObject(cx, args[0]));
4337 if (!obj)
4338 return false;
4339
4340 AutoIdVector ids(cx);
4341 if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &ids))
4342 return false;
4343
4344 if (ids.length() == 0) {
4345 args.rval().setBoolean(true);
4346 return true;
4347 }
4348
4349 Vector<uint32_t> textIds(cx);
4350 if (!textIds.reserve(ids.length()))
4351 return false;
4352
4353 Vector<bool> values(cx);
4354 if (!values.reserve(ids.length()))
4355 return false;
4356
4357 for (size_t i = 0; i < ids.length(); i++) {
4358 if (!JSID_IS_STRING(ids[i])) {
4359 args.rval().setBoolean(false);
4360 return true;
4361 }
4362
4363 JSString* id = JSID_TO_STRING(ids[i]);
4364 JSLinearString* linear = id->ensureLinear(cx);
4365 if (!linear)
4366 return false;
4367
4368 uint32_t textId = TLStringToTextId(linear);
4369
4370 if (!TLTextIdIsToggable(textId)) {
4371 args.rval().setBoolean(false);
4372 return true;
4373 }
4374
4375 RootedValue v(cx);
4376 if (!GetProperty(cx, obj, obj, ids[i], &v))
4377 return false;
4378
4379 textIds.infallibleAppend(textId);
4380 values.infallibleAppend(ToBoolean(v));
4381 }
4382
4383 MOZ_ASSERT(ids.length() == textIds.length());
4384 MOZ_ASSERT(textIds.length() == values.length());
4385
4386 for (size_t i = 0; i < textIds.length(); i++) {
4387 if (values[i])
4388 TraceLogEnableTextId(cx, textIds[i]);
4389 else
4390 TraceLogDisableTextId(cx, textIds[i]);
4391 }
4392
4393 args.rval().setBoolean(true);
4394 return true;
4395 }
4396
4397 bool
drainTraceLogger(JSContext * cx,unsigned argc,Value * vp)4398 Debugger::drainTraceLogger(JSContext* cx, unsigned argc, Value* vp)
4399 {
4400 THIS_DEBUGGER(cx, argc, vp, "drainTraceLogger", args, dbg);
4401 if (!args.requireAtLeast(cx, "Debugger.drainTraceLogger", 0))
4402 return false;
4403
4404 size_t num;
4405 TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
4406 bool lostEvents = logger->lostEvents(dbg->traceLoggerLastDrainedIteration,
4407 dbg->traceLoggerLastDrainedSize);
4408 EventEntry* events = logger->getEventsStartingAt(&dbg->traceLoggerLastDrainedIteration,
4409 &dbg->traceLoggerLastDrainedSize,
4410 &num);
4411
4412 RootedObject array(cx, NewDenseEmptyArray(cx));
4413 JSAtom* dataAtom = Atomize(cx, "data", strlen("data"));
4414 if (!dataAtom)
4415 return false;
4416 RootedId dataId(cx, AtomToId(dataAtom));
4417
4418 /* Add all events to the array. */
4419 uint32_t index = 0;
4420 for (EventEntry* eventItem = events; eventItem < events + num; eventItem++, index++) {
4421 RootedObject item(cx, NewObjectWithGivenProto(cx, &PlainObject::class_, nullptr));
4422 if (!item)
4423 return false;
4424
4425 const char* eventText = logger->eventText(eventItem->textId);
4426 if (!DefineProperty(cx, item, dataId, eventText, strlen(eventText)))
4427 return false;
4428
4429 RootedValue obj(cx, ObjectValue(*item));
4430 if (!JS_DefineElement(cx, array, index, obj, JSPROP_ENUMERATE))
4431 return false;
4432 }
4433
4434 /* Add "lostEvents" indicating if there are events that were lost. */
4435 RootedValue lost(cx, BooleanValue(lostEvents));
4436 if (!JS_DefineProperty(cx, array, "lostEvents", lost, JSPROP_ENUMERATE))
4437 return false;
4438
4439 args.rval().setObject(*array);
4440
4441 return true;
4442 }
4443 # endif // NIGHTLY_BUILD
4444
4445 bool
setupTraceLoggerScriptCalls(JSContext * cx,unsigned argc,Value * vp)4446 Debugger::setupTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp)
4447 {
4448 THIS_DEBUGGER(cx, argc, vp, "setupTraceLoggerScriptCalls", args, dbg);
4449 if (!args.requireAtLeast(cx, "Debugger.setupTraceLoggerScriptCalls", 0))
4450 return false;
4451
4452 TraceLogEnableTextId(cx, TraceLogger_Scripts);
4453 TraceLogEnableTextId(cx, TraceLogger_InlinedScripts);
4454 TraceLogDisableTextId(cx, TraceLogger_AnnotateScripts);
4455
4456 args.rval().setBoolean(true);
4457
4458 return true;
4459 }
4460
4461 bool
startTraceLogger(JSContext * cx,unsigned argc,Value * vp)4462 Debugger::startTraceLogger(JSContext* cx, unsigned argc, Value* vp)
4463 {
4464 THIS_DEBUGGER(cx, argc, vp, "startTraceLogger", args, dbg);
4465 if (!args.requireAtLeast(cx, "Debugger.startTraceLogger", 0))
4466 return false;
4467
4468 TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
4469 if (!TraceLoggerEnable(logger, cx))
4470 return false;
4471
4472 args.rval().setUndefined();
4473
4474 return true;
4475 }
4476
4477 bool
endTraceLogger(JSContext * cx,unsigned argc,Value * vp)4478 Debugger::endTraceLogger(JSContext* cx, unsigned argc, Value* vp)
4479 {
4480 THIS_DEBUGGER(cx, argc, vp, "endTraceLogger", args, dbg);
4481 if (!args.requireAtLeast(cx, "Debugger.endTraceLogger", 0))
4482 return false;
4483
4484 TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
4485 TraceLoggerDisable(logger);
4486
4487 args.rval().setUndefined();
4488
4489 return true;
4490 }
4491
4492 bool
drainTraceLoggerScriptCalls(JSContext * cx,unsigned argc,Value * vp)4493 Debugger::drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp)
4494 {
4495 THIS_DEBUGGER(cx, argc, vp, "drainTraceLoggerScriptCalls", args, dbg);
4496 if (!args.requireAtLeast(cx, "Debugger.drainTraceLoggerScriptCalls", 0))
4497 return false;
4498
4499 size_t num;
4500 TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
4501 bool lostEvents = logger->lostEvents(dbg->traceLoggerScriptedCallsLastDrainedIteration,
4502 dbg->traceLoggerScriptedCallsLastDrainedSize);
4503 EventEntry* events = logger->getEventsStartingAt(
4504 &dbg->traceLoggerScriptedCallsLastDrainedIteration,
4505 &dbg->traceLoggerScriptedCallsLastDrainedSize,
4506 &num);
4507
4508 RootedObject array(cx, NewDenseEmptyArray(cx));
4509 RootedId fileNameId(cx, AtomToId(cx->names().fileName));
4510 RootedId lineNumberId(cx, AtomToId(cx->names().lineNumber));
4511 RootedId columnNumberId(cx, AtomToId(cx->names().columnNumber));
4512 JSAtom* logTypeAtom = Atomize(cx, "logType", strlen("logType"));
4513 if (!logTypeAtom)
4514 return false;
4515 RootedId logTypeId(cx, AtomToId(logTypeAtom));
4516
4517 /* Add all events to the array. */
4518 uint32_t index = 0;
4519 for (EventEntry* eventItem = events; eventItem < events + num; eventItem++) {
4520 RootedObject item(cx, NewObjectWithGivenProto(cx, &PlainObject::class_, nullptr));
4521 if (!item)
4522 return false;
4523
4524 uint32_t textId = eventItem->textId;
4525 if (textId != TraceLogger_Stop && !logger->textIdIsScriptEvent(textId))
4526 continue;
4527
4528 const char* type = (textId == TraceLogger_Stop) ? "Stop" : "Script";
4529 if (!DefineProperty(cx, item, logTypeId, type, strlen(type)))
4530 return false;
4531
4532 if (textId != TraceLogger_Stop) {
4533 const char* filename;
4534 const char* lineno;
4535 const char* colno;
4536 size_t filename_len, lineno_len, colno_len;
4537 logger->extractScriptDetails(textId, &filename, &filename_len, &lineno, &lineno_len,
4538 &colno, &colno_len);
4539
4540 if (!DefineProperty(cx, item, fileNameId, filename, filename_len))
4541 return false;
4542 if (!DefineProperty(cx, item, lineNumberId, lineno, lineno_len))
4543 return false;
4544 if (!DefineProperty(cx, item, columnNumberId, colno, colno_len))
4545 return false;
4546 }
4547
4548 RootedValue obj(cx, ObjectValue(*item));
4549 if (!JS_DefineElement(cx, array, index, obj, JSPROP_ENUMERATE))
4550 return false;
4551
4552 index++;
4553 }
4554
4555 /* Add "lostEvents" indicating if there are events that were lost. */
4556 RootedValue lost(cx, BooleanValue(lostEvents));
4557 if (!JS_DefineProperty(cx, array, "lostEvents", lost, JSPROP_ENUMERATE))
4558 return false;
4559
4560 args.rval().setObject(*array);
4561
4562 return true;
4563 }
4564 #endif
4565
4566 const JSPropertySpec Debugger::properties[] = {
4567 JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0),
4568 JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement,
4569 Debugger::setOnDebuggerStatement, 0),
4570 JS_PSGS("onExceptionUnwind", Debugger::getOnExceptionUnwind,
4571 Debugger::setOnExceptionUnwind, 0),
4572 JS_PSGS("onNewScript", Debugger::getOnNewScript, Debugger::setOnNewScript, 0),
4573 JS_PSGS("onNewPromise", Debugger::getOnNewPromise, Debugger::setOnNewPromise, 0),
4574 JS_PSGS("onPromiseSettled", Debugger::getOnPromiseSettled, Debugger::setOnPromiseSettled, 0),
4575 JS_PSGS("onEnterFrame", Debugger::getOnEnterFrame, Debugger::setOnEnterFrame, 0),
4576 JS_PSGS("onNewGlobalObject", Debugger::getOnNewGlobalObject, Debugger::setOnNewGlobalObject, 0),
4577 JS_PSGS("uncaughtExceptionHook", Debugger::getUncaughtExceptionHook,
4578 Debugger::setUncaughtExceptionHook, 0),
4579 JS_PSGS("allowUnobservedAsmJS", Debugger::getAllowUnobservedAsmJS,
4580 Debugger::setAllowUnobservedAsmJS, 0),
4581 JS_PSGS("collectCoverageInfo", Debugger::getCollectCoverageInfo,
4582 Debugger::setCollectCoverageInfo, 0),
4583 JS_PSG("memory", Debugger::getMemory, 0),
4584 JS_PSGS("onIonCompilation", Debugger::getOnIonCompilation, Debugger::setOnIonCompilation, 0),
4585 JS_PS_END
4586 };
4587 const JSFunctionSpec Debugger::methods[] = {
4588 JS_FN("addDebuggee", Debugger::addDebuggee, 1, 0),
4589 JS_FN("addAllGlobalsAsDebuggees", Debugger::addAllGlobalsAsDebuggees, 0, 0),
4590 JS_FN("removeDebuggee", Debugger::removeDebuggee, 1, 0),
4591 JS_FN("removeAllDebuggees", Debugger::removeAllDebuggees, 0, 0),
4592 JS_FN("hasDebuggee", Debugger::hasDebuggee, 1, 0),
4593 JS_FN("getDebuggees", Debugger::getDebuggees, 0, 0),
4594 JS_FN("getNewestFrame", Debugger::getNewestFrame, 0, 0),
4595 JS_FN("clearAllBreakpoints", Debugger::clearAllBreakpoints, 0, 0),
4596 JS_FN("findScripts", Debugger::findScripts, 1, 0),
4597 JS_FN("findObjects", Debugger::findObjects, 1, 0),
4598 JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0),
4599 JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0),
4600 #ifdef JS_TRACE_LOGGING
4601 JS_FN("setupTraceLoggerScriptCalls", Debugger::setupTraceLoggerScriptCalls, 0, 0),
4602 JS_FN("drainTraceLoggerScriptCalls", Debugger::drainTraceLoggerScriptCalls, 0, 0),
4603 JS_FN("startTraceLogger", Debugger::startTraceLogger, 0, 0),
4604 JS_FN("endTraceLogger", Debugger::endTraceLogger, 0, 0),
4605 # ifdef NIGHTLY_BUILD
4606 JS_FN("setupTraceLogger", Debugger::setupTraceLogger, 1, 0),
4607 JS_FN("drainTraceLogger", Debugger::drainTraceLogger, 0, 0),
4608 # endif
4609 #endif
4610 JS_FS_END
4611 };
4612
4613
4614 /*** Debugger.Script *****************************************************************************/
4615
4616 static inline JSScript*
GetScriptReferent(JSObject * obj)4617 GetScriptReferent(JSObject* obj)
4618 {
4619 MOZ_ASSERT(obj->getClass() == &DebuggerScript_class);
4620 return static_cast<JSScript*>(obj->as<NativeObject>().getPrivate());
4621 }
4622
4623 void
DebuggerScript_trace(JSTracer * trc,JSObject * obj)4624 DebuggerScript_trace(JSTracer* trc, JSObject* obj)
4625 {
4626 /* This comes from a private pointer, so no barrier needed. */
4627 if (JSScript* script = GetScriptReferent(obj)) {
4628 TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &script, "Debugger.Script referent");
4629 obj->as<NativeObject>().setPrivateUnbarriered(script);
4630 }
4631 }
4632
4633 const Class DebuggerScript_class = {
4634 "Script",
4635 JSCLASS_HAS_PRIVATE |
4636 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT),
4637 nullptr, nullptr, nullptr, nullptr,
4638 nullptr, nullptr, nullptr, nullptr,
4639 nullptr, /* call */
4640 nullptr, /* hasInstance */
4641 nullptr, /* construct */
4642 DebuggerScript_trace
4643 };
4644
4645 JSObject*
newDebuggerScript(JSContext * cx,HandleScript script)4646 Debugger::newDebuggerScript(JSContext* cx, HandleScript script)
4647 {
4648 assertSameCompartment(cx, object.get());
4649
4650 RootedObject proto(cx, &object->getReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO).toObject());
4651 MOZ_ASSERT(proto);
4652 NativeObject* scriptobj = NewNativeObjectWithGivenProto(cx, &DebuggerScript_class,
4653 proto, TenuredObject);
4654 if (!scriptobj)
4655 return nullptr;
4656 scriptobj->setReservedSlot(JSSLOT_DEBUGSCRIPT_OWNER, ObjectValue(*object));
4657 scriptobj->setPrivateGCThing(script);
4658
4659 return scriptobj;
4660 }
4661
4662 JSObject*
wrapScript(JSContext * cx,HandleScript script)4663 Debugger::wrapScript(JSContext* cx, HandleScript script)
4664 {
4665 assertSameCompartment(cx, object.get());
4666 MOZ_ASSERT(cx->compartment() != script->compartment());
4667 DependentAddPtr<ScriptWeakMap> p(cx, scripts, script);
4668 if (!p) {
4669 JSObject* scriptobj = newDebuggerScript(cx, script);
4670 if (!scriptobj)
4671 return nullptr;
4672
4673 if (!p.add(cx, scripts, script, scriptobj))
4674 return nullptr;
4675
4676 CrossCompartmentKey key(CrossCompartmentKey::DebuggerScript, object, script);
4677 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*scriptobj))) {
4678 scripts.remove(script);
4679 ReportOutOfMemory(cx);
4680 return nullptr;
4681 }
4682 }
4683
4684 MOZ_ASSERT(GetScriptReferent(p->value()) == script);
4685 return p->value();
4686 }
4687
4688 static JSObject*
DebuggerScript_check(JSContext * cx,const Value & v,const char * clsname,const char * fnname)4689 DebuggerScript_check(JSContext* cx, const Value& v, const char* clsname, const char* fnname)
4690 {
4691 JSObject* thisobj = NonNullObject(cx, v);
4692 if (!thisobj)
4693 return nullptr;
4694 if (thisobj->getClass() != &DebuggerScript_class) {
4695 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
4696 clsname, fnname, thisobj->getClass()->name);
4697 return nullptr;
4698 }
4699
4700 /*
4701 * Check for Debugger.Script.prototype, which is of class DebuggerScript_class
4702 * but whose script is null.
4703 */
4704 if (!GetScriptReferent(thisobj)) {
4705 MOZ_ASSERT(!GetScriptReferent(thisobj));
4706 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
4707 clsname, fnname, "prototype object");
4708 return nullptr;
4709 }
4710
4711 return thisobj;
4712 }
4713
4714 static JSObject*
DebuggerScript_checkThis(JSContext * cx,const CallArgs & args,const char * fnname)4715 DebuggerScript_checkThis(JSContext* cx, const CallArgs& args, const char* fnname)
4716 {
4717 return DebuggerScript_check(cx, args.thisv(), "Debugger.Script", fnname);
4718 }
4719
4720 #define THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, fnname, args, obj, script) \
4721 CallArgs args = CallArgsFromVp(argc, vp); \
4722 RootedObject obj(cx, DebuggerScript_checkThis(cx, args, fnname)); \
4723 if (!obj) \
4724 return false; \
4725 Rooted<JSScript*> script(cx, GetScriptReferent(obj))
4726
4727 static bool
DebuggerScript_getDisplayName(JSContext * cx,unsigned argc,Value * vp)4728 DebuggerScript_getDisplayName(JSContext* cx, unsigned argc, Value* vp)
4729 {
4730 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get displayName)", args, obj, script);
4731 Debugger* dbg = Debugger::fromChildJSObject(obj);
4732
4733 JSFunction* func = script->functionNonDelazifying();
4734 JSString* name = func ? func->displayAtom() : nullptr;
4735 if (!name) {
4736 args.rval().setUndefined();
4737 return true;
4738 }
4739
4740 RootedValue namev(cx, StringValue(name));
4741 if (!dbg->wrapDebuggeeValue(cx, &namev))
4742 return false;
4743 args.rval().set(namev);
4744 return true;
4745 }
4746
4747 static bool
DebuggerScript_getUrl(JSContext * cx,unsigned argc,Value * vp)4748 DebuggerScript_getUrl(JSContext* cx, unsigned argc, Value* vp)
4749 {
4750 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get url)", args, obj, script);
4751
4752 if (script->filename()) {
4753 JSString* str;
4754 if (script->scriptSource()->introducerFilename())
4755 str = NewStringCopyZ<CanGC>(cx, script->scriptSource()->introducerFilename());
4756 else
4757 str = NewStringCopyZ<CanGC>(cx, script->filename());
4758 if (!str)
4759 return false;
4760 args.rval().setString(str);
4761 } else {
4762 args.rval().setNull();
4763 }
4764 return true;
4765 }
4766
4767 static bool
DebuggerScript_getStartLine(JSContext * cx,unsigned argc,Value * vp)4768 DebuggerScript_getStartLine(JSContext* cx, unsigned argc, Value* vp)
4769 {
4770 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get startLine)", args, obj, script);
4771 args.rval().setNumber(uint32_t(script->lineno()));
4772 return true;
4773 }
4774
4775 static bool
DebuggerScript_getLineCount(JSContext * cx,unsigned argc,Value * vp)4776 DebuggerScript_getLineCount(JSContext* cx, unsigned argc, Value* vp)
4777 {
4778 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get lineCount)", args, obj, script);
4779
4780 unsigned maxLine = GetScriptLineExtent(script);
4781 args.rval().setNumber(double(maxLine));
4782 return true;
4783 }
4784
4785 static bool
DebuggerScript_getSource(JSContext * cx,unsigned argc,Value * vp)4786 DebuggerScript_getSource(JSContext* cx, unsigned argc, Value* vp)
4787 {
4788 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get source)", args, obj, script);
4789 Debugger* dbg = Debugger::fromChildJSObject(obj);
4790
4791 RootedScriptSource source(cx, &UncheckedUnwrap(script->sourceObject())->as<ScriptSourceObject>());
4792 RootedObject sourceObject(cx, dbg->wrapSource(cx, source));
4793 if (!sourceObject)
4794 return false;
4795
4796 args.rval().setObject(*sourceObject);
4797 return true;
4798 }
4799
4800 static bool
DebuggerScript_getSourceStart(JSContext * cx,unsigned argc,Value * vp)4801 DebuggerScript_getSourceStart(JSContext* cx, unsigned argc, Value* vp)
4802 {
4803 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceStart)", args, obj, script);
4804 args.rval().setNumber(uint32_t(script->sourceStart()));
4805 return true;
4806 }
4807
4808 static bool
DebuggerScript_getSourceLength(JSContext * cx,unsigned argc,Value * vp)4809 DebuggerScript_getSourceLength(JSContext* cx, unsigned argc, Value* vp)
4810 {
4811 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceEnd)", args, obj, script);
4812 args.rval().setNumber(uint32_t(script->sourceEnd() - script->sourceStart()));
4813 return true;
4814 }
4815
4816 static bool
DebuggerScript_getGlobal(JSContext * cx,unsigned argc,Value * vp)4817 DebuggerScript_getGlobal(JSContext* cx, unsigned argc, Value* vp)
4818 {
4819 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get global)", args, obj, script);
4820 Debugger* dbg = Debugger::fromChildJSObject(obj);
4821
4822 RootedValue v(cx, ObjectValue(script->global()));
4823 if (!dbg->wrapDebuggeeValue(cx, &v))
4824 return false;
4825 args.rval().set(v);
4826 return true;
4827 }
4828
4829 static bool
DebuggerScript_getChildScripts(JSContext * cx,unsigned argc,Value * vp)4830 DebuggerScript_getChildScripts(JSContext* cx, unsigned argc, Value* vp)
4831 {
4832 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getChildScripts", args, obj, script);
4833 Debugger* dbg = Debugger::fromChildJSObject(obj);
4834
4835 RootedObject result(cx, NewDenseEmptyArray(cx));
4836 if (!result)
4837 return false;
4838 if (script->hasObjects()) {
4839 /*
4840 * script->savedCallerFun indicates that this is a direct eval script
4841 * and the calling function is stored as script->objects()->vector[0].
4842 * It is not really a child script of this script, so skip it using
4843 * innerObjectsStart().
4844 */
4845 ObjectArray* objects = script->objects();
4846 RootedFunction fun(cx);
4847 RootedScript funScript(cx);
4848 RootedObject obj(cx), s(cx);
4849 for (uint32_t i = script->innerObjectsStart(); i < objects->length; i++) {
4850 obj = objects->vector[i];
4851 if (obj->is<JSFunction>()) {
4852 fun = &obj->as<JSFunction>();
4853 // The inner function could be an asm.js native.
4854 if (fun->isNative())
4855 continue;
4856 funScript = GetOrCreateFunctionScript(cx, fun);
4857 if (!funScript)
4858 return false;
4859 s = dbg->wrapScript(cx, funScript);
4860 if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s)))
4861 return false;
4862 }
4863 }
4864 }
4865 args.rval().setObject(*result);
4866 return true;
4867 }
4868
4869 static bool
ScriptOffset(JSContext * cx,JSScript * script,const Value & v,size_t * offsetp)4870 ScriptOffset(JSContext* cx, JSScript* script, const Value& v, size_t* offsetp)
4871 {
4872 double d;
4873 size_t off;
4874
4875 bool ok = v.isNumber();
4876 if (ok) {
4877 d = v.toNumber();
4878 off = size_t(d);
4879 }
4880 if (!ok || off != d || !IsValidBytecodeOffset(cx, script, off)) {
4881 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_OFFSET);
4882 return false;
4883 }
4884 *offsetp = off;
4885 return true;
4886 }
4887
4888 namespace {
4889
4890 class BytecodeRangeWithPosition : private BytecodeRange
4891 {
4892 public:
4893 using BytecodeRange::empty;
4894 using BytecodeRange::frontPC;
4895 using BytecodeRange::frontOpcode;
4896 using BytecodeRange::frontOffset;
4897
BytecodeRangeWithPosition(JSContext * cx,JSScript * script)4898 BytecodeRangeWithPosition(JSContext* cx, JSScript* script)
4899 : BytecodeRange(cx, script), lineno(script->lineno()), column(0),
4900 sn(script->notes()), snpc(script->code()), isEntryPoint(false)
4901 {
4902 if (!SN_IS_TERMINATOR(sn))
4903 snpc += SN_DELTA(sn);
4904 updatePosition();
4905 while (frontPC() != script->main())
4906 popFront();
4907 isEntryPoint = true;
4908 }
4909
popFront()4910 void popFront() {
4911 BytecodeRange::popFront();
4912 if (empty())
4913 isEntryPoint = false;
4914 else
4915 updatePosition();
4916 }
4917
frontLineNumber() const4918 size_t frontLineNumber() const { return lineno; }
frontColumnNumber() const4919 size_t frontColumnNumber() const { return column; }
4920
4921 // Entry points are restricted to bytecode offsets that have an
4922 // explicit mention in the line table. This restriction avoids a
4923 // number of failing cases caused by some instructions not having
4924 // sensible (to the user) line numbers, and it is one way to
4925 // implement the idea that the bytecode emitter should tell the
4926 // debugger exactly which offsets represent "interesting" (to the
4927 // user) places to stop.
frontIsEntryPoint() const4928 bool frontIsEntryPoint() const { return isEntryPoint; }
4929
4930 private:
updatePosition()4931 void updatePosition() {
4932 /*
4933 * Determine the current line number by reading all source notes up to
4934 * and including the current offset.
4935 */
4936 jsbytecode *lastLinePC = nullptr;
4937 while (!SN_IS_TERMINATOR(sn) && snpc <= frontPC()) {
4938 SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
4939 if (type == SRC_COLSPAN) {
4940 ptrdiff_t colspan = SN_OFFSET_TO_COLSPAN(GetSrcNoteOffset(sn, 0));
4941 MOZ_ASSERT(ptrdiff_t(column) + colspan >= 0);
4942 column += colspan;
4943 lastLinePC = snpc;
4944 } else if (type == SRC_SETLINE) {
4945 lineno = size_t(GetSrcNoteOffset(sn, 0));
4946 column = 0;
4947 lastLinePC = snpc;
4948 } else if (type == SRC_NEWLINE) {
4949 lineno++;
4950 column = 0;
4951 lastLinePC = snpc;
4952 }
4953
4954 sn = SN_NEXT(sn);
4955 snpc += SN_DELTA(sn);
4956 }
4957 isEntryPoint = lastLinePC == frontPC();
4958 }
4959
4960 size_t lineno;
4961 size_t column;
4962 jssrcnote* sn;
4963 jsbytecode* snpc;
4964 bool isEntryPoint;
4965 };
4966
4967 /*
4968 * FlowGraphSummary::populate(cx, script) computes a summary of script's
4969 * control flow graph used by DebuggerScript_{getAllOffsets,getLineOffsets}.
4970 *
4971 * An instruction on a given line is an entry point for that line if it can be
4972 * reached from (an instruction on) a different line. We distinguish between the
4973 * following cases:
4974 * - hasNoEdges:
4975 * The instruction cannot be reached, so the instruction is not an entry
4976 * point for the line it is on.
4977 * - hasSingleEdge:
4978 * - hasMultipleEdgesFromSingleLine:
4979 * The instruction can be reached from a single line. If this line is
4980 * different from the line the instruction is on, the instruction is an
4981 * entry point for that line.
4982 * - hasMultipleEdgesFromMultipleLines:
4983 * The instruction can be reached from multiple lines. At least one of
4984 * these lines is guaranteed to be different from the line the instruction
4985 * is on, so the instruction is an entry point for that line.
4986 *
4987 * Similarly, an instruction on a given position (line/column pair) is an
4988 * entry point for that position if it can be reached from (an instruction on) a
4989 * different position. Again, we distinguish between the following cases:
4990 * - hasNoEdges:
4991 * The instruction cannot be reached, so the instruction is not an entry
4992 * point for the position it is on.
4993 * - hasSingleEdge:
4994 * The instruction can be reached from a single position. If this line is
4995 * different from the position the instruction is on, the instruction is
4996 * an entry point for that position.
4997 * - hasMultipleEdgesFromSingleLine:
4998 * - hasMultipleEdgesFromMultipleLines:
4999 * The instruction can be reached from multiple positions. At least one
5000 * of these positions is guaranteed to be different from the position the
5001 * instruction is on, so the instruction is an entry point for that
5002 * position.
5003 */
5004 class FlowGraphSummary {
5005 public:
5006 class Entry {
5007 public:
createWithNoEdges()5008 static Entry createWithNoEdges() {
5009 return Entry(SIZE_MAX, 0);
5010 }
5011
createWithSingleEdge(size_t lineno,size_t column)5012 static Entry createWithSingleEdge(size_t lineno, size_t column) {
5013 return Entry(lineno, column);
5014 }
5015
createWithMultipleEdgesFromSingleLine(size_t lineno)5016 static Entry createWithMultipleEdgesFromSingleLine(size_t lineno) {
5017 return Entry(lineno, SIZE_MAX);
5018 }
5019
createWithMultipleEdgesFromMultipleLines()5020 static Entry createWithMultipleEdgesFromMultipleLines() {
5021 return Entry(SIZE_MAX, SIZE_MAX);
5022 }
5023
Entry()5024 Entry() {}
5025
hasNoEdges() const5026 bool hasNoEdges() const {
5027 return lineno_ == SIZE_MAX && column_ != SIZE_MAX;
5028 }
5029
hasSingleEdge() const5030 bool hasSingleEdge() const {
5031 return lineno_ != SIZE_MAX && column_ != SIZE_MAX;
5032 }
5033
hasMultipleEdgesFromSingleLine() const5034 bool hasMultipleEdgesFromSingleLine() const {
5035 return lineno_ != SIZE_MAX && column_ == SIZE_MAX;
5036 }
5037
hasMultipleEdgesFromMultipleLines() const5038 bool hasMultipleEdgesFromMultipleLines() const {
5039 return lineno_ == SIZE_MAX && column_ == SIZE_MAX;
5040 }
5041
operator ==(const Entry & other) const5042 bool operator==(const Entry& other) const {
5043 return lineno_ == other.lineno_ && column_ == other.column_;
5044 }
5045
operator !=(const Entry & other) const5046 bool operator!=(const Entry& other) const {
5047 return lineno_ != other.lineno_ || column_ != other.column_;
5048 }
5049
lineno() const5050 size_t lineno() const {
5051 return lineno_;
5052 }
5053
column() const5054 size_t column() const {
5055 return column_;
5056 }
5057
5058 private:
Entry(size_t lineno,size_t column)5059 Entry(size_t lineno, size_t column) : lineno_(lineno), column_(column) {}
5060
5061 size_t lineno_;
5062 size_t column_;
5063 };
5064
FlowGraphSummary(JSContext * cx)5065 explicit FlowGraphSummary(JSContext* cx) : entries_(cx) {}
5066
operator [](size_t index)5067 Entry& operator[](size_t index) {
5068 return entries_[index];
5069 }
5070
populate(JSContext * cx,JSScript * script)5071 bool populate(JSContext* cx, JSScript* script) {
5072 if (!entries_.growBy(script->length()))
5073 return false;
5074 unsigned mainOffset = script->pcToOffset(script->main());
5075 entries_[mainOffset] = Entry::createWithMultipleEdgesFromMultipleLines();
5076 for (size_t i = mainOffset + 1; i < script->length(); i++)
5077 entries_[i] = Entry::createWithNoEdges();
5078
5079 size_t prevLineno = script->lineno();
5080 size_t prevColumn = 0;
5081 JSOp prevOp = JSOP_NOP;
5082 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
5083 size_t lineno = r.frontLineNumber();
5084 size_t column = r.frontColumnNumber();
5085 JSOp op = r.frontOpcode();
5086
5087 if (FlowsIntoNext(prevOp))
5088 addEdge(prevLineno, prevColumn, r.frontOffset());
5089
5090 if (CodeSpec[op].type() == JOF_JUMP) {
5091 addEdge(lineno, column, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC()));
5092 } else if (op == JSOP_TABLESWITCH) {
5093 jsbytecode* pc = r.frontPC();
5094 size_t offset = r.frontOffset();
5095 ptrdiff_t step = JUMP_OFFSET_LEN;
5096 size_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
5097 pc += step;
5098 addEdge(lineno, column, defaultOffset);
5099
5100 int32_t low = GET_JUMP_OFFSET(pc);
5101 pc += JUMP_OFFSET_LEN;
5102 int ncases = GET_JUMP_OFFSET(pc) - low + 1;
5103 pc += JUMP_OFFSET_LEN;
5104
5105 for (int i = 0; i < ncases; i++) {
5106 size_t target = offset + GET_JUMP_OFFSET(pc);
5107 addEdge(lineno, column, target);
5108 pc += step;
5109 }
5110 }
5111
5112 prevLineno = lineno;
5113 prevColumn = column;
5114 prevOp = op;
5115 }
5116
5117 return true;
5118 }
5119
5120 private:
addEdge(size_t sourceLineno,size_t sourceColumn,size_t targetOffset)5121 void addEdge(size_t sourceLineno, size_t sourceColumn, size_t targetOffset) {
5122 if (entries_[targetOffset].hasNoEdges())
5123 entries_[targetOffset] = Entry::createWithSingleEdge(sourceLineno, sourceColumn);
5124 else if (entries_[targetOffset].lineno() != sourceLineno)
5125 entries_[targetOffset] = Entry::createWithMultipleEdgesFromMultipleLines();
5126 else if (entries_[targetOffset].column() != sourceColumn)
5127 entries_[targetOffset] = Entry::createWithMultipleEdgesFromSingleLine(sourceLineno);
5128 }
5129
5130 Vector<Entry> entries_;
5131 };
5132
5133 } /* anonymous namespace */
5134
5135 static bool
DebuggerScript_getOffsetLocation(JSContext * cx,unsigned argc,Value * vp)5136 DebuggerScript_getOffsetLocation(JSContext* cx, unsigned argc, Value* vp)
5137 {
5138 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetLocation", args, obj, script);
5139 if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetLocation", 1))
5140 return false;
5141 size_t offset;
5142 if (!ScriptOffset(cx, script, args[0], &offset))
5143 return false;
5144
5145 FlowGraphSummary flowData(cx);
5146 if (!flowData.populate(cx, script))
5147 return false;
5148
5149 RootedPlainObject result(cx, NewBuiltinClassInstance<PlainObject>(cx));
5150 if (!result)
5151 return false;
5152
5153 BytecodeRangeWithPosition r(cx, script);
5154 while (!r.empty() && r.frontOffset() < offset)
5155 r.popFront();
5156
5157 RootedId id(cx, NameToId(cx->names().lineNumber));
5158 RootedValue value(cx, NumberValue(r.frontLineNumber()));
5159 if (!DefineProperty(cx, result, id, value))
5160 return false;
5161
5162 value = NumberValue(r.frontColumnNumber());
5163 if (!DefineProperty(cx, result, cx->names().columnNumber, value))
5164 return false;
5165
5166 // The same entry point test that is used by getAllColumnOffsets.
5167 bool isEntryPoint = (r.frontIsEntryPoint() &&
5168 !flowData[offset].hasNoEdges() &&
5169 (flowData[offset].lineno() != r.frontLineNumber() ||
5170 flowData[offset].column() != r.frontColumnNumber()));
5171 value.setBoolean(isEntryPoint);
5172 if (!DefineProperty(cx, result, cx->names().isEntryPoint, value))
5173 return false;
5174
5175 args.rval().setObject(*result);
5176 return true;
5177 }
5178
5179 static bool
DebuggerScript_getAllOffsets(JSContext * cx,unsigned argc,Value * vp)5180 DebuggerScript_getAllOffsets(JSContext* cx, unsigned argc, Value* vp)
5181 {
5182 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script);
5183
5184 /*
5185 * First pass: determine which offsets in this script are jump targets and
5186 * which line numbers jump to them.
5187 */
5188 FlowGraphSummary flowData(cx);
5189 if (!flowData.populate(cx, script))
5190 return false;
5191
5192 /* Second pass: build the result array. */
5193 RootedObject result(cx, NewDenseEmptyArray(cx));
5194 if (!result)
5195 return false;
5196 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
5197 if (!r.frontIsEntryPoint())
5198 continue;
5199
5200 size_t offset = r.frontOffset();
5201 size_t lineno = r.frontLineNumber();
5202
5203 /* Make a note, if the current instruction is an entry point for the current line. */
5204 if (!flowData[offset].hasNoEdges() && flowData[offset].lineno() != lineno) {
5205 /* Get the offsets array for this line. */
5206 RootedObject offsets(cx);
5207 RootedValue offsetsv(cx);
5208
5209 RootedId id(cx, INT_TO_JSID(lineno));
5210
5211 bool found;
5212 if (!HasOwnProperty(cx, result, id, &found))
5213 return false;
5214 if (found && !GetProperty(cx, result, result, id, &offsetsv))
5215 return false;
5216
5217 if (offsetsv.isObject()) {
5218 offsets = &offsetsv.toObject();
5219 } else {
5220 MOZ_ASSERT(offsetsv.isUndefined());
5221
5222 /*
5223 * Create an empty offsets array for this line.
5224 * Store it in the result array.
5225 */
5226 RootedId id(cx);
5227 RootedValue v(cx, NumberValue(lineno));
5228 offsets = NewDenseEmptyArray(cx);
5229 if (!offsets ||
5230 !ValueToId<CanGC>(cx, v, &id))
5231 {
5232 return false;
5233 }
5234
5235 RootedValue value(cx, ObjectValue(*offsets));
5236 if (!DefineProperty(cx, result, id, value))
5237 return false;
5238 }
5239
5240 /* Append the current offset to the offsets array. */
5241 if (!NewbornArrayPush(cx, offsets, NumberValue(offset)))
5242 return false;
5243 }
5244 }
5245
5246 args.rval().setObject(*result);
5247 return true;
5248 }
5249
5250 static bool
DebuggerScript_getAllColumnOffsets(JSContext * cx,unsigned argc,Value * vp)5251 DebuggerScript_getAllColumnOffsets(JSContext* cx, unsigned argc, Value* vp)
5252 {
5253 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllColumnOffsets", args, obj, script);
5254
5255 /*
5256 * First pass: determine which offsets in this script are jump targets and
5257 * which positions jump to them.
5258 */
5259 FlowGraphSummary flowData(cx);
5260 if (!flowData.populate(cx, script))
5261 return false;
5262
5263 /* Second pass: build the result array. */
5264 RootedObject result(cx, NewDenseEmptyArray(cx));
5265 if (!result)
5266 return false;
5267 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
5268 size_t lineno = r.frontLineNumber();
5269 size_t column = r.frontColumnNumber();
5270 size_t offset = r.frontOffset();
5271
5272 /* Make a note, if the current instruction is an entry point for the current position. */
5273 if (r.frontIsEntryPoint() &&
5274 !flowData[offset].hasNoEdges() &&
5275 (flowData[offset].lineno() != lineno ||
5276 flowData[offset].column() != column)) {
5277 RootedPlainObject entry(cx, NewBuiltinClassInstance<PlainObject>(cx));
5278 if (!entry)
5279 return false;
5280
5281 RootedId id(cx, NameToId(cx->names().lineNumber));
5282 RootedValue value(cx, NumberValue(lineno));
5283 if (!DefineProperty(cx, entry, id, value))
5284 return false;
5285
5286 value = NumberValue(column);
5287 if (!DefineProperty(cx, entry, cx->names().columnNumber, value))
5288 return false;
5289
5290 id = NameToId(cx->names().offset);
5291 value = NumberValue(offset);
5292 if (!DefineProperty(cx, entry, id, value))
5293 return false;
5294
5295 if (!NewbornArrayPush(cx, result, ObjectValue(*entry)))
5296 return false;
5297 }
5298 }
5299
5300 args.rval().setObject(*result);
5301 return true;
5302 }
5303
5304 static bool
DebuggerScript_getLineOffsets(JSContext * cx,unsigned argc,Value * vp)5305 DebuggerScript_getLineOffsets(JSContext* cx, unsigned argc, Value* vp)
5306 {
5307 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineOffsets", args, obj, script);
5308 if (!args.requireAtLeast(cx, "Debugger.Script.getLineOffsets", 1))
5309 return false;
5310
5311 /* Parse lineno argument. */
5312 RootedValue linenoValue(cx, args[0]);
5313 size_t lineno;
5314 if (!ToNumber(cx, &linenoValue))
5315 return false;
5316 {
5317 double d = linenoValue.toNumber();
5318 lineno = size_t(d);
5319 if (lineno != d) {
5320 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE);
5321 return false;
5322 }
5323 }
5324
5325 /*
5326 * First pass: determine which offsets in this script are jump targets and
5327 * which line numbers jump to them.
5328 */
5329 FlowGraphSummary flowData(cx);
5330 if (!flowData.populate(cx, script))
5331 return false;
5332
5333 /* Second pass: build the result array. */
5334 RootedObject result(cx, NewDenseEmptyArray(cx));
5335 if (!result)
5336 return false;
5337 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
5338 if (!r.frontIsEntryPoint())
5339 continue;
5340
5341 size_t offset = r.frontOffset();
5342
5343 /* If the op at offset is an entry point, append offset to result. */
5344 if (r.frontLineNumber() == lineno &&
5345 !flowData[offset].hasNoEdges() &&
5346 flowData[offset].lineno() != lineno)
5347 {
5348 if (!NewbornArrayPush(cx, result, NumberValue(offset)))
5349 return false;
5350 }
5351 }
5352
5353 args.rval().setObject(*result);
5354 return true;
5355 }
5356
5357 bool
observesFrame(AbstractFramePtr frame) const5358 Debugger::observesFrame(AbstractFramePtr frame) const
5359 {
5360 return observesScript(frame.script());
5361 }
5362
5363 bool
observesFrame(const ScriptFrameIter & iter) const5364 Debugger::observesFrame(const ScriptFrameIter& iter) const
5365 {
5366 return observesScript(iter.script());
5367 }
5368
5369 bool
observesScript(JSScript * script) const5370 Debugger::observesScript(JSScript* script) const
5371 {
5372 if (!enabled)
5373 return false;
5374 // Don't ever observe self-hosted scripts: the Debugger API can break
5375 // self-hosted invariants.
5376 return observesGlobal(&script->global()) && !script->selfHosted();
5377 }
5378
5379 /* static */ bool
replaceFrameGuts(JSContext * cx,AbstractFramePtr from,AbstractFramePtr to,ScriptFrameIter & iter)5380 Debugger::replaceFrameGuts(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to,
5381 ScriptFrameIter& iter)
5382 {
5383 // Forward live Debugger.Frame objects.
5384 for (Debugger::FrameRange r(from); !r.empty(); r.popFront()) {
5385 RootedNativeObject frameobj(cx, r.frontFrame());
5386 Debugger* dbg = r.frontDebugger();
5387 MOZ_ASSERT(dbg == Debugger::fromChildJSObject(frameobj));
5388
5389 // Update frame object's ScriptFrameIter::data pointer.
5390 DebuggerFrame_freeScriptFrameIterData(cx->runtime()->defaultFreeOp(), frameobj);
5391 ScriptFrameIter::Data* data = iter.copyData();
5392 if (!data)
5393 return false;
5394 frameobj->setPrivate(data);
5395
5396 // Remove the old entry before mutating the HashMap.
5397 r.removeFrontFrame();
5398
5399 // Add the frame object with |to| as key.
5400 if (!dbg->frames.putNew(to, frameobj)) {
5401 ReportOutOfMemory(cx);
5402 return false;
5403 }
5404 }
5405
5406 // Rekey missingScopes to maintain Debugger.Environment identity and
5407 // forward liveScopes to point to the new frame, as the old frame will be
5408 // gone.
5409 DebugScopes::forwardLiveFrame(cx, from, to);
5410
5411 return true;
5412 }
5413
5414 /* static */ bool
inFrameMaps(AbstractFramePtr frame)5415 Debugger::inFrameMaps(AbstractFramePtr frame)
5416 {
5417 FrameRange r(frame);
5418 return !r.empty();
5419 }
5420
5421 /* static */ void
removeFromFrameMapsAndClearBreakpointsIn(JSContext * cx,AbstractFramePtr frame)5422 Debugger::removeFromFrameMapsAndClearBreakpointsIn(JSContext* cx, AbstractFramePtr frame)
5423 {
5424 Handle<GlobalObject*> global = cx->global();
5425
5426 for (FrameRange r(frame, global); !r.empty(); r.popFront()) {
5427 RootedNativeObject frameobj(cx, r.frontFrame());
5428 Debugger* dbg = r.frontDebugger();
5429 MOZ_ASSERT(dbg == Debugger::fromChildJSObject(frameobj));
5430
5431 FreeOp* fop = cx->runtime()->defaultFreeOp();
5432 DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
5433 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
5434
5435 dbg->frames.remove(frame);
5436 }
5437
5438 /*
5439 * If this is an eval frame, then from the debugger's perspective the
5440 * script is about to be destroyed. Remove any breakpoints in it.
5441 */
5442 if (frame.isEvalFrame()) {
5443 RootedScript script(cx, frame.script());
5444 script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), nullptr, nullptr);
5445 }
5446 }
5447
5448 /* static */ bool
handleBaselineOsr(JSContext * cx,InterpreterFrame * from,jit::BaselineFrame * to)5449 Debugger::handleBaselineOsr(JSContext* cx, InterpreterFrame* from, jit::BaselineFrame* to)
5450 {
5451 ScriptFrameIter iter(cx);
5452 MOZ_ASSERT(iter.abstractFramePtr() == to);
5453 return replaceFrameGuts(cx, from, to, iter);
5454 }
5455
5456 /* static */ bool
handleIonBailout(JSContext * cx,jit::RematerializedFrame * from,jit::BaselineFrame * to)5457 Debugger::handleIonBailout(JSContext* cx, jit::RematerializedFrame* from, jit::BaselineFrame* to)
5458 {
5459 // When we return to a bailed-out Ion real frame, we must update all
5460 // Debugger.Frames that refer to its inline frames. However, since we
5461 // can't pop individual inline frames off the stack (we can only pop the
5462 // real frame that contains them all, as a unit), we cannot assume that
5463 // the frame we're dealing with is the top frame. Advance the iterator
5464 // across any inlined frames younger than |to|, the baseline frame
5465 // reconstructed during bailout from the Ion frame corresponding to
5466 // |from|.
5467 ScriptFrameIter iter(cx);
5468 while (iter.abstractFramePtr() != to)
5469 ++iter;
5470 return replaceFrameGuts(cx, from, to, iter);
5471 }
5472
5473 /* static */ void
handleUnrecoverableIonBailoutError(JSContext * cx,jit::RematerializedFrame * frame)5474 Debugger::handleUnrecoverableIonBailoutError(JSContext* cx, jit::RematerializedFrame* frame)
5475 {
5476 // Ion bailout can fail due to overrecursion. In such cases we cannot
5477 // honor any further Debugger hooks on the frame, and need to ensure that
5478 // its Debugger.Frame entry is cleaned up.
5479 removeFromFrameMapsAndClearBreakpointsIn(cx, frame);
5480 }
5481
5482 /* static */ void
propagateForcedReturn(JSContext * cx,AbstractFramePtr frame,HandleValue rval)5483 Debugger::propagateForcedReturn(JSContext* cx, AbstractFramePtr frame, HandleValue rval)
5484 {
5485 // Invoking the interrupt handler is considered a step and invokes the
5486 // youngest frame's onStep handler, if any. However, we cannot handle
5487 // { return: ... } resumption values straightforwardly from the interrupt
5488 // handler. Instead, we set the intended return value in the frame's rval
5489 // slot and set the propagating-forced-return flag on the JSContext.
5490 //
5491 // The interrupt handler then returns false with no exception set,
5492 // signaling an uncatchable exception. In the exception handlers, we then
5493 // check for the special propagating-forced-return flag.
5494 MOZ_ASSERT(!cx->isExceptionPending());
5495 cx->setPropagatingForcedReturn();
5496 frame.setReturnValue(rval);
5497 }
5498
5499 static bool
DebuggerScript_setBreakpoint(JSContext * cx,unsigned argc,Value * vp)5500 DebuggerScript_setBreakpoint(JSContext* cx, unsigned argc, Value* vp)
5501 {
5502 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script);
5503 if (!args.requireAtLeast(cx, "Debugger.Script.setBreakpoint", 2))
5504 return false;
5505 Debugger* dbg = Debugger::fromChildJSObject(obj);
5506
5507 if (!dbg->observesScript(script)) {
5508 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGING);
5509 return false;
5510 }
5511
5512 size_t offset;
5513 if (!ScriptOffset(cx, script, args[0], &offset))
5514 return false;
5515
5516 RootedObject handler(cx, NonNullObject(cx, args[1]));
5517 if (!handler)
5518 return false;
5519
5520 // Ensure observability *before* setting the breakpoint. If the script is
5521 // not already a debuggee, trying to ensure observability after setting
5522 // the breakpoint (and thus marking the script as a debuggee) will skip
5523 // actually ensuring observability.
5524 if (!dbg->ensureExecutionObservabilityOfScript(cx, script))
5525 return false;
5526
5527 jsbytecode* pc = script->offsetToPC(offset);
5528 BreakpointSite* site = script->getOrCreateBreakpointSite(cx, pc);
5529 if (!site)
5530 return false;
5531 site->inc(cx->runtime()->defaultFreeOp());
5532 if (cx->runtime()->new_<Breakpoint>(dbg, site, handler)) {
5533 args.rval().setUndefined();
5534 return true;
5535 }
5536 site->dec(cx->runtime()->defaultFreeOp());
5537 site->destroyIfEmpty(cx->runtime()->defaultFreeOp());
5538 return false;
5539 }
5540
5541 static bool
DebuggerScript_getBreakpoints(JSContext * cx,unsigned argc,Value * vp)5542 DebuggerScript_getBreakpoints(JSContext* cx, unsigned argc, Value* vp)
5543 {
5544 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getBreakpoints", args, obj, script);
5545 Debugger* dbg = Debugger::fromChildJSObject(obj);
5546
5547 jsbytecode* pc;
5548 if (args.length() > 0) {
5549 size_t offset;
5550 if (!ScriptOffset(cx, script, args[0], &offset))
5551 return false;
5552 pc = script->offsetToPC(offset);
5553 } else {
5554 pc = nullptr;
5555 }
5556
5557 RootedObject arr(cx, NewDenseEmptyArray(cx));
5558 if (!arr)
5559 return false;
5560
5561 for (unsigned i = 0; i < script->length(); i++) {
5562 BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i));
5563 if (site && (!pc || site->pc == pc)) {
5564 for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
5565 if (bp->debugger == dbg &&
5566 !NewbornArrayPush(cx, arr, ObjectValue(*bp->getHandler())))
5567 {
5568 return false;
5569 }
5570 }
5571 }
5572 }
5573 args.rval().setObject(*arr);
5574 return true;
5575 }
5576
5577 static bool
DebuggerScript_clearBreakpoint(JSContext * cx,unsigned argc,Value * vp)5578 DebuggerScript_clearBreakpoint(JSContext* cx, unsigned argc, Value* vp)
5579 {
5580 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script);
5581 if (!args.requireAtLeast(cx, "Debugger.Script.clearBreakpoint", 1))
5582 return false;
5583 Debugger* dbg = Debugger::fromChildJSObject(obj);
5584
5585 JSObject* handler = NonNullObject(cx, args[0]);
5586 if (!handler)
5587 return false;
5588
5589 script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, handler);
5590 args.rval().setUndefined();
5591 return true;
5592 }
5593
5594 static bool
DebuggerScript_clearAllBreakpoints(JSContext * cx,unsigned argc,Value * vp)5595 DebuggerScript_clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp)
5596 {
5597 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearAllBreakpoints", args, obj, script);
5598 Debugger* dbg = Debugger::fromChildJSObject(obj);
5599 script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, nullptr);
5600 args.rval().setUndefined();
5601 return true;
5602 }
5603
5604 static bool
DebuggerScript_isInCatchScope(JSContext * cx,unsigned argc,Value * vp)5605 DebuggerScript_isInCatchScope(JSContext* cx, unsigned argc, Value* vp)
5606 {
5607 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "isInCatchScope", args, obj, script);
5608 if (!args.requireAtLeast(cx, "Debugger.Script.isInCatchScope", 1))
5609 return false;
5610
5611 size_t offset;
5612 if (!ScriptOffset(cx, script, args[0], &offset))
5613 return false;
5614
5615 /*
5616 * Try note ranges are relative to the mainOffset of the script, so adjust
5617 * offset accordingly.
5618 */
5619 offset -= script->mainOffset();
5620
5621 args.rval().setBoolean(false);
5622 if (script->hasTrynotes()) {
5623 JSTryNote* tnBegin = script->trynotes()->vector;
5624 JSTryNote* tnEnd = tnBegin + script->trynotes()->length;
5625 while (tnBegin != tnEnd) {
5626 if (tnBegin->start <= offset &&
5627 offset <= tnBegin->start + tnBegin->length &&
5628 tnBegin->kind == JSTRY_CATCH)
5629 {
5630 args.rval().setBoolean(true);
5631 break;
5632 }
5633 ++tnBegin;
5634 }
5635 }
5636 return true;
5637 }
5638
5639 static bool
DebuggerScript_getOffsetsCoverage(JSContext * cx,unsigned argc,Value * vp)5640 DebuggerScript_getOffsetsCoverage(JSContext* cx, unsigned argc, Value* vp)
5641 {
5642 THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetsCoverage", args, obj, script);
5643
5644 // If the script has no coverage information, then skip this and return null
5645 // instead.
5646 if (!script->hasScriptCounts()) {
5647 args.rval().setNull();
5648 return true;
5649 }
5650
5651 ScriptCounts* sc = &script->getScriptCounts();
5652
5653 // If the main ever got visited, then assume that any code before main got
5654 // visited once.
5655 uint64_t hits = 0;
5656 const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(script->main()));
5657 if (counts->numExec())
5658 hits = 1;
5659
5660 // Build an array of objects which are composed of 4 properties:
5661 // - offset PC offset of the current opcode.
5662 // - lineNumber Line of the current opcode.
5663 // - columnNumber Column of the current opcode.
5664 // - count Number of times the instruction got executed.
5665 RootedObject result(cx, NewDenseEmptyArray(cx));
5666 if (!result)
5667 return false;
5668
5669 RootedId offsetId(cx, AtomToId(cx->names().offset));
5670 RootedId lineNumberId(cx, AtomToId(cx->names().lineNumber));
5671 RootedId columnNumberId(cx, AtomToId(cx->names().columnNumber));
5672 RootedId countId(cx, AtomToId(cx->names().count));
5673
5674 RootedObject item(cx);
5675 RootedValue offsetValue(cx);
5676 RootedValue lineNumberValue(cx);
5677 RootedValue columnNumberValue(cx);
5678 RootedValue countValue(cx);
5679
5680 // Iterate linearly over the bytecode.
5681 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
5682 size_t offset = r.frontOffset();
5683
5684 // The beginning of each non-branching sequences of instruction set the
5685 // number of execution of the current instruction and any following
5686 // instruction.
5687 counts = sc->maybeGetPCCounts(offset);
5688 if (counts)
5689 hits = counts->numExec();
5690
5691 offsetValue.setNumber(double(offset));
5692 lineNumberValue.setNumber(double(r.frontLineNumber()));
5693 columnNumberValue.setNumber(double(r.frontColumnNumber()));
5694 countValue.setNumber(double(hits));
5695
5696 // Create a new object with the offset, line number, column number, the
5697 // number of hit counts, and append it to the array.
5698 item = NewObjectWithGivenProto<PlainObject>(cx, nullptr);
5699 if (!item ||
5700 !DefineProperty(cx, item, offsetId, offsetValue) ||
5701 !DefineProperty(cx, item, lineNumberId, lineNumberValue) ||
5702 !DefineProperty(cx, item, columnNumberId, columnNumberValue) ||
5703 !DefineProperty(cx, item, countId, countValue) ||
5704 !NewbornArrayPush(cx, result, ObjectValue(*item)))
5705 {
5706 return false;
5707 }
5708
5709 // If the current instruction has thrown, then decrement the hit counts
5710 // with the number of throws.
5711 counts = sc->maybeGetThrowCounts(offset);
5712 if (counts)
5713 hits -= counts->numExec();
5714 }
5715
5716 args.rval().setObject(*result);
5717 return true;
5718 }
5719
5720 static bool
DebuggerScript_construct(JSContext * cx,unsigned argc,Value * vp)5721 DebuggerScript_construct(JSContext* cx, unsigned argc, Value* vp)
5722 {
5723 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
5724 "Debugger.Script");
5725 return false;
5726 }
5727
5728 static const JSPropertySpec DebuggerScript_properties[] = {
5729 JS_PSG("displayName", DebuggerScript_getDisplayName, 0),
5730 JS_PSG("url", DebuggerScript_getUrl, 0),
5731 JS_PSG("startLine", DebuggerScript_getStartLine, 0),
5732 JS_PSG("lineCount", DebuggerScript_getLineCount, 0),
5733 JS_PSG("source", DebuggerScript_getSource, 0),
5734 JS_PSG("sourceStart", DebuggerScript_getSourceStart, 0),
5735 JS_PSG("sourceLength", DebuggerScript_getSourceLength, 0),
5736 JS_PSG("global", DebuggerScript_getGlobal, 0),
5737 JS_PS_END
5738 };
5739
5740 static const JSFunctionSpec DebuggerScript_methods[] = {
5741 JS_FN("getChildScripts", DebuggerScript_getChildScripts, 0, 0),
5742 JS_FN("getAllOffsets", DebuggerScript_getAllOffsets, 0, 0),
5743 JS_FN("getAllColumnOffsets", DebuggerScript_getAllColumnOffsets, 0, 0),
5744 JS_FN("getLineOffsets", DebuggerScript_getLineOffsets, 1, 0),
5745 JS_FN("getOffsetLocation", DebuggerScript_getOffsetLocation, 0, 0),
5746 JS_FN("setBreakpoint", DebuggerScript_setBreakpoint, 2, 0),
5747 JS_FN("getBreakpoints", DebuggerScript_getBreakpoints, 1, 0),
5748 JS_FN("clearBreakpoint", DebuggerScript_clearBreakpoint, 1, 0),
5749 JS_FN("clearAllBreakpoints", DebuggerScript_clearAllBreakpoints, 0, 0),
5750 JS_FN("isInCatchScope", DebuggerScript_isInCatchScope, 1, 0),
5751 JS_FN("getOffsetsCoverage", DebuggerScript_getOffsetsCoverage, 0, 0),
5752 JS_FS_END
5753 };
5754
5755
5756 /*** Debugger.Source *****************************************************************************/
5757
5758 static inline ScriptSourceObject*
GetSourceReferent(JSObject * obj)5759 GetSourceReferent(JSObject* obj)
5760 {
5761 MOZ_ASSERT(obj->getClass() == &DebuggerSource_class);
5762 return static_cast<ScriptSourceObject*>(obj->as<NativeObject>().getPrivate());
5763 }
5764
5765 void
DebuggerSource_trace(JSTracer * trc,JSObject * obj)5766 DebuggerSource_trace(JSTracer* trc, JSObject* obj)
5767 {
5768 /*
5769 * There is a barrier on private pointers, so the Unbarriered marking
5770 * is okay.
5771 */
5772 if (JSObject* referent = GetSourceReferent(obj)) {
5773 TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
5774 "Debugger.Source referent");
5775 obj->as<NativeObject>().setPrivateUnbarriered(referent);
5776 }
5777 }
5778
5779 const Class DebuggerSource_class = {
5780 "Source",
5781 JSCLASS_HAS_PRIVATE |
5782 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSOURCE_COUNT),
5783 nullptr, nullptr, nullptr, nullptr,
5784 nullptr, nullptr, nullptr, nullptr,
5785 nullptr, /* call */
5786 nullptr, /* hasInstance */
5787 nullptr, /* construct */
5788 DebuggerSource_trace
5789 };
5790
5791 JSObject*
newDebuggerSource(JSContext * cx,HandleScriptSource source)5792 Debugger::newDebuggerSource(JSContext* cx, HandleScriptSource source)
5793 {
5794 assertSameCompartment(cx, object.get());
5795
5796 RootedObject proto(cx, &object->getReservedSlot(JSSLOT_DEBUG_SOURCE_PROTO).toObject());
5797 MOZ_ASSERT(proto);
5798 NativeObject* sourceobj = NewNativeObjectWithGivenProto(cx, &DebuggerSource_class,
5799 proto, TenuredObject);
5800 if (!sourceobj)
5801 return nullptr;
5802 sourceobj->setReservedSlot(JSSLOT_DEBUGSOURCE_OWNER, ObjectValue(*object));
5803 sourceobj->setPrivateGCThing(source);
5804
5805 return sourceobj;
5806 }
5807
5808 JSObject*
wrapSource(JSContext * cx,HandleScriptSource source)5809 Debugger::wrapSource(JSContext* cx, HandleScriptSource source)
5810 {
5811 assertSameCompartment(cx, object.get());
5812 MOZ_ASSERT(cx->compartment() != source->compartment());
5813 DependentAddPtr<SourceWeakMap> p(cx, sources, source);
5814 if (!p) {
5815 JSObject* sourceobj = newDebuggerSource(cx, source);
5816 if (!sourceobj)
5817 return nullptr;
5818
5819 if (!p.add(cx, sources, source, sourceobj))
5820 return nullptr;
5821
5822 CrossCompartmentKey key(CrossCompartmentKey::DebuggerSource, object, source);
5823 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*sourceobj))) {
5824 sources.remove(source);
5825 ReportOutOfMemory(cx);
5826 return nullptr;
5827 }
5828 }
5829
5830 MOZ_ASSERT(GetSourceReferent(p->value()) == source);
5831 return p->value();
5832 }
5833
5834 static bool
DebuggerSource_construct(JSContext * cx,unsigned argc,Value * vp)5835 DebuggerSource_construct(JSContext* cx, unsigned argc, Value* vp)
5836 {
5837 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
5838 "Debugger.Source");
5839 return false;
5840 }
5841
5842 static NativeObject*
DebuggerSource_checkThis(JSContext * cx,const CallArgs & args,const char * fnname)5843 DebuggerSource_checkThis(JSContext* cx, const CallArgs& args, const char* fnname)
5844 {
5845 JSObject* thisobj = NonNullObject(cx, args.thisv());
5846 if (!thisobj)
5847 return nullptr;
5848 if (thisobj->getClass() != &DebuggerSource_class) {
5849 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
5850 "Debugger.Source", fnname, thisobj->getClass()->name);
5851 return nullptr;
5852 }
5853
5854 NativeObject* nthisobj = &thisobj->as<NativeObject>();
5855
5856 if (!GetSourceReferent(thisobj)) {
5857 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
5858 "Debugger.Frame", fnname, "prototype object");
5859 return nullptr;
5860 }
5861
5862 return nthisobj;
5863 }
5864
5865 #define THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, fnname, args, obj, sourceObject) \
5866 CallArgs args = CallArgsFromVp(argc, vp); \
5867 RootedNativeObject obj(cx, DebuggerSource_checkThis(cx, args, fnname)); \
5868 if (!obj) \
5869 return false; \
5870 RootedScriptSource sourceObject(cx, GetSourceReferent(obj)); \
5871 if (!sourceObject) \
5872 return false;
5873
5874 static bool
DebuggerSource_getText(JSContext * cx,unsigned argc,Value * vp)5875 DebuggerSource_getText(JSContext* cx, unsigned argc, Value* vp)
5876 {
5877 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get text)", args, obj, sourceObject);
5878 Value textv = obj->getReservedSlot(JSSLOT_DEBUGSOURCE_TEXT);
5879 if (!textv.isUndefined()) {
5880 MOZ_ASSERT(textv.isString());
5881 args.rval().set(textv);
5882 return true;
5883 }
5884
5885 ScriptSource* ss = sourceObject->source();
5886 bool hasSourceData = ss->hasSourceData();
5887 if (!ss->hasSourceData() && !JSScript::loadSource(cx, ss, &hasSourceData))
5888 return false;
5889
5890 JSString* str = hasSourceData ? ss->substring(cx, 0, ss->length())
5891 : NewStringCopyZ<CanGC>(cx, "[no source]");
5892 if (!str)
5893 return false;
5894
5895 args.rval().setString(str);
5896 obj->setReservedSlot(JSSLOT_DEBUGSOURCE_TEXT, args.rval());
5897 return true;
5898 }
5899
5900 static bool
DebuggerSource_getUrl(JSContext * cx,unsigned argc,Value * vp)5901 DebuggerSource_getUrl(JSContext* cx, unsigned argc, Value* vp)
5902 {
5903 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject);
5904
5905 ScriptSource* ss = sourceObject->source();
5906 if (ss->filename()) {
5907 JSString* str = NewStringCopyZ<CanGC>(cx, ss->filename());
5908 if (!str)
5909 return false;
5910 args.rval().setString(str);
5911 } else {
5912 args.rval().setNull();
5913 }
5914 return true;
5915 }
5916
5917 static bool
DebuggerSource_getDisplayURL(JSContext * cx,unsigned argc,Value * vp)5918 DebuggerSource_getDisplayURL(JSContext* cx, unsigned argc, Value* vp)
5919 {
5920 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject);
5921
5922 ScriptSource* ss = sourceObject->source();
5923 MOZ_ASSERT(ss);
5924
5925 if (ss->hasDisplayURL()) {
5926 JSString* str = JS_NewUCStringCopyZ(cx, ss->displayURL());
5927 if (!str)
5928 return false;
5929 args.rval().setString(str);
5930 } else {
5931 args.rval().setNull();
5932 }
5933
5934 return true;
5935 }
5936
5937 static bool
DebuggerSource_getElement(JSContext * cx,unsigned argc,Value * vp)5938 DebuggerSource_getElement(JSContext* cx, unsigned argc, Value* vp)
5939 {
5940 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get element)", args, obj, sourceObject);
5941
5942 if (sourceObject->element()) {
5943 args.rval().setObjectOrNull(sourceObject->element());
5944 if (!Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval()))
5945 return false;
5946 } else {
5947 args.rval().setUndefined();
5948 }
5949 return true;
5950 }
5951
5952 static bool
DebuggerSource_getElementProperty(JSContext * cx,unsigned argc,Value * vp)5953 DebuggerSource_getElementProperty(JSContext* cx, unsigned argc, Value* vp)
5954 {
5955 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get elementAttributeName)", args, obj, sourceObject);
5956 args.rval().set(sourceObject->elementAttributeName());
5957 return Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval());
5958 }
5959
5960 static bool
DebuggerSource_getIntroductionScript(JSContext * cx,unsigned argc,Value * vp)5961 DebuggerSource_getIntroductionScript(JSContext* cx, unsigned argc, Value* vp)
5962 {
5963 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionScript)", args, obj, sourceObject);
5964
5965 RootedScript script(cx, sourceObject->introductionScript());
5966 if (script) {
5967 RootedObject scriptDO(cx, Debugger::fromChildJSObject(obj)->wrapScript(cx, script));
5968 if (!scriptDO)
5969 return false;
5970 args.rval().setObject(*scriptDO);
5971 } else {
5972 args.rval().setUndefined();
5973 }
5974 return true;
5975 }
5976
5977 static bool
DebuggerSource_getIntroductionOffset(JSContext * cx,unsigned argc,Value * vp)5978 DebuggerSource_getIntroductionOffset(JSContext* cx, unsigned argc, Value* vp)
5979 {
5980 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionOffset)", args, obj, sourceObject);
5981
5982 // Regardless of what's recorded in the ScriptSourceObject and
5983 // ScriptSource, only hand out the introduction offset if we also have
5984 // the script within which it applies.
5985 ScriptSource* ss = sourceObject->source();
5986 if (ss->hasIntroductionOffset() && sourceObject->introductionScript())
5987 args.rval().setInt32(ss->introductionOffset());
5988 else
5989 args.rval().setUndefined();
5990 return true;
5991 }
5992
5993 static bool
DebuggerSource_getIntroductionType(JSContext * cx,unsigned argc,Value * vp)5994 DebuggerSource_getIntroductionType(JSContext* cx, unsigned argc, Value* vp)
5995 {
5996 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get introductionType)", args, obj, sourceObject);
5997
5998 ScriptSource* ss = sourceObject->source();
5999 if (ss->hasIntroductionType()) {
6000 JSString* str = NewStringCopyZ<CanGC>(cx, ss->introductionType());
6001 if (!str)
6002 return false;
6003 args.rval().setString(str);
6004 } else {
6005 args.rval().setUndefined();
6006 }
6007 return true;
6008 }
6009
6010 static bool
DebuggerSource_setSourceMapUrl(JSContext * cx,unsigned argc,Value * vp)6011 DebuggerSource_setSourceMapUrl(JSContext* cx, unsigned argc, Value* vp)
6012 {
6013 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "sourceMapURL", args, obj, sourceObject);
6014 ScriptSource* ss = sourceObject->source();
6015 MOZ_ASSERT(ss);
6016
6017 JSString* str = ToString<CanGC>(cx, args[0]);
6018 if (!str)
6019 return false;
6020
6021 AutoStableStringChars stableChars(cx);
6022 if (!stableChars.initTwoByte(cx, str))
6023 return false;
6024
6025 ss->setSourceMapURL(cx, stableChars.twoByteChars());
6026 args.rval().setUndefined();
6027 return true;
6028 }
6029
6030 static bool
DebuggerSource_getSourceMapUrl(JSContext * cx,unsigned argc,Value * vp)6031 DebuggerSource_getSourceMapUrl(JSContext* cx, unsigned argc, Value* vp)
6032 {
6033 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get sourceMapURL)", args, obj, sourceObject);
6034
6035 ScriptSource* ss = sourceObject->source();
6036 MOZ_ASSERT(ss);
6037
6038 if (ss->hasSourceMapURL()) {
6039 JSString* str = JS_NewUCStringCopyZ(cx, ss->sourceMapURL());
6040 if (!str)
6041 return false;
6042 args.rval().setString(str);
6043 } else {
6044 args.rval().setNull();
6045 }
6046
6047 return true;
6048 }
6049
6050 static bool
DebuggerSource_getCanonicalId(JSContext * cx,unsigned argc,Value * vp)6051 DebuggerSource_getCanonicalId(JSContext* cx, unsigned argc, Value* vp)
6052 {
6053 THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get sourceMapURL)", args, obj, sourceObject);
6054
6055 ScriptSource* ss = sourceObject->source();
6056 MOZ_ASSERT(ss);
6057
6058 static_assert(!mozilla::IsBaseOf<gc::Cell, ScriptSource>::value,
6059 "We rely on ScriptSource* pointers to be stable, and not move in memory. "
6060 "Currently, this holds true because ScriptSource is not managed by the GC. If "
6061 "that changes, it doesn't necessarily mean that it will start moving, but we "
6062 "will need a new assertion here. If we do start moving ScriptSources in memory, "
6063 "then DebuggerSource_getCanonicalId will need to be reworked!");
6064 auto id = uintptr_t(ss);
6065
6066 // IEEE 754 doubles can precisely store integers of up 53 bits. On 32 bit
6067 // platforms, pointers trivially fit. On 64 bit platforms, pointers only use
6068 // 48 bits so we are still good.
6069 MOZ_ASSERT(Value::isNumberRepresentable(id));
6070
6071 args.rval().set(NumberValue(id));
6072 return true;
6073 }
6074
6075 static const JSPropertySpec DebuggerSource_properties[] = {
6076 JS_PSG("text", DebuggerSource_getText, 0),
6077 JS_PSG("url", DebuggerSource_getUrl, 0),
6078 JS_PSG("element", DebuggerSource_getElement, 0),
6079 JS_PSG("displayURL", DebuggerSource_getDisplayURL, 0),
6080 JS_PSG("introductionScript", DebuggerSource_getIntroductionScript, 0),
6081 JS_PSG("introductionOffset", DebuggerSource_getIntroductionOffset, 0),
6082 JS_PSG("introductionType", DebuggerSource_getIntroductionType, 0),
6083 JS_PSG("elementAttributeName", DebuggerSource_getElementProperty, 0),
6084 JS_PSGS("sourceMapURL", DebuggerSource_getSourceMapUrl, DebuggerSource_setSourceMapUrl, 0),
6085 JS_PSG("canonicalId", DebuggerSource_getCanonicalId, 0),
6086 JS_PS_END
6087 };
6088
6089 static const JSFunctionSpec DebuggerSource_methods[] = {
6090 JS_FS_END
6091 };
6092
6093
6094 /*** Debugger.Frame ******************************************************************************/
6095
6096 static void
UpdateFrameIterPc(FrameIter & iter)6097 UpdateFrameIterPc(FrameIter& iter)
6098 {
6099 if (iter.abstractFramePtr().isRematerializedFrame()) {
6100 #ifdef DEBUG
6101 // Rematerialized frames don't need their pc updated. The reason we
6102 // need to update pc is because we might get the same Debugger.Frame
6103 // object for multiple re-entries into debugger code from debuggee
6104 // code. This reentrancy is not possible with rematerialized frames,
6105 // because when returning to debuggee code, we would have bailed out
6106 // to baseline.
6107 //
6108 // We walk the stack to assert that it doesn't need updating.
6109 jit::RematerializedFrame* frame = iter.abstractFramePtr().asRematerializedFrame();
6110 jit::JitFrameLayout* jsFrame = (jit::JitFrameLayout*)frame->top();
6111 jit::JitActivation* activation = iter.activation()->asJit();
6112
6113 ActivationIterator activationIter(activation->cx()->runtime());
6114 while (activationIter.activation() != activation)
6115 ++activationIter;
6116
6117 jit::JitFrameIterator jitIter(activationIter);
6118 while (!jitIter.isIonJS() || jitIter.jsFrame() != jsFrame)
6119 ++jitIter;
6120
6121 jit::InlineFrameIterator ionInlineIter(activation->cx(), &jitIter);
6122 while (ionInlineIter.frameNo() != frame->frameNo())
6123 ++ionInlineIter;
6124
6125 MOZ_ASSERT(ionInlineIter.pc() == iter.pc());
6126 #endif
6127 return;
6128 }
6129
6130 iter.updatePcQuadratic();
6131 }
6132
6133 static void
DebuggerFrame_freeScriptFrameIterData(FreeOp * fop,JSObject * obj)6134 DebuggerFrame_freeScriptFrameIterData(FreeOp* fop, JSObject* obj)
6135 {
6136 AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->as<NativeObject>().getPrivate());
6137 if (frame.isScriptFrameIterData())
6138 fop->delete_((ScriptFrameIter::Data*) frame.raw());
6139 obj->as<NativeObject>().setPrivate(nullptr);
6140 }
6141
6142 static void
DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp * fop,AbstractFramePtr frame,NativeObject * frameobj)6143 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp* fop, AbstractFramePtr frame,
6144 NativeObject* frameobj)
6145 {
6146 /* If this frame has an onStep handler, decrement the script's count. */
6147 if (!frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
6148 frame.script()->decrementStepModeCount(fop);
6149 }
6150
6151 static void
DebuggerFrame_finalize(FreeOp * fop,JSObject * obj)6152 DebuggerFrame_finalize(FreeOp* fop, JSObject* obj)
6153 {
6154 DebuggerFrame_freeScriptFrameIterData(fop, obj);
6155 }
6156
6157 const Class DebuggerFrame_class = {
6158 "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT),
6159 nullptr, nullptr, nullptr, nullptr,
6160 nullptr, nullptr, nullptr, DebuggerFrame_finalize
6161 };
6162
6163 static NativeObject*
CheckThisFrame(JSContext * cx,const CallArgs & args,const char * fnname,bool checkLive)6164 CheckThisFrame(JSContext* cx, const CallArgs& args, const char* fnname, bool checkLive)
6165 {
6166 JSObject* thisobj = NonNullObject(cx, args.thisv());
6167 if (!thisobj)
6168 return nullptr;
6169 if (thisobj->getClass() != &DebuggerFrame_class) {
6170 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
6171 "Debugger.Frame", fnname, thisobj->getClass()->name);
6172 return nullptr;
6173 }
6174
6175 NativeObject* nthisobj = &thisobj->as<NativeObject>();
6176
6177 /*
6178 * Forbid Debugger.Frame.prototype, which is of class DebuggerFrame_class
6179 * but isn't really a working Debugger.Frame object. The prototype object
6180 * is distinguished by having a nullptr private value. Also, forbid popped
6181 * frames.
6182 */
6183 if (!nthisobj->getPrivate()) {
6184 if (nthisobj->getReservedSlot(JSSLOT_DEBUGFRAME_OWNER).isUndefined()) {
6185 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
6186 "Debugger.Frame", fnname, "prototype object");
6187 return nullptr;
6188 }
6189 if (checkLive) {
6190 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
6191 "Debugger.Frame");
6192 return nullptr;
6193 }
6194 }
6195 return nthisobj;
6196 }
6197
6198 /*
6199 * To make frequently fired hooks like onEnterFrame more performant,
6200 * Debugger.Frame methods should not create a ScriptFrameIter unless it
6201 * absolutely needs to. That is, unless the method has to call a method on
6202 * ScriptFrameIter that's otherwise not available on AbstractFramePtr.
6203 *
6204 * When a Debugger.Frame is first created, its private slot is set to the
6205 * AbstractFramePtr itself. The first time the users asks for a
6206 * ScriptFrameIter, we construct one, have it settle on the frame pointed to
6207 * by the AbstractFramePtr and cache its internal Data in the Debugger.Frame
6208 * object's private slot. Subsequent uses of the Debugger.Frame object will
6209 * always create a ScriptFrameIter from the cached Data.
6210 *
6211 * Methods that only need the AbstractFramePtr should use THIS_FRAME.
6212 * Methods that need a ScriptFrameIterator should use THIS_FRAME_ITER.
6213 */
6214
6215 #define THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj) \
6216 CallArgs args = CallArgsFromVp(argc, vp); \
6217 RootedNativeObject thisobj(cx, CheckThisFrame(cx, args, fnname, true)); \
6218 if (!thisobj) \
6219 return false
6220
6221 #define THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame) \
6222 THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \
6223 AbstractFramePtr frame = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
6224 if (frame.isScriptFrameIterData()) { \
6225 ScriptFrameIter iter(*(ScriptFrameIter::Data*)(frame.raw())); \
6226 frame = iter.abstractFramePtr(); \
6227 }
6228
6229 #define THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter) \
6230 THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \
6231 Maybe<ScriptFrameIter> maybeIter; \
6232 { \
6233 AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
6234 if (f.isScriptFrameIterData()) { \
6235 maybeIter.emplace(*(ScriptFrameIter::Data*)(f.raw())); \
6236 } else { \
6237 maybeIter.emplace(cx, ScriptFrameIter::ALL_CONTEXTS, \
6238 ScriptFrameIter::GO_THROUGH_SAVED, \
6239 ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); \
6240 ScriptFrameIter& iter = *maybeIter; \
6241 while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != f) \
6242 ++iter; \
6243 AbstractFramePtr data = iter.copyDataAsAbstractFramePtr(); \
6244 if (!data) \
6245 return false; \
6246 thisobj->setPrivate(data.raw()); \
6247 } \
6248 } \
6249 ScriptFrameIter& iter = *maybeIter
6250
6251 #define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, frame, dbg) \
6252 THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame); \
6253 Debugger* dbg = Debugger::fromChildJSObject(thisobj)
6254
6255 #define THIS_FRAME_OWNER_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter, dbg) \
6256 THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter); \
6257 Debugger* dbg = Debugger::fromChildJSObject(thisobj)
6258
6259 static bool
DebuggerFrame_getType(JSContext * cx,unsigned argc,Value * vp)6260 DebuggerFrame_getType(JSContext* cx, unsigned argc, Value* vp)
6261 {
6262 THIS_FRAME(cx, argc, vp, "get type", args, thisobj, frame);
6263
6264 /*
6265 * Indirect eval frames are both isGlobalFrame() and isEvalFrame(), so the
6266 * order of checks here is significant.
6267 */
6268 JSString* type;
6269 if (frame.isEvalFrame())
6270 type = cx->names().eval;
6271 else if (frame.isGlobalFrame())
6272 type = cx->names().global;
6273 else if (frame.isFunctionFrame())
6274 type = cx->names().call;
6275 else if (frame.isModuleFrame())
6276 type = cx->names().module;
6277 else
6278 MOZ_CRASH("Unknown frame type");
6279 args.rval().setString(type);
6280 return true;
6281 }
6282
6283 static bool
DebuggerFrame_getImplementation(JSContext * cx,unsigned argc,Value * vp)6284 DebuggerFrame_getImplementation(JSContext* cx, unsigned argc, Value* vp)
6285 {
6286 THIS_FRAME(cx, argc, vp, "get implementation", args, thisobj, frame);
6287
6288 const char* s;
6289 if (frame.isBaselineFrame())
6290 s = "baseline";
6291 else if (frame.isRematerializedFrame())
6292 s = "ion";
6293 else
6294 s = "interpreter";
6295
6296 JSAtom* str = Atomize(cx, s, strlen(s));
6297 if (!str)
6298 return false;
6299
6300 args.rval().setString(str);
6301 return true;
6302 }
6303
6304 static bool
DebuggerFrame_getEnvironment(JSContext * cx,unsigned argc,Value * vp)6305 DebuggerFrame_getEnvironment(JSContext* cx, unsigned argc, Value* vp)
6306 {
6307 THIS_FRAME_OWNER_ITER(cx, argc, vp, "get environment", args, thisobj, _, iter, dbg);
6308
6309 Rooted<Env*> env(cx);
6310 {
6311 AutoCompartment ac(cx, iter.abstractFramePtr().scopeChain());
6312 UpdateFrameIterPc(iter);
6313 env = GetDebugScopeForFrame(cx, iter.abstractFramePtr(), iter.pc());
6314 if (!env)
6315 return false;
6316 }
6317
6318 return dbg->wrapEnvironment(cx, env, args.rval());
6319 }
6320
6321 static bool
DebuggerFrame_getCallee(JSContext * cx,unsigned argc,Value * vp)6322 DebuggerFrame_getCallee(JSContext* cx, unsigned argc, Value* vp)
6323 {
6324 THIS_FRAME(cx, argc, vp, "get callee", args, thisobj, frame);
6325 RootedValue calleev(cx, frame.isNonEvalFunctionFrame() ? frame.calleev() : NullValue());
6326 if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &calleev))
6327 return false;
6328 args.rval().set(calleev);
6329 return true;
6330 }
6331
6332 static bool
DebuggerFrame_getGenerator(JSContext * cx,unsigned argc,Value * vp)6333 DebuggerFrame_getGenerator(JSContext* cx, unsigned argc, Value* vp)
6334 {
6335 THIS_FRAME(cx, argc, vp, "get generator", args, thisobj, frame);
6336 args.rval().setBoolean(frame.script()->isGenerator());
6337 return true;
6338 }
6339
6340 static bool
DebuggerFrame_getConstructing(JSContext * cx,unsigned argc,Value * vp)6341 DebuggerFrame_getConstructing(JSContext* cx, unsigned argc, Value* vp)
6342 {
6343 THIS_FRAME_ITER(cx, argc, vp, "get constructing", args, thisobj, _, iter);
6344 args.rval().setBoolean(iter.isFunctionFrame() && iter.isConstructing());
6345 return true;
6346 }
6347
6348 static bool
DebuggerFrame_getThis(JSContext * cx,unsigned argc,Value * vp)6349 DebuggerFrame_getThis(JSContext* cx, unsigned argc, Value* vp)
6350 {
6351 THIS_FRAME_ITER(cx, argc, vp, "get this", args, thisobj, _, iter);
6352 RootedValue thisv(cx);
6353 {
6354 AbstractFramePtr frame = iter.abstractFramePtr();
6355 AutoCompartment ac(cx, frame.scopeChain());
6356
6357 UpdateFrameIterPc(iter);
6358
6359 if (!GetThisValueForDebuggerMaybeOptimizedOut(cx, frame, iter.pc(), &thisv))
6360 return false;
6361 }
6362
6363 if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &thisv))
6364 return false;
6365 args.rval().set(thisv);
6366 return true;
6367 }
6368
6369 static bool
DebuggerFrame_getOlder(JSContext * cx,unsigned argc,Value * vp)6370 DebuggerFrame_getOlder(JSContext* cx, unsigned argc, Value* vp)
6371 {
6372 THIS_FRAME_ITER(cx, argc, vp, "get this", args, thisobj, _, iter);
6373 Debugger* dbg = Debugger::fromChildJSObject(thisobj);
6374
6375 for (++iter; !iter.done(); ++iter) {
6376 if (dbg->observesFrame(iter)) {
6377 if (iter.isIon() && !iter.ensureHasRematerializedFrame(cx))
6378 return false;
6379 return dbg->getScriptFrame(cx, iter, args.rval());
6380 }
6381 }
6382 args.rval().setNull();
6383 return true;
6384 }
6385
6386 const Class DebuggerArguments_class = {
6387 "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT)
6388 };
6389
6390 /* The getter used for each element of frame.arguments. See DebuggerFrame_getArguments. */
6391 static bool
DebuggerArguments_getArg(JSContext * cx,unsigned argc,Value * vp)6392 DebuggerArguments_getArg(JSContext* cx, unsigned argc, Value* vp)
6393 {
6394 CallArgs args = CallArgsFromVp(argc, vp);
6395 int32_t i = args.callee().as<JSFunction>().getExtendedSlot(0).toInt32();
6396
6397 /* Check that the this value is an Arguments object. */
6398 RootedObject argsobj(cx, NonNullObject(cx, args.thisv()));
6399 if (!argsobj)
6400 return false;
6401 if (argsobj->getClass() != &DebuggerArguments_class) {
6402 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
6403 "Arguments", "getArgument", argsobj->getClass()->name);
6404 return false;
6405 }
6406
6407 /*
6408 * Put the Debugger.Frame into the this-value slot, then use THIS_FRAME
6409 * to check that it is still live and get the fp.
6410 */
6411 args.setThis(argsobj->as<NativeObject>().getReservedSlot(JSSLOT_DEBUGARGUMENTS_FRAME));
6412 THIS_FRAME(cx, argc, vp, "get argument", ca2, thisobj, frame);
6413
6414 /*
6415 * Since getters can be extracted and applied to other objects,
6416 * there is no guarantee this object has an ith argument.
6417 */
6418 MOZ_ASSERT(i >= 0);
6419 RootedValue arg(cx);
6420 RootedScript script(cx);
6421 if (unsigned(i) < frame.numActualArgs()) {
6422 script = frame.script();
6423 {
6424 AutoCompartment ac(cx, script->compartment());
6425 if (!script->ensureHasAnalyzedArgsUsage(cx))
6426 return false;
6427 }
6428 if (unsigned(i) < frame.numFormalArgs() && script->formalIsAliased(i)) {
6429 for (AliasedFormalIter fi(script); ; fi++) {
6430 if (fi.frameIndex() == unsigned(i)) {
6431 arg = frame.callObj().aliasedVar(fi);
6432 break;
6433 }
6434 }
6435 } else if (script->argsObjAliasesFormals() && frame.hasArgsObj()) {
6436 arg = frame.argsObj().arg(i);
6437 } else {
6438 arg = frame.unaliasedActual(i, DONT_CHECK_ALIASING);
6439 }
6440 } else {
6441 arg.setUndefined();
6442 }
6443
6444 if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &arg))
6445 return false;
6446 args.rval().set(arg);
6447 return true;
6448 }
6449
6450 static bool
DebuggerFrame_getArguments(JSContext * cx,unsigned argc,Value * vp)6451 DebuggerFrame_getArguments(JSContext* cx, unsigned argc, Value* vp)
6452 {
6453 THIS_FRAME(cx, argc, vp, "get arguments", args, thisobj, frame);
6454 Value argumentsv = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS);
6455 if (!argumentsv.isUndefined()) {
6456 MOZ_ASSERT(argumentsv.isObjectOrNull());
6457 args.rval().set(argumentsv);
6458 return true;
6459 }
6460
6461 RootedNativeObject argsobj(cx);
6462 if (frame.hasArgs()) {
6463 /* Create an arguments object. */
6464 Rooted<GlobalObject*> global(cx, &args.callee().global());
6465 RootedObject proto(cx, GlobalObject::getOrCreateArrayPrototype(cx, global));
6466 if (!proto)
6467 return false;
6468 argsobj = NewNativeObjectWithGivenProto(cx, &DebuggerArguments_class, proto);
6469 if (!argsobj)
6470 return false;
6471 SetReservedSlot(argsobj, JSSLOT_DEBUGARGUMENTS_FRAME, ObjectValue(*thisobj));
6472
6473 MOZ_ASSERT(frame.numActualArgs() <= 0x7fffffff);
6474 unsigned fargc = frame.numActualArgs();
6475 RootedValue fargcVal(cx, Int32Value(fargc));
6476 if (!NativeDefineProperty(cx, argsobj, cx->names().length, fargcVal, nullptr, nullptr,
6477 JSPROP_PERMANENT | JSPROP_READONLY))
6478 {
6479 return false;
6480 }
6481
6482 Rooted<jsid> id(cx);
6483 for (unsigned i = 0; i < fargc; i++) {
6484 RootedFunction getobj(cx);
6485 getobj = NewNativeFunction(cx, DebuggerArguments_getArg, 0, nullptr,
6486 gc::AllocKind::FUNCTION_EXTENDED);
6487 if (!getobj)
6488 return false;
6489 id = INT_TO_JSID(i);
6490 if (!getobj ||
6491 !NativeDefineProperty(cx, argsobj, id, UndefinedHandleValue,
6492 JS_DATA_TO_FUNC_PTR(GetterOp, getobj.get()), nullptr,
6493 JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER))
6494 {
6495 return false;
6496 }
6497 getobj->setExtendedSlot(0, Int32Value(i));
6498 }
6499 } else {
6500 argsobj = nullptr;
6501 }
6502 args.rval().setObjectOrNull(argsobj);
6503 thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS, args.rval());
6504 return true;
6505 }
6506
6507 static bool
DebuggerFrame_getScript(JSContext * cx,unsigned argc,Value * vp)6508 DebuggerFrame_getScript(JSContext* cx, unsigned argc, Value* vp)
6509 {
6510 THIS_FRAME(cx, argc, vp, "get script", args, thisobj, frame);
6511 Debugger* debug = Debugger::fromChildJSObject(thisobj);
6512
6513 RootedObject scriptObject(cx);
6514 if (frame.isFunctionFrame() && !frame.isEvalFrame()) {
6515 RootedFunction callee(cx, frame.callee());
6516 if (callee->isInterpreted()) {
6517 RootedScript script(cx, callee->nonLazyScript());
6518 scriptObject = debug->wrapScript(cx, script);
6519 if (!scriptObject)
6520 return false;
6521 }
6522 } else {
6523 /*
6524 * We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script
6525 * frames.
6526 */
6527 RootedScript script(cx, frame.script());
6528 scriptObject = debug->wrapScript(cx, script);
6529 if (!scriptObject)
6530 return false;
6531 }
6532 args.rval().setObjectOrNull(scriptObject);
6533 return true;
6534 }
6535
6536 static bool
DebuggerFrame_getOffset(JSContext * cx,unsigned argc,Value * vp)6537 DebuggerFrame_getOffset(JSContext* cx, unsigned argc, Value* vp)
6538 {
6539 THIS_FRAME_ITER(cx, argc, vp, "get offset", args, thisobj, _, iter);
6540 JSScript* script = iter.script();
6541 UpdateFrameIterPc(iter);
6542 jsbytecode* pc = iter.pc();
6543 size_t offset = script->pcToOffset(pc);
6544 args.rval().setNumber(double(offset));
6545 return true;
6546 }
6547
6548 static bool
DebuggerFrame_getLive(JSContext * cx,unsigned argc,Value * vp)6549 DebuggerFrame_getLive(JSContext* cx, unsigned argc, Value* vp)
6550 {
6551 CallArgs args = CallArgsFromVp(argc, vp);
6552 NativeObject* thisobj = CheckThisFrame(cx, args, "get live", false);
6553 if (!thisobj)
6554 return false;
6555 bool hasFrame = !!thisobj->getPrivate();
6556 args.rval().setBoolean(hasFrame);
6557 return true;
6558 }
6559
6560 static bool
IsValidHook(const Value & v)6561 IsValidHook(const Value& v)
6562 {
6563 return v.isUndefined() || (v.isObject() && v.toObject().isCallable());
6564 }
6565
6566 static bool
DebuggerFrame_getOnStep(JSContext * cx,unsigned argc,Value * vp)6567 DebuggerFrame_getOnStep(JSContext* cx, unsigned argc, Value* vp)
6568 {
6569 THIS_FRAME(cx, argc, vp, "get onStep", args, thisobj, frame);
6570 (void) frame; // Silence GCC warning
6571 RootedValue handler(cx, thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER));
6572 MOZ_ASSERT(IsValidHook(handler));
6573 args.rval().set(handler);
6574 return true;
6575 }
6576
6577 static bool
DebuggerFrame_setOnStep(JSContext * cx,unsigned argc,Value * vp)6578 DebuggerFrame_setOnStep(JSContext* cx, unsigned argc, Value* vp)
6579 {
6580 THIS_FRAME(cx, argc, vp, "set onStep", args, thisobj, frame);
6581 if (!args.requireAtLeast(cx, "Debugger.Frame.set onStep", 1))
6582 return false;
6583 if (!IsValidHook(args[0])) {
6584 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
6585 return false;
6586 }
6587
6588 Value prior = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
6589 if (!args[0].isUndefined() && prior.isUndefined()) {
6590 // Single stepping toggled off->on.
6591 AutoCompartment ac(cx, frame.scopeChain());
6592 // Ensure observability *before* incrementing the step mode
6593 // count. Calling this function after calling incrementStepModeCount
6594 // will make it a no-op.
6595 Debugger* dbg = Debugger::fromChildJSObject(thisobj);
6596 if (!dbg->ensureExecutionObservabilityOfScript(cx, frame.script()))
6597 return false;
6598 if (!frame.script()->incrementStepModeCount(cx))
6599 return false;
6600 } else if (args[0].isUndefined() && !prior.isUndefined()) {
6601 // Single stepping toggled on->off.
6602 frame.script()->decrementStepModeCount(cx->runtime()->defaultFreeOp());
6603 }
6604
6605 /* Now that the step mode switch has succeeded, we can install the handler. */
6606 thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, args[0]);
6607 args.rval().setUndefined();
6608 return true;
6609 }
6610
6611 static bool
DebuggerFrame_getOnPop(JSContext * cx,unsigned argc,Value * vp)6612 DebuggerFrame_getOnPop(JSContext* cx, unsigned argc, Value* vp)
6613 {
6614 THIS_FRAME(cx, argc, vp, "get onPop", args, thisobj, frame);
6615 (void) frame; // Silence GCC warning
6616 RootedValue handler(cx, thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER));
6617 MOZ_ASSERT(IsValidHook(handler));
6618 args.rval().set(handler);
6619 return true;
6620 }
6621
6622 static bool
DebuggerFrame_setOnPop(JSContext * cx,unsigned argc,Value * vp)6623 DebuggerFrame_setOnPop(JSContext* cx, unsigned argc, Value* vp)
6624 {
6625 THIS_FRAME(cx, argc, vp, "set onPop", args, thisobj, frame);
6626 if (!args.requireAtLeast(cx, "Debugger.Frame.set onPop", 1))
6627 return false;
6628 (void) frame; // Silence GCC warning
6629 if (!IsValidHook(args[0])) {
6630 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
6631 return false;
6632 }
6633
6634 thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER, args[0]);
6635 args.rval().setUndefined();
6636 return true;
6637 }
6638
6639 /*
6640 * Evaluate |chars[0..length-1]| in the environment |env|, treating that
6641 * source as appearing starting at |lineno| in |filename|. Store the return
6642 * value in |*rval|. Use |thisv| as the 'this' value.
6643 *
6644 * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env|
6645 * must be either |frame|'s DebugScopeObject, or some extension of that
6646 * environment; either way, |frame|'s scope is where newly declared variables
6647 * go. In this case, |frame| must have a computed 'this' value, equal to |thisv|.
6648 */
6649 static bool
EvaluateInEnv(JSContext * cx,Handle<Env * > env,AbstractFramePtr frame,jsbytecode * pc,mozilla::Range<const char16_t> chars,const char * filename,unsigned lineno,MutableHandleValue rval)6650 EvaluateInEnv(JSContext* cx, Handle<Env*> env, AbstractFramePtr frame,
6651 jsbytecode* pc, mozilla::Range<const char16_t> chars, const char* filename,
6652 unsigned lineno, MutableHandleValue rval)
6653 {
6654 assertSameCompartment(cx, env, frame);
6655 MOZ_ASSERT_IF(frame, pc);
6656
6657 /*
6658 * Pass in a StaticEvalObject *not* linked to env for evalStaticScope, as
6659 * ScopeIter should stop at any non-ScopeObject or non-syntactic With
6660 * boundaries, and we are putting a DebugScopeProxy or non-syntactic With on
6661 * the scope chain.
6662 */
6663 Rooted<ScopeObject*> enclosingStaticScope(cx);
6664 if (!IsGlobalLexicalScope(env)) {
6665 // If we are doing a global evalWithBindings, we will still need to
6666 // link the static global lexical scope to the static non-syntactic
6667 // scope.
6668 if (IsGlobalLexicalScope(env->enclosingScope()))
6669 enclosingStaticScope = &cx->global()->lexicalScope().staticBlock();
6670 enclosingStaticScope = StaticNonSyntacticScopeObjects::create(cx, enclosingStaticScope);
6671 if (!enclosingStaticScope)
6672 return false;
6673 } else {
6674 enclosingStaticScope = &cx->global()->lexicalScope().staticBlock();
6675 }
6676
6677 // Do not consider executeInGlobal{WithBindings} as an eval, but instead
6678 // as executing a series of statements at the global level. This is to
6679 // circumvent the fresh lexical scope that all eval have, so that the
6680 // users of executeInGlobal, like the web console, may add new bindings to
6681 // the global scope.
6682 Rooted<ScopeObject*> staticScope(cx);
6683 if (frame) {
6684 staticScope = StaticEvalObject::create(cx, enclosingStaticScope);
6685 if (!staticScope)
6686 return false;
6687 } else {
6688 staticScope = enclosingStaticScope;
6689 }
6690
6691 CompileOptions options(cx);
6692 options.setIsRunOnce(true)
6693 .setForEval(true)
6694 .setNoScriptRval(false)
6695 .setFileAndLine(filename, lineno)
6696 .setCanLazilyParse(false)
6697 .setIntroductionType("debugger eval")
6698 .maybeMakeStrictMode(frame ? frame.script()->strict() : false);
6699 RootedScript callerScript(cx, frame ? frame.script() : nullptr);
6700 SourceBufferHolder srcBuf(chars.start().get(), chars.length(), SourceBufferHolder::NoOwnership);
6701 RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), env, staticScope,
6702 callerScript, options, srcBuf,
6703 /* source = */ nullptr));
6704 if (!script)
6705 return false;
6706
6707 // Again, executeInGlobal is not considered eval.
6708 if (frame) {
6709 if (script->strict())
6710 staticScope->as<StaticEvalObject>().setStrict();
6711 script->setActiveEval();
6712 }
6713
6714 ExecuteType type = !frame ? EXECUTE_GLOBAL : EXECUTE_DEBUG;
6715 return ExecuteKernel(cx, script, *env, NullValue(), type, frame, rval.address());
6716 }
6717
6718 enum EvalBindings { EvalHasExtraBindings = true, EvalWithDefaultBindings = false };
6719
6720 static bool
DebuggerGenericEval(JSContext * cx,const char * fullMethodName,const Value & code,EvalBindings evalWithBindings,HandleValue bindings,HandleValue options,MutableHandleValue vp,Debugger * dbg,HandleObject scope,ScriptFrameIter * iter)6721 DebuggerGenericEval(JSContext* cx, const char* fullMethodName, const Value& code,
6722 EvalBindings evalWithBindings, HandleValue bindings, HandleValue options,
6723 MutableHandleValue vp, Debugger* dbg, HandleObject scope,
6724 ScriptFrameIter* iter)
6725 {
6726 /* Either we're specifying the frame, or a global. */
6727 MOZ_ASSERT_IF(iter, !scope);
6728 MOZ_ASSERT_IF(!iter, scope && IsGlobalLexicalScope(scope));
6729
6730 /* Check the first argument, the eval code string. */
6731 if (!code.isString()) {
6732 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
6733 fullMethodName, "string", InformalValueTypeName(code));
6734 return false;
6735 }
6736 RootedLinearString linear(cx, code.toString()->ensureLinear(cx));
6737 if (!linear)
6738 return false;
6739
6740 /*
6741 * Gather keys and values of bindings, if any. This must be done in the
6742 * debugger compartment, since that is where any exceptions must be
6743 * thrown.
6744 */
6745 AutoIdVector keys(cx);
6746 AutoValueVector values(cx);
6747 if (evalWithBindings) {
6748 RootedObject bindingsobj(cx, NonNullObject(cx, bindings));
6749 if (!bindingsobj ||
6750 !GetPropertyKeys(cx, bindingsobj, JSITER_OWNONLY, &keys) ||
6751 !values.growBy(keys.length()))
6752 {
6753 return false;
6754 }
6755 for (size_t i = 0; i < keys.length(); i++) {
6756 MutableHandleValue valp = values[i];
6757 if (!GetProperty(cx, bindingsobj, bindingsobj, keys[i], valp) ||
6758 !dbg->unwrapDebuggeeValue(cx, valp))
6759 {
6760 return false;
6761 }
6762 }
6763 }
6764
6765 /* Set options from object if provided. */
6766 JSAutoByteString url_bytes;
6767 char* url = nullptr;
6768 unsigned lineNumber = 1;
6769
6770 if (options.isObject()) {
6771 RootedObject opts(cx, &options.toObject());
6772 RootedValue v(cx);
6773
6774 if (!JS_GetProperty(cx, opts, "url", &v))
6775 return false;
6776 if (!v.isUndefined()) {
6777 RootedString url_str(cx, ToString<CanGC>(cx, v));
6778 if (!url_str)
6779 return false;
6780 url = url_bytes.encodeLatin1(cx, url_str);
6781 if (!url)
6782 return false;
6783 }
6784
6785 if (!JS_GetProperty(cx, opts, "lineNumber", &v))
6786 return false;
6787 if (!v.isUndefined()) {
6788 uint32_t lineno;
6789 if (!ToUint32(cx, v, &lineno))
6790 return false;
6791 lineNumber = lineno;
6792 }
6793 }
6794
6795 Maybe<AutoCompartment> ac;
6796 if (iter)
6797 ac.emplace(cx, iter->scopeChain(cx));
6798 else
6799 ac.emplace(cx, scope);
6800
6801 Rooted<Env*> env(cx);
6802 if (iter) {
6803 env = GetDebugScopeForFrame(cx, iter->abstractFramePtr(), iter->pc());
6804 if (!env)
6805 return false;
6806 } else {
6807 env = scope;
6808 }
6809
6810 /* If evalWithBindings, create the inner environment. */
6811 if (evalWithBindings) {
6812 RootedPlainObject nenv(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
6813 if (!nenv)
6814 return false;
6815 RootedId id(cx);
6816 for (size_t i = 0; i < keys.length(); i++) {
6817 id = keys[i];
6818 MutableHandleValue val = values[i];
6819 if (!cx->compartment()->wrap(cx, val) ||
6820 !NativeDefineProperty(cx, nenv, id, val, nullptr, nullptr, 0))
6821 {
6822 return false;
6823 }
6824 }
6825
6826 AutoObjectVector scopeChain(cx);
6827 if (!scopeChain.append(nenv))
6828 return false;
6829
6830 RootedObject dynamicScope(cx);
6831 if (!CreateScopeObjectsForScopeChain(cx, scopeChain, env, &dynamicScope))
6832 return false;
6833
6834 env = dynamicScope;
6835 }
6836
6837 /* Run the code and produce the completion value. */
6838 RootedValue rval(cx);
6839 AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr();
6840 jsbytecode* pc = iter ? iter->pc() : nullptr;
6841 AutoStableStringChars stableChars(cx);
6842 if (!stableChars.initTwoByte(cx, linear))
6843 return false;
6844
6845 mozilla::Range<const char16_t> chars = stableChars.twoByteRange();
6846 bool ok = EvaluateInEnv(cx, env, frame, pc, chars, url ? url : "debugger eval code",
6847 lineNumber, &rval);
6848 return dbg->receiveCompletionValue(ac, ok, rval, vp);
6849 }
6850
6851 static bool
DebuggerFrame_eval(JSContext * cx,unsigned argc,Value * vp)6852 DebuggerFrame_eval(JSContext* cx, unsigned argc, Value* vp)
6853 {
6854 THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter);
6855 if (!args.requireAtLeast(cx, "Debugger.Frame.prototype.eval", 1))
6856 return false;
6857 Debugger* dbg = Debugger::fromChildJSObject(thisobj);
6858 UpdateFrameIterPc(iter);
6859 return DebuggerGenericEval(cx, "Debugger.Frame.prototype.eval",
6860 args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue,
6861 args.get(1), args.rval(), dbg, nullptr, &iter);
6862 }
6863
6864 static bool
DebuggerFrame_evalWithBindings(JSContext * cx,unsigned argc,Value * vp)6865 DebuggerFrame_evalWithBindings(JSContext* cx, unsigned argc, Value* vp)
6866 {
6867 THIS_FRAME_ITER(cx, argc, vp, "evalWithBindings", args, thisobj, _, iter);
6868 if (!args.requireAtLeast(cx, "Debugger.Frame.prototype.evalWithBindings", 2))
6869 return false;
6870 Debugger* dbg = Debugger::fromChildJSObject(thisobj);
6871 UpdateFrameIterPc(iter);
6872 return DebuggerGenericEval(cx, "Debugger.Frame.prototype.evalWithBindings",
6873 args[0], EvalHasExtraBindings, args[1], args.get(2),
6874 args.rval(), dbg, nullptr, &iter);
6875 }
6876
6877 static bool
DebuggerFrame_construct(JSContext * cx,unsigned argc,Value * vp)6878 DebuggerFrame_construct(JSContext* cx, unsigned argc, Value* vp)
6879 {
6880 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
6881 "Debugger.Frame");
6882 return false;
6883 }
6884
6885 static const JSPropertySpec DebuggerFrame_properties[] = {
6886 JS_PSG("arguments", DebuggerFrame_getArguments, 0),
6887 JS_PSG("callee", DebuggerFrame_getCallee, 0),
6888 JS_PSG("constructing", DebuggerFrame_getConstructing, 0),
6889 JS_PSG("environment", DebuggerFrame_getEnvironment, 0),
6890 JS_PSG("generator", DebuggerFrame_getGenerator, 0),
6891 JS_PSG("live", DebuggerFrame_getLive, 0),
6892 JS_PSG("offset", DebuggerFrame_getOffset, 0),
6893 JS_PSG("older", DebuggerFrame_getOlder, 0),
6894 JS_PSG("script", DebuggerFrame_getScript, 0),
6895 JS_PSG("this", DebuggerFrame_getThis, 0),
6896 JS_PSG("type", DebuggerFrame_getType, 0),
6897 JS_PSG("implementation", DebuggerFrame_getImplementation, 0),
6898 JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0),
6899 JS_PSGS("onPop", DebuggerFrame_getOnPop, DebuggerFrame_setOnPop, 0),
6900 JS_PS_END
6901 };
6902
6903 static const JSFunctionSpec DebuggerFrame_methods[] = {
6904 JS_FN("eval", DebuggerFrame_eval, 1, 0),
6905 JS_FN("evalWithBindings", DebuggerFrame_evalWithBindings, 1, 0),
6906 JS_FS_END
6907 };
6908
6909
6910 /*** Debugger.Object *****************************************************************************/
6911
6912 void
DebuggerObject_trace(JSTracer * trc,JSObject * obj)6913 DebuggerObject_trace(JSTracer* trc, JSObject* obj)
6914 {
6915 /*
6916 * There is a barrier on private pointers, so the Unbarriered marking
6917 * is okay.
6918 */
6919 if (JSObject* referent = (JSObject*) obj->as<NativeObject>().getPrivate()) {
6920 TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
6921 "Debugger.Object referent");
6922 obj->as<NativeObject>().setPrivateUnbarriered(referent);
6923 }
6924 }
6925
6926 const Class DebuggerObject_class = {
6927 "Object",
6928 JSCLASS_HAS_PRIVATE |
6929 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT),
6930 nullptr, nullptr, nullptr, nullptr,
6931 nullptr, nullptr, nullptr, nullptr,
6932 nullptr, /* call */
6933 nullptr, /* hasInstance */
6934 nullptr, /* construct */
6935 DebuggerObject_trace
6936 };
6937
6938 static NativeObject*
DebuggerObject_checkThis(JSContext * cx,const CallArgs & args,const char * fnname)6939 DebuggerObject_checkThis(JSContext* cx, const CallArgs& args, const char* fnname)
6940 {
6941 JSObject* thisobj = NonNullObject(cx, args.thisv());
6942 if (!thisobj)
6943 return nullptr;
6944 if (thisobj->getClass() != &DebuggerObject_class) {
6945 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
6946 "Debugger.Object", fnname, thisobj->getClass()->name);
6947 return nullptr;
6948 }
6949
6950 /*
6951 * Forbid Debugger.Object.prototype, which is of class DebuggerObject_class
6952 * but isn't a real working Debugger.Object. The prototype object is
6953 * distinguished by having no referent.
6954 */
6955 NativeObject* nthisobj = &thisobj->as<NativeObject>();
6956 if (!nthisobj->getPrivate()) {
6957 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
6958 "Debugger.Object", fnname, "prototype object");
6959 return nullptr;
6960 }
6961 return nthisobj;
6962 }
6963
6964 #define THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, fnname, args, obj) \
6965 CallArgs args = CallArgsFromVp(argc, vp); \
6966 RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname)); \
6967 if (!obj) \
6968 return false; \
6969 obj = (JSObject*) obj->as<NativeObject>().getPrivate(); \
6970 MOZ_ASSERT(obj)
6971
6972 #define THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, fnname, args, dbg, obj) \
6973 CallArgs args = CallArgsFromVp(argc, vp); \
6974 RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname)); \
6975 if (!obj) \
6976 return false; \
6977 Debugger* dbg = Debugger::fromChildJSObject(obj); \
6978 obj = (JSObject*) obj->as<NativeObject>().getPrivate(); \
6979 MOZ_ASSERT(obj)
6980
6981 static bool
DebuggerObject_construct(JSContext * cx,unsigned argc,Value * vp)6982 DebuggerObject_construct(JSContext* cx, unsigned argc, Value* vp)
6983 {
6984 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
6985 "Debugger.Object");
6986 return false;
6987 }
6988
6989 static bool
DebuggerObject_getProto(JSContext * cx,unsigned argc,Value * vp)6990 DebuggerObject_getProto(JSContext* cx, unsigned argc, Value* vp)
6991 {
6992 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get proto", args, dbg, refobj);
6993 RootedObject proto(cx);
6994 {
6995 AutoCompartment ac(cx, refobj);
6996 if (!GetPrototype(cx, refobj, &proto))
6997 return false;
6998 }
6999 RootedValue protov(cx, ObjectOrNullValue(proto));
7000 if (!dbg->wrapDebuggeeValue(cx, &protov))
7001 return false;
7002 args.rval().set(protov);
7003 return true;
7004 }
7005
7006 static bool
DebuggerObject_getClass(JSContext * cx,unsigned argc,Value * vp)7007 DebuggerObject_getClass(JSContext* cx, unsigned argc, Value* vp)
7008 {
7009 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get class", args, refobj);
7010 const char* className;
7011 {
7012 AutoCompartment ac(cx, refobj);
7013 className = GetObjectClassName(cx, refobj);
7014 }
7015 JSAtom* str = Atomize(cx, className, strlen(className));
7016 if (!str)
7017 return false;
7018 args.rval().setString(str);
7019 return true;
7020 }
7021
7022 static bool
DebuggerObject_getCallable(JSContext * cx,unsigned argc,Value * vp)7023 DebuggerObject_getCallable(JSContext* cx, unsigned argc, Value* vp)
7024 {
7025 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get callable", args, refobj);
7026 args.rval().setBoolean(refobj->isCallable());
7027 return true;
7028 }
7029
7030 static bool
DebuggerObject_getName(JSContext * cx,unsigned argc,Value * vp)7031 DebuggerObject_getName(JSContext* cx, unsigned argc, Value* vp)
7032 {
7033 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get name", args, dbg, obj);
7034 if (!obj->is<JSFunction>()) {
7035 args.rval().setUndefined();
7036 return true;
7037 }
7038
7039 JSString* name = obj->as<JSFunction>().atom();
7040 if (!name) {
7041 args.rval().setUndefined();
7042 return true;
7043 }
7044
7045 RootedValue namev(cx, StringValue(name));
7046 if (!dbg->wrapDebuggeeValue(cx, &namev))
7047 return false;
7048 args.rval().set(namev);
7049 return true;
7050 }
7051
7052 static bool
DebuggerObject_getDisplayName(JSContext * cx,unsigned argc,Value * vp)7053 DebuggerObject_getDisplayName(JSContext* cx, unsigned argc, Value* vp)
7054 {
7055 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get display name", args, dbg, obj);
7056 if (!obj->is<JSFunction>()) {
7057 args.rval().setUndefined();
7058 return true;
7059 }
7060
7061 JSString* name = obj->as<JSFunction>().displayAtom();
7062 if (!name) {
7063 args.rval().setUndefined();
7064 return true;
7065 }
7066
7067 RootedValue namev(cx, StringValue(name));
7068 if (!dbg->wrapDebuggeeValue(cx, &namev))
7069 return false;
7070 args.rval().set(namev);
7071 return true;
7072 }
7073
7074 static bool
DebuggerObject_getParameterNames(JSContext * cx,unsigned argc,Value * vp)7075 DebuggerObject_getParameterNames(JSContext* cx, unsigned argc, Value* vp)
7076 {
7077 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get parameterNames", args, dbg, obj);
7078 if (!obj->is<JSFunction>()) {
7079 args.rval().setUndefined();
7080 return true;
7081 }
7082
7083 RootedFunction fun(cx, &obj->as<JSFunction>());
7084
7085 /* Only hand out parameter info for debuggee functions. */
7086 if (!dbg->observesGlobal(&fun->global())) {
7087 args.rval().setUndefined();
7088 return true;
7089 }
7090
7091 RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, fun->nargs()));
7092 if (!result)
7093 return false;
7094 result->ensureDenseInitializedLength(cx, 0, fun->nargs());
7095
7096 if (fun->isInterpreted()) {
7097 RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
7098 if (!script)
7099 return false;
7100
7101 MOZ_ASSERT(fun->nargs() == script->bindings.numArgs());
7102
7103 if (fun->nargs() > 0) {
7104 BindingIter bi(script);
7105 for (size_t i = 0; i < fun->nargs(); i++, bi++) {
7106 MOZ_ASSERT(bi.argIndex() == i);
7107 Value v;
7108 if (bi->name()->length() == 0)
7109 v = UndefinedValue();
7110 else
7111 v = StringValue(bi->name());
7112 result->setDenseElement(i, v);
7113 }
7114 }
7115 } else {
7116 for (size_t i = 0; i < fun->nargs(); i++)
7117 result->setDenseElement(i, UndefinedValue());
7118 }
7119
7120 args.rval().setObject(*result);
7121 return true;
7122 }
7123
7124 static bool
DebuggerObject_getScript(JSContext * cx,unsigned argc,Value * vp)7125 DebuggerObject_getScript(JSContext* cx, unsigned argc, Value* vp)
7126 {
7127 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get script", args, dbg, obj);
7128
7129 if (!obj->is<JSFunction>()) {
7130 args.rval().setUndefined();
7131 return true;
7132 }
7133
7134 RootedFunction fun(cx, &obj->as<JSFunction>());
7135 if (!fun->isInterpreted()) {
7136 args.rval().setUndefined();
7137 return true;
7138 }
7139
7140 RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
7141 if (!script)
7142 return false;
7143
7144 /* Only hand out debuggee scripts. */
7145 if (!dbg->observesScript(script)) {
7146 args.rval().setNull();
7147 return true;
7148 }
7149
7150 RootedObject scriptObject(cx, dbg->wrapScript(cx, script));
7151 if (!scriptObject)
7152 return false;
7153
7154 args.rval().setObject(*scriptObject);
7155 return true;
7156 }
7157
7158 static bool
DebuggerObject_getEnvironment(JSContext * cx,unsigned argc,Value * vp)7159 DebuggerObject_getEnvironment(JSContext* cx, unsigned argc, Value* vp)
7160 {
7161 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get environment", args, dbg, obj);
7162
7163 /* Don't bother switching compartments just to check obj's type and get its env. */
7164 if (!obj->is<JSFunction>() || !obj->as<JSFunction>().isInterpreted()) {
7165 args.rval().setUndefined();
7166 return true;
7167 }
7168
7169 /* Only hand out environments of debuggee functions. */
7170 if (!dbg->observesGlobal(&obj->global())) {
7171 args.rval().setNull();
7172 return true;
7173 }
7174
7175 Rooted<Env*> env(cx);
7176 {
7177 AutoCompartment ac(cx, obj);
7178 RootedFunction fun(cx, &obj->as<JSFunction>());
7179 env = GetDebugScopeForFunction(cx, fun);
7180 if (!env)
7181 return false;
7182 }
7183
7184 return dbg->wrapEnvironment(cx, env, args.rval());
7185 }
7186
7187 static bool
DebuggerObject_getIsArrowFunction(JSContext * cx,unsigned argc,Value * vp)7188 DebuggerObject_getIsArrowFunction(JSContext* cx, unsigned argc, Value* vp)
7189 {
7190 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get isArrowFunction", args, refobj);
7191
7192 args.rval().setBoolean(refobj->is<JSFunction>()
7193 && refobj->as<JSFunction>().isArrow());
7194 return true;
7195 }
7196
7197 static bool
DebuggerObject_getIsBoundFunction(JSContext * cx,unsigned argc,Value * vp)7198 DebuggerObject_getIsBoundFunction(JSContext* cx, unsigned argc, Value* vp)
7199 {
7200 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get isBoundFunction", args, refobj);
7201
7202 args.rval().setBoolean(refobj->isBoundFunction());
7203 return true;
7204 }
7205
7206 static bool
DebuggerObject_getBoundTargetFunction(JSContext * cx,unsigned argc,Value * vp)7207 DebuggerObject_getBoundTargetFunction(JSContext* cx, unsigned argc, Value* vp)
7208 {
7209 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get boundFunctionTarget", args, dbg, refobj);
7210
7211 if (!refobj->isBoundFunction()) {
7212 args.rval().setUndefined();
7213 return true;
7214 }
7215
7216 args.rval().setObject(*refobj->as<JSFunction>().getBoundFunctionTarget());
7217 return dbg->wrapDebuggeeValue(cx, args.rval());
7218 }
7219
7220 static bool
DebuggerObject_getBoundThis(JSContext * cx,unsigned argc,Value * vp)7221 DebuggerObject_getBoundThis(JSContext* cx, unsigned argc, Value* vp)
7222 {
7223 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get boundThis", args, dbg, refobj);
7224
7225 if (!refobj->isBoundFunction()) {
7226 args.rval().setUndefined();
7227 return true;
7228 }
7229 args.rval().set(refobj->as<JSFunction>().getBoundFunctionThis());
7230 return dbg->wrapDebuggeeValue(cx, args.rval());
7231 }
7232
7233 static bool
DebuggerObject_getBoundArguments(JSContext * cx,unsigned argc,Value * vp)7234 DebuggerObject_getBoundArguments(JSContext* cx, unsigned argc, Value* vp)
7235 {
7236 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get boundArguments", args, dbg, refobj);
7237
7238 if (!refobj->isBoundFunction()) {
7239 args.rval().setUndefined();
7240 return true;
7241 }
7242
7243 Rooted<JSFunction*> fun(cx, &refobj->as<JSFunction>());
7244 size_t length = fun->getBoundFunctionArgumentCount();
7245 AutoValueVector boundArgs(cx);
7246 if (!boundArgs.resize(length))
7247 return false;
7248 for (size_t i = 0; i < length; i++) {
7249 boundArgs[i].set(fun->getBoundFunctionArgument(i));
7250 if (!dbg->wrapDebuggeeValue(cx, boundArgs[i]))
7251 return false;
7252 }
7253
7254 JSObject* aobj = NewDenseCopiedArray(cx, boundArgs.length(), boundArgs.begin());
7255 if (!aobj)
7256 return false;
7257 args.rval().setObject(*aobj);
7258 return true;
7259 }
7260
7261 static bool
DebuggerObject_getGlobal(JSContext * cx,unsigned argc,Value * vp)7262 DebuggerObject_getGlobal(JSContext* cx, unsigned argc, Value* vp)
7263 {
7264 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get global", args, dbg, obj);
7265
7266 RootedValue v(cx, ObjectValue(obj->global()));
7267 if (!dbg->wrapDebuggeeValue(cx, &v))
7268 return false;
7269 args.rval().set(v);
7270 return true;
7271 }
7272
7273 static bool
null(CallArgs & args)7274 null(CallArgs& args)
7275 {
7276 args.rval().setNull();
7277 return true;
7278 }
7279
7280 /* static */ SavedFrame*
getObjectAllocationSite(JSObject & obj)7281 Debugger::getObjectAllocationSite(JSObject& obj)
7282 {
7283 JSObject* metadata = GetObjectMetadata(&obj);
7284 if (!metadata)
7285 return nullptr;
7286
7287 MOZ_ASSERT(!metadata->is<WrapperObject>());
7288 return SavedFrame::isSavedFrameAndNotProto(*metadata)
7289 ? &metadata->as<SavedFrame>()
7290 : nullptr;
7291 }
7292
7293 static bool
DebuggerObject_getAllocationSite(JSContext * cx,unsigned argc,Value * vp)7294 DebuggerObject_getAllocationSite(JSContext* cx, unsigned argc, Value* vp)
7295 {
7296 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get allocationSite", args, obj);
7297
7298 RootedObject allocSite(cx, Debugger::getObjectAllocationSite(*obj));
7299 if (!allocSite)
7300 return null(args);
7301 if (!cx->compartment()->wrap(cx, &allocSite))
7302 return false;
7303 args.rval().setObject(*allocSite);
7304 return true;
7305 }
7306
7307 static bool
DebuggerObject_getOwnPropertyDescriptor(JSContext * cx,unsigned argc,Value * vp)7308 DebuggerObject_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, Value* vp)
7309 {
7310 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "getOwnPropertyDescriptor", args, dbg, obj);
7311
7312 RootedId id(cx);
7313 if (!ValueToId<CanGC>(cx, args.get(0), &id))
7314 return false;
7315
7316 /* Bug: This can cause the debuggee to run! */
7317 Rooted<PropertyDescriptor> desc(cx);
7318 {
7319 Maybe<AutoCompartment> ac;
7320 ac.emplace(cx, obj);
7321
7322 ErrorCopier ec(ac);
7323 if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
7324 return false;
7325 }
7326
7327 if (desc.object()) {
7328 /* Rewrap the debuggee values in desc for the debugger. */
7329 if (!dbg->wrapDebuggeeValue(cx, desc.value()))
7330 return false;
7331
7332 if (desc.hasGetterObject()) {
7333 RootedValue get(cx, ObjectOrNullValue(desc.getterObject()));
7334 if (!dbg->wrapDebuggeeValue(cx, &get))
7335 return false;
7336 desc.setGetterObject(get.toObjectOrNull());
7337 }
7338 if (desc.hasSetterObject()) {
7339 RootedValue set(cx, ObjectOrNullValue(desc.setterObject()));
7340 if (!dbg->wrapDebuggeeValue(cx, &set))
7341 return false;
7342 desc.setSetterObject(set.toObjectOrNull());
7343 }
7344 }
7345
7346 return FromPropertyDescriptor(cx, desc, args.rval());
7347 }
7348
7349
7350 static bool
getOwnPropertyKeys(JSContext * cx,unsigned argc,unsigned flags,Value * vp)7351 getOwnPropertyKeys(JSContext* cx, unsigned argc, unsigned flags, Value* vp)
7352 {
7353 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "getOwnPropertyKeys", args, obj);
7354 AutoIdVector keys(cx);
7355 {
7356 Maybe<AutoCompartment> ac;
7357 ac.emplace(cx, obj);
7358 ErrorCopier ec(ac);
7359 if (!GetPropertyKeys(cx, obj, flags, &keys))
7360 return false;
7361 }
7362
7363 AutoValueVector vals(cx);
7364 if (!vals.resize(keys.length()))
7365 return false;
7366
7367 for (size_t i = 0, len = keys.length(); i < len; i++) {
7368 jsid id = keys[i];
7369 if (JSID_IS_INT(id)) {
7370 JSString* str = Int32ToString<CanGC>(cx, JSID_TO_INT(id));
7371 if (!str)
7372 return false;
7373 vals[i].setString(str);
7374 } else if (JSID_IS_ATOM(id)) {
7375 vals[i].setString(JSID_TO_STRING(id));
7376 } else if (JSID_IS_SYMBOL(id)) {
7377 vals[i].setSymbol(JSID_TO_SYMBOL(id));
7378 } else {
7379 MOZ_ASSERT_UNREACHABLE("GetPropertyKeys must return only string, int, and Symbol jsids");
7380 }
7381 }
7382
7383 JSObject* aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin());
7384 if (!aobj)
7385 return false;
7386 args.rval().setObject(*aobj);
7387 return true;
7388 }
7389
7390 static bool
DebuggerObject_getOwnPropertyNames(JSContext * cx,unsigned argc,Value * vp)7391 DebuggerObject_getOwnPropertyNames(JSContext* cx, unsigned argc, Value* vp) {
7392 return getOwnPropertyKeys(cx, argc, JSITER_OWNONLY | JSITER_HIDDEN, vp);
7393 }
7394
7395 static bool
DebuggerObject_getOwnPropertySymbols(JSContext * cx,unsigned argc,Value * vp)7396 DebuggerObject_getOwnPropertySymbols(JSContext* cx, unsigned argc, Value* vp) {
7397 return getOwnPropertyKeys(cx, argc,
7398 JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY,
7399 vp);
7400 }
7401
7402 static bool
DebuggerObject_defineProperty(JSContext * cx,unsigned argc,Value * vp)7403 DebuggerObject_defineProperty(JSContext* cx, unsigned argc, Value* vp)
7404 {
7405 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperty", args, dbg, obj);
7406 if (!args.requireAtLeast(cx, "Debugger.Object.defineProperty", 2))
7407 return false;
7408
7409 RootedId id(cx);
7410 if (!ValueToId<CanGC>(cx, args[0], &id))
7411 return false;
7412
7413 Rooted<PropertyDescriptor> desc(cx);
7414 if (!ToPropertyDescriptor(cx, args[1], false, &desc))
7415 return false;
7416
7417 if (!dbg->unwrapPropertyDescriptor(cx, obj, &desc))
7418 return false;
7419 if (!CheckPropertyDescriptorAccessors(cx, desc))
7420 return false;
7421
7422 {
7423 Maybe<AutoCompartment> ac;
7424 ac.emplace(cx, obj);
7425 if (!cx->compartment()->wrap(cx, &desc))
7426 return false;
7427
7428 ErrorCopier ec(ac);
7429 if (!DefineProperty(cx, obj, id, desc))
7430 return false;
7431 }
7432
7433 args.rval().setUndefined();
7434 return true;
7435 }
7436
7437 static bool
DebuggerObject_defineProperties(JSContext * cx,unsigned argc,Value * vp)7438 DebuggerObject_defineProperties(JSContext* cx, unsigned argc, Value* vp)
7439 {
7440 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperties", args, dbg, obj);
7441 if (!args.requireAtLeast(cx, "Debugger.Object.defineProperties", 1))
7442 return false;
7443
7444 RootedValue arg(cx, args[0]);
7445 RootedObject props(cx, ToObject(cx, arg));
7446 if (!props)
7447 return false;
7448
7449 AutoIdVector ids(cx);
7450 Rooted<PropertyDescriptorVector> descs(cx, PropertyDescriptorVector(cx));
7451 if (!ReadPropertyDescriptors(cx, props, false, &ids, &descs))
7452 return false;
7453 size_t n = ids.length();
7454
7455 for (size_t i = 0; i < n; i++) {
7456 if (!dbg->unwrapPropertyDescriptor(cx, obj, descs[i]))
7457 return false;
7458 if (!CheckPropertyDescriptorAccessors(cx, descs[i]))
7459 return false;
7460 }
7461
7462 {
7463 Maybe<AutoCompartment> ac;
7464 ac.emplace(cx, obj);
7465 for (size_t i = 0; i < n; i++) {
7466 if (!cx->compartment()->wrap(cx, descs[i]))
7467 return false;
7468 }
7469
7470 ErrorCopier ec(ac);
7471 for (size_t i = 0; i < n; i++) {
7472 if (!DefineProperty(cx, obj, ids[i], descs[i]))
7473 return false;
7474 }
7475 }
7476
7477 args.rval().setUndefined();
7478 return true;
7479 }
7480
7481 /*
7482 * This does a non-strict delete, as a matter of API design. The case where the
7483 * property is non-configurable isn't necessarily exceptional here.
7484 */
7485 static bool
DebuggerObject_deleteProperty(JSContext * cx,unsigned argc,Value * vp)7486 DebuggerObject_deleteProperty(JSContext* cx, unsigned argc, Value* vp)
7487 {
7488 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "deleteProperty", args, obj);
7489 RootedId id(cx);
7490 if (!ValueToId<CanGC>(cx, args.get(0), &id))
7491 return false;
7492
7493 Maybe<AutoCompartment> ac;
7494 ac.emplace(cx, obj);
7495 ErrorCopier ec(ac);
7496
7497 ObjectOpResult result;
7498 if (!DeleteProperty(cx, obj, id, result))
7499 return false;
7500 args.rval().setBoolean(result.ok());
7501 return true;
7502 }
7503
7504 enum SealHelperOp { OpSeal, OpFreeze, OpPreventExtensions };
7505
7506 static bool
DebuggerObject_sealHelper(JSContext * cx,unsigned argc,Value * vp,SealHelperOp op,const char * name)7507 DebuggerObject_sealHelper(JSContext* cx, unsigned argc, Value* vp, SealHelperOp op, const char* name)
7508 {
7509 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, name, args, obj);
7510
7511 Maybe<AutoCompartment> ac;
7512 ac.emplace(cx, obj);
7513 ErrorCopier ec(ac);
7514 if (op == OpSeal) {
7515 if (!SetIntegrityLevel(cx, obj, IntegrityLevel::Sealed))
7516 return false;
7517 } else if (op == OpFreeze) {
7518 if (!SetIntegrityLevel(cx, obj, IntegrityLevel::Frozen))
7519 return false;
7520 } else {
7521 MOZ_ASSERT(op == OpPreventExtensions);
7522 if (!PreventExtensions(cx, obj))
7523 return false;
7524 }
7525 args.rval().setUndefined();
7526 return true;
7527 }
7528
7529 static bool
DebuggerObject_seal(JSContext * cx,unsigned argc,Value * vp)7530 DebuggerObject_seal(JSContext* cx, unsigned argc, Value* vp)
7531 {
7532 return DebuggerObject_sealHelper(cx, argc, vp, OpSeal, "seal");
7533 }
7534
7535 static bool
DebuggerObject_freeze(JSContext * cx,unsigned argc,Value * vp)7536 DebuggerObject_freeze(JSContext* cx, unsigned argc, Value* vp)
7537 {
7538 return DebuggerObject_sealHelper(cx, argc, vp, OpFreeze, "freeze");
7539 }
7540
7541 static bool
DebuggerObject_preventExtensions(JSContext * cx,unsigned argc,Value * vp)7542 DebuggerObject_preventExtensions(JSContext* cx, unsigned argc, Value* vp)
7543 {
7544 return DebuggerObject_sealHelper(cx, argc, vp, OpPreventExtensions, "preventExtensions");
7545 }
7546
7547 static bool
DebuggerObject_isSealedHelper(JSContext * cx,unsigned argc,Value * vp,SealHelperOp op,const char * name)7548 DebuggerObject_isSealedHelper(JSContext* cx, unsigned argc, Value* vp, SealHelperOp op,
7549 const char* name)
7550 {
7551 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, name, args, obj);
7552
7553 Maybe<AutoCompartment> ac;
7554 ac.emplace(cx, obj);
7555 ErrorCopier ec(ac);
7556 bool r;
7557 if (op == OpSeal) {
7558 if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Sealed, &r))
7559 return false;
7560 } else if (op == OpFreeze) {
7561 if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Frozen, &r))
7562 return false;
7563 } else {
7564 if (!IsExtensible(cx, obj, &r))
7565 return false;
7566 }
7567 args.rval().setBoolean(r);
7568 return true;
7569 }
7570
7571 static bool
DebuggerObject_isSealed(JSContext * cx,unsigned argc,Value * vp)7572 DebuggerObject_isSealed(JSContext* cx, unsigned argc, Value* vp)
7573 {
7574 return DebuggerObject_isSealedHelper(cx, argc, vp, OpSeal, "isSealed");
7575 }
7576
7577 static bool
DebuggerObject_isFrozen(JSContext * cx,unsigned argc,Value * vp)7578 DebuggerObject_isFrozen(JSContext* cx, unsigned argc, Value* vp)
7579 {
7580 return DebuggerObject_isSealedHelper(cx, argc, vp, OpFreeze, "isFrozen");
7581 }
7582
7583 static bool
DebuggerObject_isExtensible(JSContext * cx,unsigned argc,Value * vp)7584 DebuggerObject_isExtensible(JSContext* cx, unsigned argc, Value* vp)
7585 {
7586 return DebuggerObject_isSealedHelper(cx, argc, vp, OpPreventExtensions, "isExtensible");
7587 }
7588
7589 enum ApplyOrCallMode { ApplyMode, CallMode };
7590
7591 static bool
ApplyOrCall(JSContext * cx,unsigned argc,Value * vp,ApplyOrCallMode mode)7592 ApplyOrCall(JSContext* cx, unsigned argc, Value* vp, ApplyOrCallMode mode)
7593 {
7594 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "apply", args, dbg, obj);
7595
7596 /*
7597 * Any JS exceptions thrown must be in the debugger compartment, so do
7598 * sanity checks and fallible conversions before entering the debuggee.
7599 */
7600 RootedValue calleev(cx, ObjectValue(*obj));
7601 if (!obj->isCallable()) {
7602 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
7603 "Debugger.Object", "apply", obj->getClass()->name);
7604 return false;
7605 }
7606
7607 /*
7608 * Unwrap Debugger.Objects. This happens in the debugger's compartment since
7609 * that is where any exceptions must be reported.
7610 */
7611 RootedValue thisv(cx, args.get(0));
7612 if (!dbg->unwrapDebuggeeValue(cx, &thisv))
7613 return false;
7614 unsigned callArgc = 0;
7615 Value* callArgv = nullptr;
7616 AutoValueVector argv(cx);
7617 if (mode == ApplyMode) {
7618 if (args.length() >= 2 && !args[1].isNullOrUndefined()) {
7619 if (!args[1].isObject()) {
7620 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_APPLY_ARGS,
7621 js_apply_str);
7622 return false;
7623 }
7624 RootedObject argsobj(cx, &args[1].toObject());
7625 if (!GetLengthProperty(cx, argsobj, &callArgc))
7626 return false;
7627 callArgc = unsigned(Min(callArgc, ARGS_LENGTH_MAX));
7628 if (!argv.growBy(callArgc) || !GetElements(cx, argsobj, callArgc, argv.begin()))
7629 return false;
7630 callArgv = argv.begin();
7631 }
7632 } else {
7633 callArgc = args.length() > 0 ? unsigned(Min(args.length() - 1, ARGS_LENGTH_MAX)) : 0;
7634 callArgv = args.array() + 1;
7635 }
7636
7637 AutoArrayRooter callArgvRooter(cx, callArgc, callArgv);
7638 for (unsigned i = 0; i < callArgc; i++) {
7639 if (!dbg->unwrapDebuggeeValue(cx, callArgvRooter.handleAt(i)))
7640 return false;
7641 }
7642
7643 /*
7644 * Enter the debuggee compartment and rewrap all input value for that compartment.
7645 * (Rewrapping always takes place in the destination compartment.)
7646 */
7647 Maybe<AutoCompartment> ac;
7648 ac.emplace(cx, obj);
7649 if (!cx->compartment()->wrap(cx, &calleev) || !cx->compartment()->wrap(cx, &thisv))
7650 return false;
7651
7652 RootedValue arg(cx);
7653 for (unsigned i = 0; i < callArgc; i++) {
7654 if (!cx->compartment()->wrap(cx, callArgvRooter.handleAt(i)))
7655 return false;
7656 }
7657
7658 /*
7659 * Call the function. Use receiveCompletionValue to return to the debugger
7660 * compartment and populate args.rval().
7661 */
7662 RootedValue rval(cx);
7663 bool ok = Invoke(cx, thisv, calleev, callArgc, callArgv, &rval);
7664 return dbg->receiveCompletionValue(ac, ok, rval, args.rval());
7665 }
7666
7667 static bool
DebuggerObject_apply(JSContext * cx,unsigned argc,Value * vp)7668 DebuggerObject_apply(JSContext* cx, unsigned argc, Value* vp)
7669 {
7670 return ApplyOrCall(cx, argc, vp, ApplyMode);
7671 }
7672
7673 static bool
DebuggerObject_call(JSContext * cx,unsigned argc,Value * vp)7674 DebuggerObject_call(JSContext* cx, unsigned argc, Value* vp)
7675 {
7676 return ApplyOrCall(cx, argc, vp, CallMode);
7677 }
7678
7679 static bool
DebuggerObject_makeDebuggeeValue(JSContext * cx,unsigned argc,Value * vp)7680 DebuggerObject_makeDebuggeeValue(JSContext* cx, unsigned argc, Value* vp)
7681 {
7682 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "makeDebuggeeValue", args, dbg, referent);
7683 if (!args.requireAtLeast(cx, "Debugger.Object.prototype.makeDebuggeeValue", 1))
7684 return false;
7685
7686 RootedValue arg0(cx, args[0]);
7687
7688 /* Non-objects are already debuggee values. */
7689 if (arg0.isObject()) {
7690 // Enter this Debugger.Object's referent's compartment, and wrap the
7691 // argument as appropriate for references from there.
7692 {
7693 AutoCompartment ac(cx, referent);
7694 if (!cx->compartment()->wrap(cx, &arg0))
7695 return false;
7696 }
7697
7698 // Back in the debugger's compartment, produce a new Debugger.Object
7699 // instance referring to the wrapped argument.
7700 if (!dbg->wrapDebuggeeValue(cx, &arg0))
7701 return false;
7702 }
7703
7704 args.rval().set(arg0);
7705 return true;
7706 }
7707
7708 static bool
RequireGlobalObject(JSContext * cx,HandleValue dbgobj,HandleObject referent)7709 RequireGlobalObject(JSContext* cx, HandleValue dbgobj, HandleObject referent)
7710 {
7711 RootedObject obj(cx, referent);
7712
7713 if (!obj->is<GlobalObject>()) {
7714 const char* isWrapper = "";
7715 const char* isWindowProxy = "";
7716
7717 /* Help the poor programmer by pointing out wrappers around globals... */
7718 if (obj->is<WrapperObject>()) {
7719 obj = js::UncheckedUnwrap(obj);
7720 isWrapper = "a wrapper around ";
7721 }
7722
7723 /* ... and WindowProxies around Windows. */
7724 if (IsWindowProxy(obj)) {
7725 obj = ToWindowIfWindowProxy(obj);
7726 isWindowProxy = "a WindowProxy referring to ";
7727 }
7728
7729 if (obj->is<GlobalObject>()) {
7730 ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_WRAPPER_IN_WAY,
7731 JSDVG_SEARCH_STACK, dbgobj, nullptr,
7732 isWrapper, isWindowProxy);
7733 } else {
7734 ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_BAD_REFERENT,
7735 JSDVG_SEARCH_STACK, dbgobj, nullptr,
7736 "a global object", nullptr);
7737 }
7738 return false;
7739 }
7740
7741 return true;
7742 }
7743
7744 static bool
DebuggerObject_executeInGlobal(JSContext * cx,unsigned argc,Value * vp)7745 DebuggerObject_executeInGlobal(JSContext* cx, unsigned argc, Value* vp)
7746 {
7747 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "executeInGlobal", args, dbg, referent);
7748 if (!args.requireAtLeast(cx, "Debugger.Object.prototype.executeInGlobal", 1))
7749 return false;
7750 if (!RequireGlobalObject(cx, args.thisv(), referent))
7751 return false;
7752
7753 RootedObject globalLexical(cx, &referent->as<GlobalObject>().lexicalScope());
7754 return DebuggerGenericEval(cx, "Debugger.Object.prototype.executeInGlobal",
7755 args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue,
7756 args.get(1), args.rval(), dbg, globalLexical, nullptr);
7757 }
7758
7759 static bool
DebuggerObject_executeInGlobalWithBindings(JSContext * cx,unsigned argc,Value * vp)7760 DebuggerObject_executeInGlobalWithBindings(JSContext* cx, unsigned argc, Value* vp)
7761 {
7762 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "executeInGlobalWithBindings", args, dbg,
7763 referent);
7764 if (!args.requireAtLeast(cx, "Debugger.Object.prototype.executeInGlobalWithBindings", 2))
7765 return false;
7766 if (!RequireGlobalObject(cx, args.thisv(), referent))
7767 return false;
7768
7769 RootedObject globalLexical(cx, &referent->as<GlobalObject>().lexicalScope());
7770 return DebuggerGenericEval(cx, "Debugger.Object.prototype.executeInGlobalWithBindings",
7771 args[0], EvalHasExtraBindings, args[1], args.get(2),
7772 args.rval(), dbg, globalLexical, nullptr);
7773 }
7774
7775 static bool
DebuggerObject_asEnvironment(JSContext * cx,unsigned argc,Value * vp)7776 DebuggerObject_asEnvironment(JSContext* cx, unsigned argc, Value* vp)
7777 {
7778 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "asEnvironment", args, dbg, referent);
7779 if (!RequireGlobalObject(cx, args.thisv(), referent))
7780 return false;
7781
7782 Rooted<Env*> env(cx);
7783 {
7784 AutoCompartment ac(cx, referent);
7785 env = GetDebugScopeForGlobalLexicalScope(cx);
7786 if (!env)
7787 return false;
7788 }
7789
7790 return dbg->wrapEnvironment(cx, env, args.rval());
7791 }
7792
7793 static bool
DebuggerObject_unwrap(JSContext * cx,unsigned argc,Value * vp)7794 DebuggerObject_unwrap(JSContext* cx, unsigned argc, Value* vp)
7795 {
7796 THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "unwrap", args, dbg, referent);
7797 JSObject* unwrapped = UnwrapOneChecked(referent);
7798 if (!unwrapped) {
7799 args.rval().setNull();
7800 return true;
7801 }
7802
7803 // Don't allow unwrapping to create a D.O whose referent is in an
7804 // invisible-to-Debugger global. (If our referent is a *wrapper* to such,
7805 // and the wrapper is in a visible compartment, that's fine.)
7806 JSCompartment* unwrappedCompartment = unwrapped->compartment();
7807 if (unwrappedCompartment->options().invisibleToDebugger()) {
7808 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
7809 JSMSG_DEBUG_INVISIBLE_COMPARTMENT);
7810 return false;
7811 }
7812
7813 args.rval().setObject(*unwrapped);
7814 if (!dbg->wrapDebuggeeValue(cx, args.rval()))
7815 return false;
7816 return true;
7817 }
7818
7819 static bool
DebuggerObject_unsafeDereference(JSContext * cx,unsigned argc,Value * vp)7820 DebuggerObject_unsafeDereference(JSContext* cx, unsigned argc, Value* vp)
7821 {
7822 THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "unsafeDereference", args, referent);
7823 args.rval().setObject(*referent);
7824 if (!cx->compartment()->wrap(cx, args.rval()))
7825 return false;
7826
7827 // Wrapping should return the WindowProxy.
7828 MOZ_ASSERT(!IsWindow(&args.rval().toObject()));
7829
7830 return true;
7831 }
7832
7833 static const JSPropertySpec DebuggerObject_properties[] = {
7834 JS_PSG("proto", DebuggerObject_getProto, 0),
7835 JS_PSG("class", DebuggerObject_getClass, 0),
7836 JS_PSG("callable", DebuggerObject_getCallable, 0),
7837 JS_PSG("name", DebuggerObject_getName, 0),
7838 JS_PSG("displayName", DebuggerObject_getDisplayName, 0),
7839 JS_PSG("parameterNames", DebuggerObject_getParameterNames, 0),
7840 JS_PSG("script", DebuggerObject_getScript, 0),
7841 JS_PSG("environment", DebuggerObject_getEnvironment, 0),
7842 JS_PSG("isArrowFunction", DebuggerObject_getIsArrowFunction, 0),
7843 JS_PSG("isBoundFunction", DebuggerObject_getIsBoundFunction, 0),
7844 JS_PSG("boundTargetFunction", DebuggerObject_getBoundTargetFunction, 0),
7845 JS_PSG("boundThis", DebuggerObject_getBoundThis, 0),
7846 JS_PSG("boundArguments", DebuggerObject_getBoundArguments, 0),
7847 JS_PSG("global", DebuggerObject_getGlobal, 0),
7848 JS_PSG("allocationSite", DebuggerObject_getAllocationSite, 0),
7849 JS_PS_END
7850 };
7851
7852 static const JSFunctionSpec DebuggerObject_methods[] = {
7853 JS_FN("getOwnPropertyDescriptor", DebuggerObject_getOwnPropertyDescriptor, 1, 0),
7854 JS_FN("getOwnPropertyNames", DebuggerObject_getOwnPropertyNames, 0, 0),
7855 JS_FN("getOwnPropertySymbols", DebuggerObject_getOwnPropertySymbols, 0, 0),
7856 JS_FN("defineProperty", DebuggerObject_defineProperty, 2, 0),
7857 JS_FN("defineProperties", DebuggerObject_defineProperties, 1, 0),
7858 JS_FN("deleteProperty", DebuggerObject_deleteProperty, 1, 0),
7859 JS_FN("seal", DebuggerObject_seal, 0, 0),
7860 JS_FN("freeze", DebuggerObject_freeze, 0, 0),
7861 JS_FN("preventExtensions", DebuggerObject_preventExtensions, 0, 0),
7862 JS_FN("isSealed", DebuggerObject_isSealed, 0, 0),
7863 JS_FN("isFrozen", DebuggerObject_isFrozen, 0, 0),
7864 JS_FN("isExtensible", DebuggerObject_isExtensible, 0, 0),
7865 JS_FN("apply", DebuggerObject_apply, 0, 0),
7866 JS_FN("call", DebuggerObject_call, 0, 0),
7867 JS_FN("makeDebuggeeValue", DebuggerObject_makeDebuggeeValue, 1, 0),
7868 JS_FN("executeInGlobal", DebuggerObject_executeInGlobal, 1, 0),
7869 JS_FN("executeInGlobalWithBindings", DebuggerObject_executeInGlobalWithBindings, 2, 0),
7870 JS_FN("asEnvironment", DebuggerObject_asEnvironment, 0, 0),
7871 JS_FN("unwrap", DebuggerObject_unwrap, 0, 0),
7872 JS_FN("unsafeDereference", DebuggerObject_unsafeDereference, 0, 0),
7873 JS_FS_END
7874 };
7875
7876
7877 /*** Debugger.Environment ************************************************************************/
7878
7879 void
DebuggerEnv_trace(JSTracer * trc,JSObject * obj)7880 DebuggerEnv_trace(JSTracer* trc, JSObject* obj)
7881 {
7882 /*
7883 * There is a barrier on private pointers, so the Unbarriered marking
7884 * is okay.
7885 */
7886 if (Env* referent = (JSObject*) obj->as<NativeObject>().getPrivate()) {
7887 TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
7888 "Debugger.Environment referent");
7889 obj->as<NativeObject>().setPrivateUnbarriered(referent);
7890 }
7891 }
7892
7893 const Class DebuggerEnv_class = {
7894 "Environment",
7895 JSCLASS_HAS_PRIVATE |
7896 JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGENV_COUNT),
7897 nullptr, nullptr, nullptr, nullptr,
7898 nullptr, nullptr, nullptr, nullptr,
7899 nullptr, /* call */
7900 nullptr, /* hasInstance */
7901 nullptr, /* construct */
7902 DebuggerEnv_trace
7903 };
7904
7905 static NativeObject*
DebuggerEnv_checkThis(JSContext * cx,const CallArgs & args,const char * fnname,bool requireDebuggee=true)7906 DebuggerEnv_checkThis(JSContext* cx, const CallArgs& args, const char* fnname,
7907 bool requireDebuggee = true)
7908 {
7909 JSObject* thisobj = NonNullObject(cx, args.thisv());
7910 if (!thisobj)
7911 return nullptr;
7912 if (thisobj->getClass() != &DebuggerEnv_class) {
7913 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
7914 "Debugger.Environment", fnname, thisobj->getClass()->name);
7915 return nullptr;
7916 }
7917
7918 /*
7919 * Forbid Debugger.Environment.prototype, which is of class DebuggerEnv_class
7920 * but isn't a real working Debugger.Environment. The prototype object is
7921 * distinguished by having no referent.
7922 */
7923 NativeObject* nthisobj = &thisobj->as<NativeObject>();
7924 if (!nthisobj->getPrivate()) {
7925 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
7926 "Debugger.Environment", fnname, "prototype object");
7927 return nullptr;
7928 }
7929
7930 /*
7931 * Forbid access to Debugger.Environment objects that are not debuggee
7932 * environments.
7933 */
7934 if (requireDebuggee) {
7935 Rooted<Env*> env(cx, static_cast<Env*>(nthisobj->getPrivate()));
7936 if (!Debugger::fromChildJSObject(nthisobj)->observesGlobal(&env->global())) {
7937 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGEE,
7938 "Debugger.Environment", "environment");
7939 return nullptr;
7940 }
7941 }
7942
7943 return nthisobj;
7944 }
7945
7946 #define THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env) \
7947 CallArgs args = CallArgsFromVp(argc, vp); \
7948 NativeObject* envobj = DebuggerEnv_checkThis(cx, args, fnname); \
7949 if (!envobj) \
7950 return false; \
7951 Rooted<Env*> env(cx, static_cast<Env*>(envobj->getPrivate())); \
7952 MOZ_ASSERT(env); \
7953 MOZ_ASSERT(!IsSyntacticScope(env));
7954
7955 #define THIS_DEBUGENV_OWNER(cx, argc, vp, fnname, args, envobj, env, dbg) \
7956 THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env); \
7957 Debugger* dbg = Debugger::fromChildJSObject(envobj)
7958
7959 static bool
DebuggerEnv_construct(JSContext * cx,unsigned argc,Value * vp)7960 DebuggerEnv_construct(JSContext* cx, unsigned argc, Value* vp)
7961 {
7962 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
7963 "Debugger.Environment");
7964 return false;
7965 }
7966
7967 static bool
IsDeclarative(Env * env)7968 IsDeclarative(Env* env)
7969 {
7970 return env->is<DebugScopeObject>() && env->as<DebugScopeObject>().isForDeclarative();
7971 }
7972
7973 static bool
IsWith(Env * env)7974 IsWith(Env* env)
7975 {
7976 return env->is<DebugScopeObject>() &&
7977 env->as<DebugScopeObject>().scope().is<DynamicWithObject>();
7978 }
7979
7980 static bool
DebuggerEnv_getType(JSContext * cx,unsigned argc,Value * vp)7981 DebuggerEnv_getType(JSContext* cx, unsigned argc, Value* vp)
7982 {
7983 THIS_DEBUGENV(cx, argc, vp, "get type", args, envobj, env);
7984
7985 /* Don't bother switching compartments just to check env's class. */
7986 const char* s;
7987 if (IsDeclarative(env))
7988 s = "declarative";
7989 else if (IsWith(env))
7990 s = "with";
7991 else
7992 s = "object";
7993
7994 JSAtom* str = Atomize(cx, s, strlen(s), PinAtom);
7995 if (!str)
7996 return false;
7997 args.rval().setString(str);
7998 return true;
7999 }
8000
8001 static bool
DebuggerEnv_getParent(JSContext * cx,unsigned argc,Value * vp)8002 DebuggerEnv_getParent(JSContext* cx, unsigned argc, Value* vp)
8003 {
8004 THIS_DEBUGENV_OWNER(cx, argc, vp, "get parent", args, envobj, env, dbg);
8005
8006 /* Don't bother switching compartments just to get env's parent. */
8007 Rooted<Env*> parent(cx, env->enclosingScope());
8008 return dbg->wrapEnvironment(cx, parent, args.rval());
8009 }
8010
8011 static bool
DebuggerEnv_getObject(JSContext * cx,unsigned argc,Value * vp)8012 DebuggerEnv_getObject(JSContext* cx, unsigned argc, Value* vp)
8013 {
8014 THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg);
8015
8016 /*
8017 * Don't bother switching compartments just to check env's class and
8018 * possibly get its proto.
8019 */
8020 if (IsDeclarative(env)) {
8021 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NO_SCOPE_OBJECT);
8022 return false;
8023 }
8024
8025 JSObject* obj;
8026 if (IsWith(env)) {
8027 obj = &env->as<DebugScopeObject>().scope().as<DynamicWithObject>().object();
8028 } else {
8029 obj = env;
8030 MOZ_ASSERT(!obj->is<DebugScopeObject>());
8031 }
8032
8033 args.rval().setObject(*obj);
8034 if (!dbg->wrapDebuggeeValue(cx, args.rval()))
8035 return false;
8036 return true;
8037 }
8038
8039 static bool
DebuggerEnv_getCallee(JSContext * cx,unsigned argc,Value * vp)8040 DebuggerEnv_getCallee(JSContext* cx, unsigned argc, Value* vp)
8041 {
8042 THIS_DEBUGENV_OWNER(cx, argc, vp, "get callee", args, envobj, env, dbg);
8043
8044 args.rval().setNull();
8045
8046 if (!env->is<DebugScopeObject>())
8047 return true;
8048
8049 JSObject& scope = env->as<DebugScopeObject>().scope();
8050 if (!scope.is<CallObject>())
8051 return true;
8052
8053 CallObject& callobj = scope.as<CallObject>();
8054 if (callobj.isForEval())
8055 return true;
8056
8057 JSFunction& callee = callobj.callee();
8058 if (IsInternalFunctionObject(callee))
8059 return true;
8060
8061 args.rval().setObject(callee);
8062 if (!dbg->wrapDebuggeeValue(cx, args.rval()))
8063 return false;
8064 return true;
8065 }
8066
8067 static bool
DebuggerEnv_getInspectable(JSContext * cx,unsigned argc,Value * vp)8068 DebuggerEnv_getInspectable(JSContext* cx, unsigned argc, Value* vp)
8069 {
8070 CallArgs args = CallArgsFromVp(argc, vp);
8071 NativeObject* envobj = DebuggerEnv_checkThis(cx, args, "get inspectable", false);
8072 if (!envobj)
8073 return false;
8074 Rooted<Env*> env(cx, static_cast<Env*>(envobj->getPrivate()));
8075 MOZ_ASSERT(env);
8076 MOZ_ASSERT(!env->is<ScopeObject>());
8077
8078 Debugger* dbg = Debugger::fromChildJSObject(envobj);
8079
8080 args.rval().setBoolean(dbg->observesGlobal(&env->global()));
8081 return true;
8082 }
8083
8084 static bool
DebuggerEnv_getOptimizedOut(JSContext * cx,unsigned argc,Value * vp)8085 DebuggerEnv_getOptimizedOut(JSContext* cx, unsigned argc, Value* vp)
8086 {
8087 CallArgs args = CallArgsFromVp(argc, vp);
8088 NativeObject* envobj = DebuggerEnv_checkThis(cx, args, "get optimizedOut", false);
8089 if (!envobj)
8090 return false;
8091 Rooted<Env*> env(cx, static_cast<Env*>(envobj->getPrivate()));
8092 MOZ_ASSERT(env);
8093 MOZ_ASSERT(!env->is<ScopeObject>());
8094
8095 args.rval().setBoolean(env->is<DebugScopeObject>() &&
8096 env->as<DebugScopeObject>().isOptimizedOut());
8097 return true;
8098 }
8099
8100 static bool
DebuggerEnv_names(JSContext * cx,unsigned argc,Value * vp)8101 DebuggerEnv_names(JSContext* cx, unsigned argc, Value* vp)
8102 {
8103 THIS_DEBUGENV(cx, argc, vp, "names", args, envobj, env);
8104
8105 AutoIdVector keys(cx);
8106 {
8107 Maybe<AutoCompartment> ac;
8108 ac.emplace(cx, env);
8109 ErrorCopier ec(ac);
8110 if (!GetPropertyKeys(cx, env, JSITER_HIDDEN, &keys))
8111 return false;
8112 }
8113
8114 RootedObject arr(cx, NewDenseEmptyArray(cx));
8115 if (!arr)
8116 return false;
8117 RootedId id(cx);
8118 for (size_t i = 0, len = keys.length(); i < len; i++) {
8119 id = keys[i];
8120 if (JSID_IS_ATOM(id) && IsIdentifier(JSID_TO_ATOM(id))) {
8121 if (!NewbornArrayPush(cx, arr, StringValue(JSID_TO_STRING(id))))
8122 return false;
8123 }
8124 }
8125 args.rval().setObject(*arr);
8126 return true;
8127 }
8128
8129 static bool
DebuggerEnv_find(JSContext * cx,unsigned argc,Value * vp)8130 DebuggerEnv_find(JSContext* cx, unsigned argc, Value* vp)
8131 {
8132 THIS_DEBUGENV_OWNER(cx, argc, vp, "find", args, envobj, env, dbg);
8133 if (!args.requireAtLeast(cx, "Debugger.Environment.find", 1))
8134 return false;
8135
8136 RootedId id(cx);
8137 if (!ValueToIdentifier(cx, args[0], &id))
8138 return false;
8139
8140 {
8141 Maybe<AutoCompartment> ac;
8142 ac.emplace(cx, env);
8143
8144 /* This can trigger resolve hooks. */
8145 ErrorCopier ec(ac);
8146 bool found;
8147 for (; env; env = env->enclosingScope()) {
8148 if (!HasProperty(cx, env, id, &found))
8149 return false;
8150 if (found)
8151 break;
8152 }
8153 }
8154
8155 return dbg->wrapEnvironment(cx, env, args.rval());
8156 }
8157
8158 static bool
DebuggerEnv_getVariable(JSContext * cx,unsigned argc,Value * vp)8159 DebuggerEnv_getVariable(JSContext* cx, unsigned argc, Value* vp)
8160 {
8161 THIS_DEBUGENV_OWNER(cx, argc, vp, "getVariable", args, envobj, env, dbg);
8162 if (!args.requireAtLeast(cx, "Debugger.Environment.getVariable", 1))
8163 return false;
8164
8165 RootedId id(cx);
8166 if (!ValueToIdentifier(cx, args[0], &id))
8167 return false;
8168
8169 RootedValue v(cx);
8170 {
8171 Maybe<AutoCompartment> ac;
8172 ac.emplace(cx, env);
8173
8174 /* This can trigger getters. */
8175 ErrorCopier ec(ac);
8176
8177 // For DebugScopeObjects, we get sentinel values for optimized out
8178 // slots and arguments instead of throwing (the default behavior).
8179 //
8180 // See wrapDebuggeeValue for how the sentinel values are wrapped.
8181 if (env->is<DebugScopeObject>()) {
8182 if (!env->as<DebugScopeObject>().getMaybeSentinelValue(cx, id, &v))
8183 return false;
8184 } else {
8185 if (!GetProperty(cx, env, env, id, &v))
8186 return false;
8187 }
8188 }
8189
8190 // When we've faked up scope chain objects for optimized-out scopes,
8191 // declarative environments may contain internal JSFunction objects, which
8192 // we shouldn't expose to the user.
8193 if (v.isObject()) {
8194 RootedObject obj(cx, &v.toObject());
8195 if (obj->is<JSFunction>() &&
8196 IsInternalFunctionObject(obj->as<JSFunction>()))
8197 v.setMagic(JS_OPTIMIZED_OUT);
8198 }
8199
8200 if (!dbg->wrapDebuggeeValue(cx, &v))
8201 return false;
8202 args.rval().set(v);
8203 return true;
8204 }
8205
8206 static bool
DebuggerEnv_setVariable(JSContext * cx,unsigned argc,Value * vp)8207 DebuggerEnv_setVariable(JSContext* cx, unsigned argc, Value* vp)
8208 {
8209 THIS_DEBUGENV_OWNER(cx, argc, vp, "setVariable", args, envobj, env, dbg);
8210 if (!args.requireAtLeast(cx, "Debugger.Environment.setVariable", 2))
8211 return false;
8212
8213 RootedId id(cx);
8214 if (!ValueToIdentifier(cx, args[0], &id))
8215 return false;
8216
8217 RootedValue v(cx, args[1]);
8218 if (!dbg->unwrapDebuggeeValue(cx, &v))
8219 return false;
8220
8221 {
8222 Maybe<AutoCompartment> ac;
8223 ac.emplace(cx, env);
8224 if (!cx->compartment()->wrap(cx, &v))
8225 return false;
8226
8227 /* This can trigger setters. */
8228 ErrorCopier ec(ac);
8229
8230 /* Make sure the environment actually has the specified binding. */
8231 bool has;
8232 if (!HasProperty(cx, env, id, &has))
8233 return false;
8234 if (!has) {
8235 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_VARIABLE_NOT_FOUND);
8236 return false;
8237 }
8238
8239 /* Just set the property. */
8240 if (!SetProperty(cx, env, id, v))
8241 return false;
8242 }
8243
8244 args.rval().setUndefined();
8245 return true;
8246 }
8247
8248 static const JSPropertySpec DebuggerEnv_properties[] = {
8249 JS_PSG("type", DebuggerEnv_getType, 0),
8250 JS_PSG("object", DebuggerEnv_getObject, 0),
8251 JS_PSG("parent", DebuggerEnv_getParent, 0),
8252 JS_PSG("callee", DebuggerEnv_getCallee, 0),
8253 JS_PSG("inspectable", DebuggerEnv_getInspectable, 0),
8254 JS_PSG("optimizedOut", DebuggerEnv_getOptimizedOut, 0),
8255 JS_PS_END
8256 };
8257
8258 static const JSFunctionSpec DebuggerEnv_methods[] = {
8259 JS_FN("names", DebuggerEnv_names, 0, 0),
8260 JS_FN("find", DebuggerEnv_find, 1, 0),
8261 JS_FN("getVariable", DebuggerEnv_getVariable, 1, 0),
8262 JS_FN("setVariable", DebuggerEnv_setVariable, 2, 0),
8263 JS_FS_END
8264 };
8265
8266
8267
8268 /*** JS::dbg::Builder ****************************************************************************/
8269
Builder(JSContext * cx,js::Debugger * debugger)8270 Builder::Builder(JSContext* cx, js::Debugger* debugger)
8271 : debuggerObject(cx, debugger->toJSObject().get()),
8272 debugger(debugger)
8273 { }
8274
8275
8276 #if DEBUG
8277 void
assertBuilt(JSObject * obj)8278 Builder::assertBuilt(JSObject* obj)
8279 {
8280 // We can't use assertSameCompartment here, because that is always keyed to
8281 // some JSContext's current compartment, whereas BuiltThings can be
8282 // constructed and assigned to without respect to any particular context;
8283 // the only constraint is that they should be in their debugger's compartment.
8284 MOZ_ASSERT_IF(obj, debuggerObject->compartment() == obj->compartment());
8285 }
8286 #endif
8287
8288 bool
definePropertyToTrusted(JSContext * cx,const char * name,JS::MutableHandleValue trusted)8289 Builder::Object::definePropertyToTrusted(JSContext* cx, const char* name,
8290 JS::MutableHandleValue trusted)
8291 {
8292 // We should have checked for false Objects before calling this.
8293 MOZ_ASSERT(value);
8294
8295 JSAtom* atom = Atomize(cx, name, strlen(name));
8296 if (!atom)
8297 return false;
8298 RootedId id(cx, AtomToId(atom));
8299
8300 return DefineProperty(cx, value, id, trusted);
8301 }
8302
8303 bool
defineProperty(JSContext * cx,const char * name,JS::HandleValue propval_)8304 Builder::Object::defineProperty(JSContext* cx, const char* name, JS::HandleValue propval_)
8305 {
8306 AutoCompartment ac(cx, debuggerObject());
8307
8308 RootedValue propval(cx, propval_);
8309 if (!debugger()->wrapDebuggeeValue(cx, &propval))
8310 return false;
8311
8312 return definePropertyToTrusted(cx, name, &propval);
8313 }
8314
8315 bool
defineProperty(JSContext * cx,const char * name,JS::HandleObject propval_)8316 Builder::Object::defineProperty(JSContext* cx, const char* name, JS::HandleObject propval_)
8317 {
8318 RootedValue propval(cx, ObjectOrNullValue(propval_));
8319 return defineProperty(cx, name, propval);
8320 }
8321
8322 bool
defineProperty(JSContext * cx,const char * name,Builder::Object & propval_)8323 Builder::Object::defineProperty(JSContext* cx, const char* name, Builder::Object& propval_)
8324 {
8325 AutoCompartment ac(cx, debuggerObject());
8326
8327 RootedValue propval(cx, ObjectOrNullValue(propval_.value));
8328 return definePropertyToTrusted(cx, name, &propval);
8329 }
8330
8331 Builder::Object
newObject(JSContext * cx)8332 Builder::newObject(JSContext* cx)
8333 {
8334 AutoCompartment ac(cx, debuggerObject);
8335
8336 RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
8337
8338 // If the allocation failed, this will return a false Object, as the spec promises.
8339 return Object(cx, *this, obj);
8340 }
8341
8342
8343 /*** JS::dbg::AutoEntryMonitor ******************************************************************/
8344
AutoEntryMonitor(JSContext * cx)8345 AutoEntryMonitor::AutoEntryMonitor(JSContext* cx)
8346 : runtime_(cx->runtime()),
8347 savedMonitor_(cx->runtime()->entryMonitor)
8348 {
8349 runtime_->entryMonitor = this;
8350 }
8351
~AutoEntryMonitor()8352 AutoEntryMonitor::~AutoEntryMonitor()
8353 {
8354 runtime_->entryMonitor = savedMonitor_;
8355 }
8356
8357
8358 /*** Glue ****************************************************************************************/
8359
8360 extern JS_PUBLIC_API(bool)
JS_DefineDebuggerObject(JSContext * cx,HandleObject obj)8361 JS_DefineDebuggerObject(JSContext* cx, HandleObject obj)
8362 {
8363 RootedNativeObject
8364 objProto(cx),
8365 debugCtor(cx),
8366 debugProto(cx),
8367 frameProto(cx),
8368 scriptProto(cx),
8369 sourceProto(cx),
8370 objectProto(cx),
8371 envProto(cx),
8372 memoryProto(cx);
8373 objProto = obj->as<GlobalObject>().getOrCreateObjectPrototype(cx);
8374 if (!objProto)
8375 return false;
8376 debugProto = InitClass(cx, obj,
8377 objProto, &Debugger::jsclass, Debugger::construct,
8378 1, Debugger::properties, Debugger::methods, nullptr, nullptr,
8379 debugCtor.address());
8380 if (!debugProto)
8381 return false;
8382
8383 frameProto = InitClass(cx, debugCtor, objProto, &DebuggerFrame_class,
8384 DebuggerFrame_construct, 0,
8385 DebuggerFrame_properties, DebuggerFrame_methods,
8386 nullptr, nullptr);
8387 if (!frameProto)
8388 return false;
8389
8390 scriptProto = InitClass(cx, debugCtor, objProto, &DebuggerScript_class,
8391 DebuggerScript_construct, 0,
8392 DebuggerScript_properties, DebuggerScript_methods,
8393 nullptr, nullptr);
8394 if (!scriptProto)
8395 return false;
8396
8397 sourceProto = InitClass(cx, debugCtor, sourceProto, &DebuggerSource_class,
8398 DebuggerSource_construct, 0,
8399 DebuggerSource_properties, DebuggerSource_methods,
8400 nullptr, nullptr);
8401 if (!sourceProto)
8402 return false;
8403
8404 objectProto = InitClass(cx, debugCtor, objProto, &DebuggerObject_class,
8405 DebuggerObject_construct, 0,
8406 DebuggerObject_properties, DebuggerObject_methods,
8407 nullptr, nullptr);
8408 if (!objectProto)
8409 return false;
8410 envProto = InitClass(cx, debugCtor, objProto, &DebuggerEnv_class,
8411 DebuggerEnv_construct, 0,
8412 DebuggerEnv_properties, DebuggerEnv_methods,
8413 nullptr, nullptr);
8414 if (!envProto)
8415 return false;
8416 memoryProto = InitClass(cx, debugCtor, objProto, &DebuggerMemory::class_,
8417 DebuggerMemory::construct, 0, DebuggerMemory::properties,
8418 DebuggerMemory::methods, nullptr, nullptr);
8419 if (!memoryProto)
8420 return false;
8421
8422 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto));
8423 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_OBJECT_PROTO, ObjectValue(*objectProto));
8424 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SCRIPT_PROTO, ObjectValue(*scriptProto));
8425 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SOURCE_PROTO, ObjectValue(*sourceProto));
8426 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_ENV_PROTO, ObjectValue(*envProto));
8427 debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO, ObjectValue(*memoryProto));
8428 return true;
8429 }
8430
8431 static inline void
AssertIsPromise(JSContext * cx,HandleObject promise)8432 AssertIsPromise(JSContext* cx, HandleObject promise)
8433 {
8434 MOZ_ASSERT(promise);
8435 assertSameCompartment(cx, promise);
8436 MOZ_ASSERT(strcmp(promise->getClass()->name, "Promise") == 0 ||
8437 strcmp(promise->getClass()->name, "MozAbortablePromise") == 0);
8438 }
8439
JS_PUBLIC_API(void)8440 JS_PUBLIC_API(void)
8441 JS::dbg::onNewPromise(JSContext* cx, HandleObject promise)
8442 {
8443 AssertIsPromise(cx, promise);
8444 Debugger::slowPathPromiseHook(cx, Debugger::OnNewPromise, promise);
8445 }
8446
JS_PUBLIC_API(void)8447 JS_PUBLIC_API(void)
8448 JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise)
8449 {
8450 AssertIsPromise(cx, promise);
8451 Debugger::slowPathPromiseHook(cx, Debugger::OnPromiseSettled, promise);
8452 }
8453
JS_PUBLIC_API(bool)8454 JS_PUBLIC_API(bool)
8455 JS::dbg::IsDebugger(JSObject& obj)
8456 {
8457 JSObject* unwrapped = CheckedUnwrap(&obj);
8458 return unwrapped &&
8459 js::GetObjectClass(unwrapped) == &Debugger::jsclass &&
8460 js::Debugger::fromJSObject(unwrapped) != nullptr;
8461 }
8462
JS_PUBLIC_API(bool)8463 JS_PUBLIC_API(bool)
8464 JS::dbg::GetDebuggeeGlobals(JSContext* cx, JSObject& dbgObj, AutoObjectVector& vector)
8465 {
8466 MOZ_ASSERT(IsDebugger(dbgObj));
8467 js::Debugger* dbg = js::Debugger::fromJSObject(CheckedUnwrap(&dbgObj));
8468
8469 if (!vector.reserve(vector.length() + dbg->debuggees.count())) {
8470 JS_ReportOutOfMemory(cx);
8471 return false;
8472 }
8473
8474 for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront())
8475 vector.infallibleAppend(static_cast<JSObject*>(r.front()));
8476
8477 return true;
8478 }
8479
8480
8481 /*** JS::dbg::GarbageCollectionEvent **************************************************************/
8482
8483 namespace JS {
8484 namespace dbg {
8485
8486 /* static */ GarbageCollectionEvent::Ptr
Create(JSRuntime * rt,::js::gcstats::Statistics & stats,uint64_t gcNumber)8487 GarbageCollectionEvent::Create(JSRuntime* rt, ::js::gcstats::Statistics& stats, uint64_t gcNumber)
8488 {
8489 auto data = rt->make_unique<GarbageCollectionEvent>(gcNumber);
8490 if (!data)
8491 return nullptr;
8492
8493 data->nonincrementalReason = stats.nonincrementalReason();
8494
8495 for (auto range = stats.sliceRange(); !range.empty(); range.popFront()) {
8496 if (!data->reason) {
8497 // There is only one GC reason for the whole cycle, but for legacy
8498 // reasons this data is stored and replicated on each slice. Each
8499 // slice used to have its own GCReason, but now they are all the
8500 // same.
8501 data->reason = gcstats::ExplainReason(range.front().reason);
8502 MOZ_ASSERT(data->reason);
8503 }
8504
8505 if (!data->collections.growBy(1))
8506 return nullptr;
8507
8508 data->collections.back().startTimestamp = range.front().startTimestamp;
8509 data->collections.back().endTimestamp = range.front().endTimestamp;
8510 }
8511
8512
8513 return data;
8514 }
8515
8516 static bool
DefineStringProperty(JSContext * cx,HandleObject obj,PropertyName * propName,const char * strVal)8517 DefineStringProperty(JSContext* cx, HandleObject obj, PropertyName* propName, const char* strVal)
8518 {
8519 RootedValue val(cx, UndefinedValue());
8520 if (strVal) {
8521 JSAtom* atomized = Atomize(cx, strVal, strlen(strVal));
8522 if (!atomized)
8523 return false;
8524 val = StringValue(atomized);
8525 }
8526 return DefineProperty(cx, obj, propName, val);
8527 }
8528
8529 JSObject*
toJSObject(JSContext * cx) const8530 GarbageCollectionEvent::toJSObject(JSContext* cx) const
8531 {
8532 RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
8533 RootedValue gcCycleNumberVal(cx, NumberValue(majorGCNumber_));
8534 if (!obj ||
8535 !DefineStringProperty(cx, obj, cx->names().nonincrementalReason, nonincrementalReason) ||
8536 !DefineStringProperty(cx, obj, cx->names().reason, reason) ||
8537 !DefineProperty(cx, obj, cx->names().gcCycleNumber, gcCycleNumberVal))
8538 {
8539 return nullptr;
8540 }
8541
8542 RootedArrayObject slicesArray(cx, NewDenseEmptyArray(cx));
8543 if (!slicesArray)
8544 return nullptr;
8545
8546 size_t idx = 0;
8547 for (auto range = collections.all(); !range.empty(); range.popFront()) {
8548 RootedPlainObject collectionObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
8549 if (!collectionObj)
8550 return nullptr;
8551
8552 RootedValue start(cx, NumberValue(range.front().startTimestamp));
8553 RootedValue end(cx, NumberValue(range.front().endTimestamp));
8554 if (!DefineProperty(cx, collectionObj, cx->names().startTimestamp, start) ||
8555 !DefineProperty(cx, collectionObj, cx->names().endTimestamp, end))
8556 {
8557 return nullptr;
8558 }
8559
8560 RootedValue collectionVal(cx, ObjectValue(*collectionObj));
8561 if (!DefineElement(cx, slicesArray, idx++, collectionVal))
8562 return nullptr;
8563 }
8564
8565 RootedValue slicesValue(cx, ObjectValue(*slicesArray));
8566 if (!DefineProperty(cx, obj, cx->names().collections, slicesValue))
8567 return nullptr;
8568
8569 return obj;
8570 }
8571
8572 JS_PUBLIC_API(bool)
FireOnGarbageCollectionHook(JSContext * cx,JS::dbg::GarbageCollectionEvent::Ptr && data)8573 FireOnGarbageCollectionHook(JSContext* cx, JS::dbg::GarbageCollectionEvent::Ptr&& data)
8574 {
8575 AutoObjectVector triggered(cx);
8576
8577 {
8578 // We had better not GC (and potentially get a dangling Debugger
8579 // pointer) while finding all Debuggers observing a debuggee that
8580 // participated in this GC.
8581 AutoCheckCannotGC noGC;
8582
8583 for (Debugger* dbg : cx->runtime()->debuggerList) {
8584 if (dbg->enabled &&
8585 dbg->observedGC(data->majorGCNumber()) &&
8586 dbg->getHook(Debugger::OnGarbageCollection))
8587 {
8588 if (!triggered.append(dbg->object)) {
8589 JS_ReportOutOfMemory(cx);
8590 return false;
8591 }
8592 }
8593 }
8594 }
8595
8596 for ( ; !triggered.empty(); triggered.popBack()) {
8597 Debugger* dbg = Debugger::fromJSObject(triggered.back());
8598 dbg->fireOnGarbageCollectionHook(cx, data);
8599 MOZ_ASSERT(!cx->isExceptionPending());
8600 }
8601
8602 return true;
8603 }
8604
8605 } // namespace dbg
8606 } // namespace JS
8607