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