1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
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 "debugger/Debugger-inl.h"
8 
9 #include "mozilla/Attributes.h"        // for MOZ_STACK_CLASS, MOZ_RAII
10 #include "mozilla/DebugOnly.h"         // for DebugOnly
11 #include "mozilla/DoublyLinkedList.h"  // for DoublyLinkedList<>::Iterator
12 #include "mozilla/GuardObjects.h"      // for MOZ_GUARD_OBJECT_NOTIFIER_PARAM
13 #include "mozilla/HashTable.h"         // for HashSet<>::Range, HashMapEntry
14 #include "mozilla/Maybe.h"             // for Maybe, Nothing, Some
15 #include "mozilla/ScopeExit.h"         // for MakeScopeExit, ScopeExit
16 #include "mozilla/ThreadLocal.h"       // for ThreadLocal
17 #include "mozilla/TimeStamp.h"         // for TimeStamp, TimeDuration
18 #include "mozilla/UniquePtr.h"         // for UniquePtr
19 #include "mozilla/Variant.h"           // for AsVariant, AsVariantTemporary
20 #include "mozilla/Vector.h"            // for Vector, Vector<>::ConstRange
21 
22 #include <algorithm>   // for std::find, std::max
23 #include <functional>  // for function
24 #include <stddef.h>    // for size_t
25 #include <stdint.h>    // for uint32_t, uint64_t, int32_t
26 #include <string.h>    // for strlen, strcmp
27 #include <utility>     // for std::move
28 
29 #include "jsapi.h"        // for CallArgs, CallArgsFromVp
30 #include "jsfriendapi.h"  // for GetErrorMessage
31 #include "jstypes.h"      // for JS_PUBLIC_API
32 
33 #include "builtin/Array.h"               // for NewDenseFullyAllocatedArray
34 #include "debugger/DebugAPI.h"           // for ResumeMode, DebugAPI
35 #include "debugger/DebuggerMemory.h"     // for DebuggerMemory
36 #include "debugger/DebugScript.h"        // for DebugScript
37 #include "debugger/Environment.h"        // for DebuggerEnvironment
38 #include "debugger/Frame.h"              // for DebuggerFrame
39 #include "debugger/NoExecute.h"          // for EnterDebuggeeNoExecute
40 #include "debugger/Object.h"             // for DebuggerObject
41 #include "debugger/Script.h"             // for DebuggerScript
42 #include "debugger/Source.h"             // for DebuggerSource
43 #include "frontend/NameAnalysisTypes.h"  // for ParseGoal, ParseGoal::Script
44 #include "frontend/ParseContext.h"       // for UsedNameTracker
45 #include "frontend/Parser.h"             // for Parser
46 #include "gc/Barrier.h"                  // for GCPtrNativeObject
47 #include "gc/FreeOp.h"                   // for JSFreeOp
48 #include "gc/GC.h"                       // for IterateLazyScripts
49 #include "gc/GCMarker.h"                 // for GCMarker
50 #include "gc/GCRuntime.h"                // for GCRuntime, AutoEnterIteration
51 #include "gc/HashUtil.h"                 // for DependentAddPtr
52 #include "gc/Marking.h"                  // for IsMarkedUnbarriered, IsMarked
53 #include "gc/PublicIterators.h"          // for RealmsIter, CompartmentsIter
54 #include "gc/Rooting.h"                  // for RootedNativeObject
55 #include "gc/Statistics.h"               // for Statistics::SliceData
56 #include "gc/Tracer.h"                   // for TraceEdge
57 #include "gc/Zone.h"                     // for Zone
58 #include "gc/ZoneAllocator.h"            // for ZoneAllocPolicy
59 #include "jit/BaselineDebugModeOSR.h"  // for RecompileOnStackBaselineScriptsForDebugMode
60 #include "jit/BaselineJIT.h"           // for FinishDiscardBaselineScript
61 #include "jit/Ion.h"                   // for JitContext
62 #include "jit/JitScript.h"             // for JitScript
63 #include "jit/JSJitFrameIter.h"       // for InlineFrameIterator
64 #include "jit/RematerializedFrame.h"  // for RematerializedFrame
65 #include "js/Conversions.h"           // for ToBoolean, ToUint32
66 #include "js/Debug.h"                 // for Builder::Object, Builder
67 #include "js/GCAPI.h"                 // for GarbageCollectionEvent
68 #include "js/HeapAPI.h"               // for ExposeObjectToActiveJS
69 #include "js/Promise.h"               // for AutoDebuggerJobQueueInterruption
70 #include "js/Proxy.h"                 // for PropertyDescriptor
71 #include "js/SourceText.h"            // for SourceOwnership, SourceText
72 #include "js/StableStringChars.h"     // for AutoStableStringChars
73 #include "js/UbiNode.h"               // for Node, RootList, Edge
74 #include "js/UbiNodeBreadthFirst.h"   // for BreadthFirst
75 #include "js/Warnings.h"              // for AutoSuppressWarningReporter
76 #include "js/Wrapper.h"               // for CheckedUnwrapStatic
77 #include "util/Text.h"                // for DuplicateString, js_strlen
78 #include "vm/ArrayObject.h"           // for ArrayObject
79 #include "vm/AsyncFunction.h"         // for AsyncFunctionGeneratorObject
80 #include "vm/AsyncIteration.h"        // for AsyncGeneratorObject
81 #include "vm/BytecodeUtil.h"          // for JSDVG_IGNORE_STACK
82 #include "vm/Compartment.h"           // for CrossCompartmentKey
83 #include "vm/EnvironmentObject.h"     // for IsSyntacticEnvironment
84 #include "vm/ErrorReporting.h"        // for ReportErrorToGlobal
85 #include "vm/GeneratorObject.h"       // for AbstractGeneratorObject
86 #include "vm/GlobalObject.h"          // for GlobalObject
87 #include "vm/Interpreter.h"           // for Call, ReportIsNotFunction
88 #include "vm/Iteration.h"             // for CreateIterResultObject
89 #include "vm/JSAtom.h"                // for Atomize, ClassName
90 #include "vm/JSContext.h"             // for JSContext
91 #include "vm/JSFunction.h"            // for JSFunction
92 #include "vm/JSObject.h"              // for JSObject, RequireObject
93 #include "vm/ObjectGroup.h"           // for TenuredObject
94 #include "vm/ObjectOperations.h"      // for DefineDataProperty
95 #include "vm/PlainObject.h"           // for js::PlainObject
96 #include "vm/PromiseObject.h"         // for js::PromiseObject
97 #include "vm/ProxyObject.h"           // for ProxyObject, JSObject::is
98 #include "vm/Realm.h"                 // for AutoRealm, Realm
99 #include "vm/Runtime.h"               // for ReportOutOfMemory, JSRuntime
100 #include "vm/SavedFrame.h"            // for SavedFrame
101 #include "vm/SavedStacks.h"           // for SavedStacks
102 #include "vm/Scope.h"                 // for Scope
103 #include "vm/StringType.h"            // for JSString, PropertyName
104 #include "vm/TraceLogging.h"          // for TraceLoggerForCurrentThread
105 #include "vm/TypeInference.h"         // for TypeZone
106 #include "vm/WrapperObject.h"         // for CrossCompartmentWrapperObject
107 #include "wasm/WasmDebug.h"           // for DebugState
108 #include "wasm/WasmInstance.h"        // for Instance
109 #include "wasm/WasmJS.h"              // for WasmInstanceObject
110 #include "wasm/WasmRealm.h"           // for Realm
111 #include "wasm/WasmTypes.h"           // for WasmInstanceObjectVector
112 
113 #include "debugger/DebugAPI-inl.h"
114 #include "debugger/Frame-inl.h"    // for DebuggerFrame::hasGenerator
115 #include "debugger/Script-inl.h"   // for DebuggerScript::getReferent
116 #include "gc/GC-inl.h"             // for ZoneCellIter
117 #include "gc/Marking-inl.h"        // for MaybeForwarded
118 #include "gc/WeakMap-inl.h"        // for DebuggerWeakMap::trace
119 #include "vm/Compartment-inl.h"    // for Compartment::wrap
120 #include "vm/GeckoProfiler-inl.h"  // for AutoSuppressProfilerSampling
121 #include "vm/JSAtom-inl.h"         // for AtomToId, ValueToId
122 #include "vm/JSContext-inl.h"      // for JSContext::check
123 #include "vm/JSObject-inl.h"  // for JSObject::isCallable, NewTenuredObjectWithGivenProto
124 #include "vm/JSScript-inl.h"      // for JSScript::isDebuggee, JSScript
125 #include "vm/NativeObject-inl.h"  // for NativeObject::ensureDenseInitializedLength
126 #include "vm/ObjectOperations-inl.h"  // for GetProperty, HasProperty
127 #include "vm/Realm-inl.h"             // for AutoRealm::AutoRealm
128 #include "vm/Stack-inl.h"             // for AbstractFramePtr::script
129 #include "vm/TypeInference-inl.h"     // for AutoEnterAnalysis
130 
131 namespace js {
132 
133 namespace frontend {
134 class FullParseHandler;
135 }
136 
137 namespace gc {
138 struct Cell;
139 }
140 
141 namespace jit {
142 class BaselineFrame;
143 }
144 
145 } /* namespace js */
146 
147 using namespace js;
148 
149 using JS::AutoStableStringChars;
150 using JS::CompileOptions;
151 using JS::SourceOwnership;
152 using JS::SourceText;
153 using JS::dbg::AutoEntryMonitor;
154 using JS::dbg::Builder;
155 using js::frontend::IsIdentifier;
156 using mozilla::AsVariant;
157 using mozilla::DebugOnly;
158 using mozilla::MakeScopeExit;
159 using mozilla::Maybe;
160 using mozilla::Nothing;
161 using mozilla::Some;
162 using mozilla::TimeDuration;
163 using mozilla::TimeStamp;
164 
165 /*** Utils ******************************************************************/
166 
IsInterpretedNonSelfHostedFunction(JSFunction * fun)167 bool js::IsInterpretedNonSelfHostedFunction(JSFunction* fun) {
168   return fun->isInterpreted() && !fun->isSelfHostedBuiltin();
169 }
170 
GetOrCreateFunctionScript(JSContext * cx,HandleFunction fun)171 JSScript* js::GetOrCreateFunctionScript(JSContext* cx, HandleFunction fun) {
172   MOZ_ASSERT(IsInterpretedNonSelfHostedFunction(fun));
173   AutoRealm ar(cx, fun);
174   return JSFunction::getOrCreateScript(cx, fun);
175 }
176 
ValueToIdentifier(JSContext * cx,HandleValue v,MutableHandleId id)177 bool js::ValueToIdentifier(JSContext* cx, HandleValue v, MutableHandleId id) {
178   if (!ValueToId<CanGC>(cx, v, id)) {
179     return false;
180   }
181   if (!JSID_IS_ATOM(id) || !IsIdentifier(JSID_TO_ATOM(id))) {
182     RootedValue val(cx, v);
183     ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, val,
184                      nullptr, "not an identifier");
185     return false;
186   }
187   return true;
188 }
189 
190 class js::AutoRestoreRealmDebugMode {
191   Realm* realm_;
192   unsigned bits_;
193 
194  public:
AutoRestoreRealmDebugMode(Realm * realm)195   explicit AutoRestoreRealmDebugMode(Realm* realm)
196       : realm_(realm), bits_(realm->debugModeBits_) {
197     MOZ_ASSERT(realm_);
198   }
199 
~AutoRestoreRealmDebugMode()200   ~AutoRestoreRealmDebugMode() {
201     if (realm_) {
202       realm_->debugModeBits_ = bits_;
203     }
204   }
205 
release()206   void release() { realm_ = nullptr; }
207 };
208 
209 /* static */
slowPathCheckNoExecute(JSContext * cx,HandleScript script)210 bool DebugAPI::slowPathCheckNoExecute(JSContext* cx, HandleScript script) {
211   MOZ_ASSERT(cx->realm()->isDebuggee());
212   MOZ_ASSERT(cx->noExecuteDebuggerTop);
213   return EnterDebuggeeNoExecute::reportIfFoundInStack(cx, script);
214 }
215 
NukeDebuggerWrapper(NativeObject * wrapper)216 static inline void NukeDebuggerWrapper(NativeObject* wrapper) {
217   // In some OOM failure cases, we need to destroy the edge to the referent,
218   // to avoid trying to trace it during untimely collections.
219   wrapper->setPrivate(nullptr);
220 }
221 
PropagateForcedReturn(JSContext * cx,AbstractFramePtr frame,HandleValue rval)222 static void PropagateForcedReturn(JSContext* cx, AbstractFramePtr frame,
223                                   HandleValue rval) {
224   // The Debugger's hooks may return a value that affects the completion
225   // value of the given frame. For example, a hook may return `{ return: 42 }`
226   // to terminate the frame and return `42` as the final frame result.
227   // To accomplish this, the debugger treats these return values as if
228   // execution of the JS function has been terminated without a pending
229   // exception, but with a special flag. When the error is handled by the
230   // interpreter or JIT, the special flag and the error state will be cleared
231   // and execution will continue from the end of the frame.
232   MOZ_ASSERT(!cx->isExceptionPending());
233   cx->setPropagatingForcedReturn();
234   frame.setReturnValue(rval);
235 }
236 
237 static MOZ_MUST_USE bool AdjustGeneratorResumptionValue(JSContext* cx,
238                                                         AbstractFramePtr frame,
239                                                         ResumeMode& resumeMode,
240                                                         MutableHandleValue vp);
241 
ApplyFrameResumeMode(JSContext * cx,AbstractFramePtr frame,ResumeMode resumeMode,HandleValue rv,HandleSavedFrame exnStack)242 static MOZ_MUST_USE bool ApplyFrameResumeMode(JSContext* cx,
243                                               AbstractFramePtr frame,
244                                               ResumeMode resumeMode,
245                                               HandleValue rv,
246                                               HandleSavedFrame exnStack) {
247   RootedValue rval(cx, rv);
248 
249   // The value passed in here is unwrapped had has no guarantees about what
250   // compartment it may be associated with, so we explcitly wrap it into the
251   // debuggee compartment.
252   if (!cx->compartment()->wrap(cx, &rval)) {
253     return false;
254   }
255 
256   if (!AdjustGeneratorResumptionValue(cx, frame, resumeMode, &rval)) {
257     return false;
258   }
259 
260   switch (resumeMode) {
261     case ResumeMode::Continue:
262       break;
263 
264     case ResumeMode::Throw:
265       // If we have a stack from the original throw, use it instead of
266       // associating the throw with the current execution point.
267       if (exnStack) {
268         cx->setPendingException(rval, exnStack);
269       } else {
270         cx->setPendingExceptionAndCaptureStack(rval);
271       }
272       return false;
273 
274     case ResumeMode::Terminate:
275       cx->clearPendingException();
276       return false;
277 
278     case ResumeMode::Return:
279       PropagateForcedReturn(cx, frame, rval);
280       return false;
281 
282     default:
283       MOZ_CRASH("bad Debugger::onEnterFrame resume mode");
284   }
285 
286   return true;
287 }
ApplyFrameResumeMode(JSContext * cx,AbstractFramePtr frame,ResumeMode resumeMode,HandleValue rval)288 static bool ApplyFrameResumeMode(JSContext* cx, AbstractFramePtr frame,
289                                  ResumeMode resumeMode, HandleValue rval) {
290   RootedSavedFrame nullStack(cx);
291   return ApplyFrameResumeMode(cx, frame, resumeMode, rval, nullStack);
292 }
293 
ValueToStableChars(JSContext * cx,const char * fnname,HandleValue value,AutoStableStringChars & stableChars)294 bool js::ValueToStableChars(JSContext* cx, const char* fnname,
295                             HandleValue value,
296                             AutoStableStringChars& stableChars) {
297   if (!value.isString()) {
298     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
299                               JSMSG_NOT_EXPECTED_TYPE, fnname, "string",
300                               InformalValueTypeName(value));
301     return false;
302   }
303   RootedLinearString linear(cx, value.toString()->ensureLinear(cx));
304   if (!linear) {
305     return false;
306   }
307   if (!stableChars.initTwoByte(cx, linear)) {
308     return false;
309   }
310   return true;
311 }
312 
setFilename(JSContext * cx,const char * filename)313 bool EvalOptions::setFilename(JSContext* cx, const char* filename) {
314   JS::UniqueChars copy;
315   if (filename) {
316     copy = DuplicateString(cx, filename);
317     if (!copy) {
318       return false;
319     }
320   }
321 
322   filename_ = std::move(copy);
323   return true;
324 }
325 
ParseEvalOptions(JSContext * cx,HandleValue value,EvalOptions & options)326 bool js::ParseEvalOptions(JSContext* cx, HandleValue value,
327                           EvalOptions& options) {
328   if (!value.isObject()) {
329     return true;
330   }
331 
332   RootedObject opts(cx, &value.toObject());
333 
334   RootedValue v(cx);
335   if (!JS_GetProperty(cx, opts, "url", &v)) {
336     return false;
337   }
338   if (!v.isUndefined()) {
339     RootedString url_str(cx, ToString<CanGC>(cx, v));
340     if (!url_str) {
341       return false;
342     }
343     UniqueChars url_bytes = JS_EncodeStringToLatin1(cx, url_str);
344     if (!url_bytes) {
345       return false;
346     }
347     if (!options.setFilename(cx, url_bytes.get())) {
348       return false;
349     }
350   }
351 
352   if (!JS_GetProperty(cx, opts, "lineNumber", &v)) {
353     return false;
354   }
355   if (!v.isUndefined()) {
356     uint32_t lineno;
357     if (!ToUint32(cx, v, &lineno)) {
358       return false;
359     }
360     options.setLineno(lineno);
361   }
362 
363   return true;
364 }
365 
366 /*** Breakpoints ************************************************************/
367 
isEmpty() const368 bool BreakpointSite::isEmpty() const { return breakpoints.isEmpty(); }
369 
trace(JSTracer * trc)370 void BreakpointSite::trace(JSTracer* trc) {
371   for (auto p = breakpoints.begin(); p; p++) {
372     p->trace(trc);
373   }
374 }
375 
finalize(JSFreeOp * fop)376 void BreakpointSite::finalize(JSFreeOp* fop) {
377   while (!breakpoints.isEmpty()) {
378     breakpoints.begin()->delete_(fop);
379   }
380 }
381 
firstBreakpoint() const382 Breakpoint* BreakpointSite::firstBreakpoint() const {
383   if (isEmpty()) {
384     return nullptr;
385   }
386   return &(*breakpoints.begin());
387 }
388 
hasBreakpoint(Breakpoint * toFind)389 bool BreakpointSite::hasBreakpoint(Breakpoint* toFind) {
390   const BreakpointList::Iterator bp(toFind);
391   for (auto p = breakpoints.begin(); p; p++) {
392     if (p == bp) {
393       return true;
394     }
395   }
396   return false;
397 }
398 
Breakpoint(Debugger * debugger,HandleObject wrappedDebugger,BreakpointSite * site,HandleObject handler)399 Breakpoint::Breakpoint(Debugger* debugger, HandleObject wrappedDebugger,
400                        BreakpointSite* site, HandleObject handler)
401     : debugger(debugger),
402       wrappedDebugger(wrappedDebugger),
403       site(site),
404       handler(handler) {
405   MOZ_ASSERT(UncheckedUnwrap(wrappedDebugger) == debugger->object);
406   MOZ_ASSERT(handler->compartment() == wrappedDebugger->compartment());
407 
408   debugger->breakpoints.pushBack(this);
409   site->breakpoints.pushBack(this);
410 }
411 
trace(JSTracer * trc)412 void Breakpoint::trace(JSTracer* trc) {
413   TraceEdge(trc, &wrappedDebugger, "breakpoint owner");
414   TraceEdge(trc, &handler, "breakpoint handler");
415 }
416 
delete_(JSFreeOp * fop)417 void Breakpoint::delete_(JSFreeOp* fop) {
418   debugger->breakpoints.remove(this);
419   site->breakpoints.remove(this);
420   gc::Cell* cell = site->owningCell();
421   fop->delete_(cell, this, MemoryUse::Breakpoint);
422 }
423 
remove(JSFreeOp * fop)424 void Breakpoint::remove(JSFreeOp* fop) {
425   BreakpointSite* savedSite = site;
426   delete_(fop);
427 
428   savedSite->destroyIfEmpty(fop);
429 }
430 
nextInDebugger()431 Breakpoint* Breakpoint::nextInDebugger() { return debuggerLink.mNext; }
432 
nextInSite()433 Breakpoint* Breakpoint::nextInSite() { return siteLink.mNext; }
434 
JSBreakpointSite(JSScript * script,jsbytecode * pc)435 JSBreakpointSite::JSBreakpointSite(JSScript* script, jsbytecode* pc)
436     : script(script), pc(pc) {
437   MOZ_ASSERT(!DebugAPI::hasBreakpointsAt(script, pc));
438 }
439 
remove(JSFreeOp * fop)440 void JSBreakpointSite::remove(JSFreeOp* fop) {
441   DebugScript::destroyBreakpointSite(fop, script, pc);
442 }
443 
trace(JSTracer * trc)444 void JSBreakpointSite::trace(JSTracer* trc) {
445   BreakpointSite::trace(trc);
446   TraceEdge(trc, &script, "breakpoint script");
447 }
448 
delete_(JSFreeOp * fop)449 void JSBreakpointSite::delete_(JSFreeOp* fop) {
450   BreakpointSite::finalize(fop);
451 
452   fop->delete_(script, this, MemoryUse::BreakpointSite);
453 }
454 
owningCell()455 gc::Cell* JSBreakpointSite::owningCell() { return script; }
456 
realm() const457 Realm* JSBreakpointSite::realm() const { return script->realm(); }
458 
WasmBreakpointSite(WasmInstanceObject * instanceObject_,uint32_t offset_)459 WasmBreakpointSite::WasmBreakpointSite(WasmInstanceObject* instanceObject_,
460                                        uint32_t offset_)
461     : instanceObject(instanceObject_), offset(offset_) {
462   MOZ_ASSERT(instanceObject_);
463   MOZ_ASSERT(instanceObject_->instance().debugEnabled());
464 }
465 
trace(JSTracer * trc)466 void WasmBreakpointSite::trace(JSTracer* trc) {
467   BreakpointSite::trace(trc);
468   TraceEdge(trc, &instanceObject, "breakpoint Wasm instance");
469 }
470 
remove(JSFreeOp * fop)471 void WasmBreakpointSite::remove(JSFreeOp* fop) {
472   instanceObject->instance().destroyBreakpointSite(fop, offset);
473 }
474 
delete_(JSFreeOp * fop)475 void WasmBreakpointSite::delete_(JSFreeOp* fop) {
476   BreakpointSite::finalize(fop);
477 
478   fop->delete_(instanceObject, this, MemoryUse::BreakpointSite);
479 }
480 
owningCell()481 gc::Cell* WasmBreakpointSite::owningCell() { return instanceObject; }
482 
realm() const483 Realm* WasmBreakpointSite::realm() const { return instanceObject->realm(); }
484 
485 /*** Debugger hook dispatch *************************************************/
486 
Debugger(JSContext * cx,NativeObject * dbg)487 Debugger::Debugger(JSContext* cx, NativeObject* dbg)
488     : object(dbg),
489       debuggees(cx->zone()),
490       uncaughtExceptionHook(nullptr),
491       allowUnobservedAsmJS(false),
492       collectCoverageInfo(false),
493       observedGCs(cx->zone()),
494       allocationsLog(cx),
495       trackingAllocationSites(false),
496       allocationSamplingProbability(1.0),
497       maxAllocationsLogLength(DEFAULT_MAX_LOG_LENGTH),
498       allocationsLogOverflowed(false),
499       frames(cx->zone()),
500       generatorFrames(cx),
501       scripts(cx),
502       sources(cx),
503       objects(cx),
504       environments(cx),
505       wasmInstanceScripts(cx),
506       wasmInstanceSources(cx),
507 #ifdef NIGHTLY_BUILD
508       traceLoggerLastDrainedSize(0),
509       traceLoggerLastDrainedIteration(0),
510 #endif
511       traceLoggerScriptedCallsLastDrainedSize(0),
512       traceLoggerScriptedCallsLastDrainedIteration(0) {
513   cx->check(dbg);
514 
515 #ifdef JS_TRACE_LOGGING
516   TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
517   if (logger) {
518 #  ifdef NIGHTLY_BUILD
519     logger->getIterationAndSize(&traceLoggerLastDrainedIteration,
520                                 &traceLoggerLastDrainedSize);
521 #  endif
522     logger->getIterationAndSize(&traceLoggerScriptedCallsLastDrainedIteration,
523                                 &traceLoggerScriptedCallsLastDrainedSize);
524   }
525 #endif
526 
527   cx->runtime()->debuggerList().insertBack(this);
528 }
529 
~Debugger()530 Debugger::~Debugger() {
531   MOZ_ASSERT(debuggees.empty());
532   allocationsLog.clear();
533 
534   // Breakpoints should hold us alive, so any breakpoints remaining must be set
535   // in dying JSScripts. We should clean them up, but this never asserts. I'm
536   // not sure why.
537   MOZ_ASSERT(breakpoints.isEmpty());
538 
539   // We don't have to worry about locking here since Debugger is not
540   // background finalized.
541   JSContext* cx = TlsContext.get();
542   if (onNewGlobalObjectWatchersLink.mPrev ||
543       onNewGlobalObjectWatchersLink.mNext ||
544       cx->runtime()->onNewGlobalObjectWatchers().begin() ==
545           JSRuntime::WatchersList::Iterator(this)) {
546     cx->runtime()->onNewGlobalObjectWatchers().remove(this);
547   }
548 }
549 
550 static_assert(unsigned(DebuggerFrame::OWNER_SLOT) ==
551               unsigned(DebuggerScript::OWNER_SLOT));
552 static_assert(unsigned(DebuggerFrame::OWNER_SLOT) ==
553               unsigned(DebuggerSource::OWNER_SLOT));
554 static_assert(unsigned(DebuggerFrame::OWNER_SLOT) ==
555               unsigned(JSSLOT_DEBUGOBJECT_OWNER));
556 static_assert(unsigned(DebuggerFrame::OWNER_SLOT) ==
557               unsigned(DebuggerEnvironment::OWNER_SLOT));
558 
559 #ifdef DEBUG
560 /* static */
isChildJSObject(JSObject * obj)561 bool Debugger::isChildJSObject(JSObject* obj) {
562   return obj->getClass() == &DebuggerFrame::class_ ||
563          obj->getClass() == &DebuggerScript::class_ ||
564          obj->getClass() == &DebuggerSource::class_ ||
565          obj->getClass() == &DebuggerObject::class_ ||
566          obj->getClass() == &DebuggerEnvironment::class_;
567 }
568 #endif
569 
570 /* static */
fromChildJSObject(JSObject * obj)571 Debugger* Debugger::fromChildJSObject(JSObject* obj) {
572   MOZ_ASSERT(isChildJSObject(obj));
573   JSObject* dbgobj = &obj->as<NativeObject>()
574                           .getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER)
575                           .toObject();
576   return fromJSObject(dbgobj);
577 }
578 
hasMemory() const579 bool Debugger::hasMemory() const {
580   return object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE).isObject();
581 }
582 
memory() const583 DebuggerMemory& Debugger::memory() const {
584   MOZ_ASSERT(hasMemory());
585   return object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE)
586       .toObject()
587       .as<DebuggerMemory>();
588 }
589 
590 /*** Debugger accessors *******************************************************/
591 
getFrame(JSContext * cx,const FrameIter & iter,MutableHandleValue vp)592 bool Debugger::getFrame(JSContext* cx, const FrameIter& iter,
593                         MutableHandleValue vp) {
594   RootedDebuggerFrame result(cx);
595   if (!Debugger::getFrame(cx, iter, &result)) {
596     return false;
597   }
598   vp.setObject(*result);
599   return true;
600 }
601 
getFrame(JSContext * cx,MutableHandleDebuggerFrame result)602 bool Debugger::getFrame(JSContext* cx, MutableHandleDebuggerFrame result) {
603   RootedObject proto(
604       cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
605   RootedNativeObject debugger(cx, object);
606 
607   // Since there is no frame/generator data to associate with this frame, this
608   // will create a new, "terminated" Debugger.Frame object.
609   RootedDebuggerFrame frame(
610       cx, DebuggerFrame::create(cx, proto, debugger, nullptr, nullptr));
611   if (!frame) {
612     return false;
613   }
614 
615   result.set(frame);
616   return true;
617 }
618 
getFrame(JSContext * cx,const FrameIter & iter,MutableHandleDebuggerFrame result)619 bool Debugger::getFrame(JSContext* cx, const FrameIter& iter,
620                         MutableHandleDebuggerFrame result) {
621   AbstractFramePtr referent = iter.abstractFramePtr();
622   MOZ_ASSERT_IF(referent.hasScript(), !referent.script()->selfHosted());
623 
624   if (referent.hasScript() &&
625       !referent.script()->ensureHasAnalyzedArgsUsage(cx)) {
626     return false;
627   }
628 
629   FrameMap::AddPtr p = frames.lookupForAdd(referent);
630   if (!p) {
631     RootedDebuggerFrame frame(cx);
632 
633     // If this is a generator frame, there may be an existing
634     // Debugger.Frame object that isn't in `frames` because the generator
635     // was suspended, popping the stack frame, and later resumed (and we
636     // were not stepping, so did not pass through slowPathOnResumeFrame).
637     Rooted<AbstractGeneratorObject*> genObj(cx);
638     if (referent.isGeneratorFrame()) {
639       {
640         AutoRealm ar(cx, referent.callee());
641         genObj = GetGeneratorObjectForFrame(cx, referent);
642       }
643       if (genObj) {
644         GeneratorWeakMap::Ptr gp = generatorFrames.lookup(genObj);
645         if (gp) {
646           frame = gp->value();
647           MOZ_ASSERT(&frame->unwrappedGenerator() == genObj);
648 
649           // We have found an existing Debugger.Frame object. But
650           // since it was previously popped (see comment above), it
651           // is not currently "live". We must revive it.
652           if (!frame->resume(iter)) {
653             return false;
654           }
655           if (!ensureExecutionObservabilityOfFrame(cx, referent)) {
656             return false;
657           }
658         }
659       }
660 
661       // If no AbstractGeneratorObject exists yet, we create a Debugger.Frame
662       // below anyway, and Debugger::onNewGenerator() will associate it
663       // with the AbstractGeneratorObject later when we hit JSOp::Generator.
664     }
665 
666     if (!frame) {
667       // Create and populate the Debugger.Frame object.
668       RootedObject proto(
669           cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
670       RootedNativeObject debugger(cx, object);
671 
672       frame = DebuggerFrame::create(cx, proto, debugger, &iter, genObj);
673       if (!frame) {
674         return false;
675       }
676 
677       if (!ensureExecutionObservabilityOfFrame(cx, referent)) {
678         return false;
679       }
680     }
681 
682     if (!frames.add(p, referent, frame)) {
683       frame->freeFrameIterData(cx->defaultFreeOp());
684       frame->clearGenerator(cx->runtime()->defaultFreeOp(), this);
685       ReportOutOfMemory(cx);
686       return false;
687     }
688   }
689 
690   result.set(p->value());
691   return true;
692 }
693 
getFrame(JSContext * cx,Handle<AbstractGeneratorObject * > genObj,MutableHandleDebuggerFrame result)694 bool Debugger::getFrame(JSContext* cx, Handle<AbstractGeneratorObject*> genObj,
695                         MutableHandleDebuggerFrame result) {
696   // To create a Debugger.Frame for a running generator, we'd also need a
697   // FrameIter for its stack frame. We could make this work by searching the
698   // stack for the generator's frame, but for the moment, we only need this
699   // function to handle generators we've found on promises' reaction records,
700   // which should always be suspended.
701   MOZ_ASSERT(!genObj->isRunning());
702 
703   // Do we have an existing Debugger.Frame for this generator?
704   GeneratorWeakMap::Ptr gp = generatorFrames.lookup(genObj);
705   if (gp) {
706     MOZ_ASSERT(&gp->value()->unwrappedGenerator() == genObj);
707     result.set(gp->value());
708     return true;
709   }
710 
711   // Create a new Debugger.Frame.
712   RootedObject proto(
713       cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
714   RootedNativeObject debugger(cx, object);
715 
716   result.set(DebuggerFrame::create(cx, proto, debugger, nullptr, genObj));
717   if (!result) {
718     return false;
719   }
720 
721   return true;
722 }
723 
DebuggerExists(GlobalObject * global,const std::function<bool (Debugger * dbg)> & predicate)724 static bool DebuggerExists(
725     GlobalObject* global, const std::function<bool(Debugger* dbg)>& predicate) {
726   // The GC analysis can't determine that the predicate can't GC, so let it know
727   // explicitly.
728   JS::AutoSuppressGCAnalysis nogc;
729 
730   for (Realm::DebuggerVectorEntry& entry : global->getDebuggers()) {
731     // Callbacks should not create new references to the debugger, so don't
732     // use a barrier. This allows this method to be called during GC.
733     if (predicate(entry.dbg.unbarrieredGet())) {
734       return true;
735     }
736   }
737   return false;
738 }
739 
740 /* static */
hasLiveHook(GlobalObject * global,Hook which)741 bool Debugger::hasLiveHook(GlobalObject* global, Hook which) {
742   return DebuggerExists(global,
743                         [=](Debugger* dbg) { return dbg->getHook(which); });
744 }
745 
746 /* static */
debuggerObservesAllExecution(GlobalObject * global)747 bool DebugAPI::debuggerObservesAllExecution(GlobalObject* global) {
748   return DebuggerExists(
749       global, [=](Debugger* dbg) { return dbg->observesAllExecution(); });
750 }
751 
752 /* static */
debuggerObservesCoverage(GlobalObject * global)753 bool DebugAPI::debuggerObservesCoverage(GlobalObject* global) {
754   return DebuggerExists(global,
755                         [=](Debugger* dbg) { return dbg->observesCoverage(); });
756 }
757 
758 /* static */
debuggerObservesAsmJS(GlobalObject * global)759 bool DebugAPI::debuggerObservesAsmJS(GlobalObject* global) {
760   return DebuggerExists(global,
761                         [=](Debugger* dbg) { return dbg->observesAsmJS(); });
762 }
763 
764 /* static */
hasExceptionUnwindHook(GlobalObject * global)765 bool DebugAPI::hasExceptionUnwindHook(GlobalObject* global) {
766   return Debugger::hasLiveHook(global, Debugger::OnExceptionUnwind);
767 }
768 
769 /* static */
hasDebuggerStatementHook(GlobalObject * global)770 bool DebugAPI::hasDebuggerStatementHook(GlobalObject* global) {
771   return Debugger::hasLiveHook(global, Debugger::OnDebuggerStatement);
772 }
773 
774 template <typename HookIsEnabledFun /* bool (Debugger*) */>
init(JSContext * cx)775 bool DebuggerList<HookIsEnabledFun>::init(JSContext* cx) {
776   // Determine which debuggers will receive this event, and in what order.
777   // Make a copy of the list, since the original is mutable and we will be
778   // calling into arbitrary JS.
779   Handle<GlobalObject*> global = cx->global();
780   for (Realm::DebuggerVectorEntry& entry : global->getDebuggers()) {
781     Debugger* dbg = entry.dbg;
782     if (dbg->isHookCallAllowed(cx) && hookIsEnabled(dbg)) {
783       if (!debuggers.append(ObjectValue(*dbg->toJSObject()))) {
784         return false;
785       }
786     }
787   }
788   return true;
789 }
790 
791 template <typename HookIsEnabledFun /* bool (Debugger*) */>
792 template <typename FireHookFun /* bool (Debugger*) */>
dispatchHook(JSContext * cx,FireHookFun fireHook)793 bool DebuggerList<HookIsEnabledFun>::dispatchHook(JSContext* cx,
794                                                   FireHookFun fireHook) {
795   // Preserve the debuggee's microtask event queue while we run the hooks, so
796   // the debugger's microtask checkpoints don't run from the debuggee's
797   // microtasks, and vice versa.
798   JS::AutoDebuggerJobQueueInterruption adjqi;
799   if (!adjqi.init(cx)) {
800     return false;
801   }
802 
803   // Deliver the event to each debugger, checking again to make sure it
804   // should still be delivered.
805   Handle<GlobalObject*> global = cx->global();
806   for (Value* p = debuggers.begin(); p != debuggers.end(); p++) {
807     Debugger* dbg = Debugger::fromJSObject(&p->toObject());
808     EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);
809     if (dbg->debuggees.has(global) && hookIsEnabled(dbg)) {
810       bool result =
811           dbg->enterDebuggerHook(cx, [&]() -> bool { return fireHook(dbg); });
812       adjqi.runJobs();
813       if (!result) {
814         return false;
815       }
816     }
817   }
818   return true;
819 }
820 
821 template <typename HookIsEnabledFun /* bool (Debugger*) */>
822 template <typename FireHookFun /* bool (Debugger*) */>
dispatchQuietHook(JSContext * cx,FireHookFun fireHook)823 void DebuggerList<HookIsEnabledFun>::dispatchQuietHook(JSContext* cx,
824                                                        FireHookFun fireHook) {
825   bool result =
826       dispatchHook(cx, [&](Debugger* dbg) -> bool { return fireHook(dbg); });
827 
828   // dispatchHook may fail due to OOM. This OOM is not handlable at the
829   // callsites of dispatchQuietHook in the engine.
830   if (!result) {
831     cx->clearPendingException();
832   }
833 }
834 
835 template <typename HookIsEnabledFun /* bool (Debugger*) */>
836 template <typename FireHookFun /* bool (Debugger*, ResumeMode&, MutableHandleValue vp) */>
dispatchResumptionHook(JSContext * cx,AbstractFramePtr frame,FireHookFun fireHook)837 bool DebuggerList<HookIsEnabledFun>::dispatchResumptionHook(
838     JSContext* cx, AbstractFramePtr frame, FireHookFun fireHook) {
839   ResumeMode resumeMode = ResumeMode::Continue;
840   RootedValue rval(cx);
841   return dispatchHook(cx,
842                       [&](Debugger* dbg) -> bool {
843                         return fireHook(dbg, resumeMode, &rval);
844                       }) &&
845          ApplyFrameResumeMode(cx, frame, resumeMode, rval);
846 }
847 
getHook(Hook hook) const848 JSObject* Debugger::getHook(Hook hook) const {
849   MOZ_ASSERT(hook >= 0 && hook < HookCount);
850   const Value& v = object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + hook);
851   return v.isUndefined() ? nullptr : &v.toObject();
852 }
853 
hasAnyLiveHooks() const854 bool Debugger::hasAnyLiveHooks() const {
855   // A onNewGlobalObject hook does not hold its Debugger live, so its behavior
856   // is nondeterministic. This behavior is not satisfying, but it is at least
857   // documented.
858   if (getHook(OnDebuggerStatement) || getHook(OnExceptionUnwind) ||
859       getHook(OnNewScript) || getHook(OnEnterFrame)) {
860     return true;
861   }
862 
863   return false;
864 }
865 
866 /* static */
slowPathOnEnterFrame(JSContext * cx,AbstractFramePtr frame)867 bool DebugAPI::slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame) {
868   return Debugger::dispatchResumptionHook(
869       cx, frame,
870       [frame](Debugger* dbg) -> bool {
871         return dbg->observesFrame(frame) && dbg->observesEnterFrame();
872       },
873       [&](Debugger* dbg, ResumeMode& resumeMode, MutableHandleValue vp)
874           -> bool { return dbg->fireEnterFrame(cx, resumeMode, vp); });
875 }
876 
877 /* static */
slowPathOnResumeFrame(JSContext * cx,AbstractFramePtr frame)878 bool DebugAPI::slowPathOnResumeFrame(JSContext* cx, AbstractFramePtr frame) {
879   // Don't count on this method to be called every time a generator is
880   // resumed! This is called only if the frame's debuggee bit is set,
881   // i.e. the script has breakpoints or the frame is stepping.
882   MOZ_ASSERT(frame.isGeneratorFrame());
883   MOZ_ASSERT(frame.isDebuggee());
884 
885   Rooted<AbstractGeneratorObject*> genObj(
886       cx, GetGeneratorObjectForFrame(cx, frame));
887   MOZ_ASSERT(genObj);
888 
889   // For each debugger, if there is an existing Debugger.Frame object for the
890   // resumed `frame`, update it with the new frame pointer and make sure the
891   // frame is observable.
892   for (Realm::DebuggerVectorEntry& entry : frame.global()->getDebuggers()) {
893     Debugger* dbg = entry.dbg;
894     if (Debugger::GeneratorWeakMap::Ptr generatorEntry =
895             dbg->generatorFrames.lookup(genObj)) {
896       DebuggerFrame* frameObj = generatorEntry->value();
897       MOZ_ASSERT(&frameObj->unwrappedGenerator() == genObj);
898       if (!dbg->frames.putNew(frame, frameObj)) {
899         ReportOutOfMemory(cx);
900         return false;
901       }
902 
903       FrameIter iter(cx);
904       MOZ_ASSERT(iter.abstractFramePtr() == frame);
905       if (!frameObj->resume(iter)) {
906         return false;
907       }
908       if (!Debugger::ensureExecutionObservabilityOfFrame(cx, frame)) {
909         return false;
910       }
911     }
912   }
913 
914   return slowPathOnEnterFrame(cx, frame);
915 }
916 
917 /* static */
slowPathOnNativeCall(JSContext * cx,const CallArgs & args,CallReason reason)918 NativeResumeMode DebugAPI::slowPathOnNativeCall(JSContext* cx,
919                                                 const CallArgs& args,
920                                                 CallReason reason) {
921   // "onNativeCall" only works consistently in the context of an explicit eval
922   // that has set the "insideDebuggerEvaluationWithOnNativeCallHook" state
923   // on the JSContext, so we fast-path this hook to bail right away if that is
924   // not currently set. If this flag is set to a _different_ debugger, the
925   // standard "isHookCallAllowed" debugger logic will apply and only hooks on
926   // that debugger will be callable.
927   if (!cx->insideDebuggerEvaluationWithOnNativeCallHook) {
928     return NativeResumeMode::Continue;
929   }
930 
931   DebuggerList debuggerList(cx, [](Debugger* dbg) -> bool {
932     return dbg->getHook(Debugger::OnNativeCall);
933   });
934 
935   if (!debuggerList.init(cx)) {
936     return NativeResumeMode::Abort;
937   }
938 
939   if (debuggerList.empty()) {
940     return NativeResumeMode::Continue;
941   }
942 
943   // The onNativeCall hook is fired when self hosted functions are called,
944   // and any other self hosted function or C++ native that is directly called
945   // by the self hosted function is considered to be part of the same
946   // native call.
947   //
948   // We check this only after checking that debuggerList has items in order
949   // to avoid unnecessary calls to cx->currentScript(), which can be expensive
950   // when the top frame is in jitcode.
951   JSScript* script = cx->currentScript();
952   if (script && script->selfHosted()) {
953     return NativeResumeMode::Continue;
954   }
955 
956   RootedValue rval(cx);
957   ResumeMode resumeMode = ResumeMode::Continue;
958   bool result = debuggerList.dispatchHook(cx, [&](Debugger* dbg) -> bool {
959     return dbg->fireNativeCall(cx, args, reason, resumeMode, &rval);
960   });
961   if (!result) {
962     return NativeResumeMode::Abort;
963   }
964 
965   // The value is not in any particular compartment, so it needs to be
966   // explicitly wrapped into the debuggee compartment.
967   if (!cx->compartment()->wrap(cx, &rval)) {
968     return NativeResumeMode::Abort;
969   }
970 
971   switch (resumeMode) {
972     case ResumeMode::Continue:
973       break;
974 
975     case ResumeMode::Throw:
976       cx->setPendingExceptionAndCaptureStack(rval);
977       return NativeResumeMode::Abort;
978 
979     case ResumeMode::Terminate:
980       cx->clearPendingException();
981       return NativeResumeMode::Abort;
982 
983     case ResumeMode::Return:
984       args.rval().set(rval);
985       return NativeResumeMode::Override;
986   }
987 
988   return NativeResumeMode::Continue;
989 }
990 
991 /*
992  * RAII class to mark a generator as "running" temporarily while running
993  * debugger code.
994  *
995  * When Debugger::slowPathOnLeaveFrame is called for a frame that is yielding
996  * or awaiting, its generator is in the "suspended" state. Letting script
997  * observe this state, with the generator on stack yet also reenterable, would
998  * be bad, so we mark it running while we fire events.
999  */
1000 class MOZ_RAII AutoSetGeneratorRunning {
1001   int32_t resumeIndex_;
1002   AsyncGeneratorObject::State asyncGenState_;
1003   Rooted<AbstractGeneratorObject*> genObj_;
1004 
1005  public:
AutoSetGeneratorRunning(JSContext * cx,Handle<AbstractGeneratorObject * > genObj)1006   AutoSetGeneratorRunning(JSContext* cx,
1007                           Handle<AbstractGeneratorObject*> genObj)
1008       : resumeIndex_(0),
1009         asyncGenState_(static_cast<AsyncGeneratorObject::State>(0)),
1010         genObj_(cx, genObj) {
1011     if (genObj) {
1012       if (!genObj->isClosed() && !genObj->isBeforeInitialYield() &&
1013           genObj->isSuspended()) {
1014         // Yielding or awaiting.
1015         resumeIndex_ = genObj->resumeIndex();
1016         genObj->setRunning();
1017 
1018         // Async generators have additionally bookkeeping which must be
1019         // adjusted when switching over to the running state.
1020         if (genObj->is<AsyncGeneratorObject>()) {
1021           auto* asyncGenObj = &genObj->as<AsyncGeneratorObject>();
1022           asyncGenState_ = asyncGenObj->state();
1023           asyncGenObj->setExecuting();
1024         }
1025       } else {
1026         // Returning or throwing. The generator is already closed, if
1027         // it was ever exposed at all.
1028         genObj_ = nullptr;
1029       }
1030     }
1031   }
1032 
~AutoSetGeneratorRunning()1033   ~AutoSetGeneratorRunning() {
1034     if (genObj_) {
1035       MOZ_ASSERT(genObj_->isRunning());
1036       genObj_->setResumeIndex(resumeIndex_);
1037       if (genObj_->is<AsyncGeneratorObject>()) {
1038         genObj_->as<AsyncGeneratorObject>().setState(asyncGenState_);
1039       }
1040     }
1041   }
1042 };
1043 
1044 /*
1045  * Handle leaving a frame with debuggers watching. |frameOk| indicates whether
1046  * the frame is exiting normally or abruptly. Set |cx|'s exception and/or
1047  * |cx->fp()|'s return value, and return a new success value.
1048  */
1049 /* static */
slowPathOnLeaveFrame(JSContext * cx,AbstractFramePtr frame,jsbytecode * pc,bool frameOk)1050 bool DebugAPI::slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame,
1051                                     jsbytecode* pc, bool frameOk) {
1052   MOZ_ASSERT_IF(!frame.isWasmDebugFrame(), pc);
1053 
1054   mozilla::DebugOnly<Handle<GlobalObject*>> debuggeeGlobal = cx->global();
1055 
1056   // These are updated below, but consulted by the cleanup code we register now,
1057   // so declare them here, initialized to quiescent values.
1058   Rooted<Completion> completion(cx);
1059   bool success = false;
1060 
1061   auto frameMapsGuard = MakeScopeExit([&] {
1062     // Clean up all Debugger.Frame instances on exit. On suspending, pass the
1063     // flag that says to leave those frames `.live`. Note that if the completion
1064     // is a suspension but success is false, the generator gets closed, not
1065     // suspended.
1066     Debugger::removeFromFrameMapsAndClearBreakpointsIn(
1067         cx, frame, success && completion.get().suspending());
1068   });
1069 
1070   // The onPop handler and associated clean up logic should not run multiple
1071   // times on the same frame. If slowPathOnLeaveFrame has already been
1072   // called, the frame will not be present in the Debugger frame maps.
1073   Rooted<Debugger::DebuggerFrameVector> frames(
1074       cx, Debugger::DebuggerFrameVector(cx));
1075   if (!Debugger::getDebuggerFrames(frame, &frames)) {
1076     return false;
1077   }
1078   if (frames.empty()) {
1079     return frameOk;
1080   }
1081 
1082   completion = Completion::fromJSFramePop(cx, frame, pc, frameOk);
1083 
1084   ResumeMode resumeMode = ResumeMode::Continue;
1085   RootedValue rval(cx);
1086 
1087   {
1088     // Preserve the debuggee's microtask event queue while we run the hooks, so
1089     // the debugger's microtask checkpoints don't run from the debuggee's
1090     // microtasks, and vice versa.
1091     JS::AutoDebuggerJobQueueInterruption adjqi;
1092     if (!adjqi.init(cx)) {
1093       return false;
1094     }
1095 
1096     // This path can be hit via unwinding the stack due to over-recursion or
1097     // OOM. In those cases, don't fire the frames' onPop handlers, because
1098     // invoking JS will only trigger the same condition. See
1099     // slowPathOnExceptionUnwind.
1100     if (!cx->isThrowingOverRecursed() && !cx->isThrowingOutOfMemory()) {
1101       Rooted<AbstractGeneratorObject*> genObj(
1102           cx, frame.isGeneratorFrame() ? GetGeneratorObjectForFrame(cx, frame)
1103                                        : nullptr);
1104 
1105       // For each Debugger.Frame, fire its onPop handler, if any.
1106       for (size_t i = 0; i < frames.length(); i++) {
1107         HandleDebuggerFrame frameobj = frames[i];
1108         Debugger* dbg = Debugger::fromChildJSObject(frameobj);
1109         EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);
1110 
1111         // Removing a global from a Debugger's debuggee set kills all of that
1112         // Debugger's D.Fs in that global. This means that one D.F's onPop can
1113         // kill the next D.F. So we have to check whether frameobj is still "on
1114         // the stack".
1115         if (frameobj->isOnStack() && frameobj->onPopHandler()) {
1116           OnPopHandler* handler = frameobj->onPopHandler();
1117 
1118           bool result = dbg->enterDebuggerHook(cx, [&]() -> bool {
1119             ResumeMode nextResumeMode = ResumeMode::Continue;
1120             RootedValue nextValue(cx);
1121 
1122             // Call the onPop handler.
1123             bool success;
1124             {
1125               // Mark the generator as running, to prevent reentrance.
1126               //
1127               // At certain points in a generator's lifetime,
1128               // GetGeneratorObjectForFrame can return null even when the
1129               // generator exists, but at those points the generator has not yet
1130               // been exposed to JavaScript, so reentrance isn't possible
1131               // anyway. So there's no harm done if this has no effect in that
1132               // case.
1133               AutoSetGeneratorRunning asgr(cx, genObj);
1134               success = handler->onPop(cx, frameobj, completion, nextResumeMode,
1135                                        &nextValue);
1136             }
1137 
1138             return dbg->processParsedHandlerResult(cx, frame, pc, success,
1139                                                    nextResumeMode, nextValue,
1140                                                    resumeMode, &rval);
1141           });
1142           adjqi.runJobs();
1143 
1144           if (!result) {
1145             return false;
1146           }
1147 
1148           // At this point, we are back in the debuggee compartment, and
1149           // any error has been wrapped up as a completion value.
1150           MOZ_ASSERT(!cx->isExceptionPending());
1151         }
1152       }
1153     }
1154   }
1155 
1156   completion.get().updateFromHookResult(resumeMode, rval);
1157 
1158   // Now that we've run all the handlers, extract the final resumption mode. */
1159   ResumeMode completionResumeMode;
1160   RootedValue completionValue(cx);
1161   RootedSavedFrame completionStack(cx);
1162   completion.get().toResumeMode(completionResumeMode, &completionValue,
1163                                 &completionStack);
1164 
1165   // If we are returning the original value used to create the completion, then
1166   // we don't want to treat the resumption value as a Return completion, because
1167   // that would cause us to apply AdjustGeneratorResumptionValue to the
1168   // already-adjusted value that the generator actually returned.
1169   if (resumeMode == ResumeMode::Continue &&
1170       completionResumeMode == ResumeMode::Return) {
1171     completionResumeMode = ResumeMode::Continue;
1172   }
1173 
1174   if (!ApplyFrameResumeMode(cx, frame, completionResumeMode, completionValue,
1175                             completionStack)) {
1176     if (!cx->isPropagatingForcedReturn()) {
1177       // If this is an exception or termination, we just propagate that along.
1178       return false;
1179     }
1180 
1181     // Since we are leaving the frame here, we can convert a forced return
1182     // into a normal return right away.
1183     cx->clearPropagatingForcedReturn();
1184   }
1185   success = true;
1186   return true;
1187 }
1188 
1189 /* static */
slowPathOnNewGenerator(JSContext * cx,AbstractFramePtr frame,Handle<AbstractGeneratorObject * > genObj)1190 bool DebugAPI::slowPathOnNewGenerator(JSContext* cx, AbstractFramePtr frame,
1191                                       Handle<AbstractGeneratorObject*> genObj) {
1192   // This is called from JSOp::Generator, after default parameter expressions
1193   // are evaluated and well after onEnterFrame, so Debugger.Frame objects for
1194   // `frame` may already have been exposed to debugger code. The
1195   // AbstractGeneratorObject for this generator call, though, has just been
1196   // created. It must be associated with any existing Debugger.Frames.
1197   bool ok = true;
1198   Debugger::forEachDebuggerFrame(frame, [&](DebuggerFrame* frameObjPtr) {
1199     if (!ok) {
1200       return;
1201     }
1202 
1203     RootedDebuggerFrame frameObj(cx, frameObjPtr);
1204     {
1205       AutoRealm ar(cx, frameObj);
1206 
1207       if (!frameObj->setGenerator(cx, genObj)) {
1208         ReportOutOfMemory(cx);
1209 
1210         // This leaves `genObj` and `frameObj` unassociated. It's OK
1211         // because we won't pause again with this generator on the stack:
1212         // the caller will immediately discard `genObj` and unwind `frame`.
1213         ok = false;
1214       }
1215     }
1216   });
1217   return ok;
1218 }
1219 
1220 /* static */
slowPathOnDebuggerStatement(JSContext * cx,AbstractFramePtr frame)1221 bool DebugAPI::slowPathOnDebuggerStatement(JSContext* cx,
1222                                            AbstractFramePtr frame) {
1223   return Debugger::dispatchResumptionHook(
1224       cx, frame,
1225       [](Debugger* dbg) -> bool {
1226         return dbg->getHook(Debugger::OnDebuggerStatement);
1227       },
1228       [&](Debugger* dbg, ResumeMode& resumeMode, MutableHandleValue vp)
1229           -> bool { return dbg->fireDebuggerStatement(cx, resumeMode, vp); });
1230 }
1231 
1232 /* static */
slowPathOnExceptionUnwind(JSContext * cx,AbstractFramePtr frame)1233 bool DebugAPI::slowPathOnExceptionUnwind(JSContext* cx,
1234                                          AbstractFramePtr frame) {
1235   // Invoking more JS on an over-recursed stack or after OOM is only going
1236   // to result in more of the same error.
1237   if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory()) {
1238     return true;
1239   }
1240 
1241   // The Debugger API mustn't muck with frames from self-hosted scripts.
1242   if (frame.hasScript() && frame.script()->selfHosted()) {
1243     return true;
1244   }
1245 
1246   DebuggerList debuggerList(cx, [](Debugger* dbg) -> bool {
1247     return dbg->getHook(Debugger::OnExceptionUnwind);
1248   });
1249 
1250   if (!debuggerList.init(cx)) {
1251     return false;
1252   }
1253 
1254   if (debuggerList.empty()) {
1255     return true;
1256   }
1257 
1258   // We save and restore the exception once up front to avoid having to do it
1259   // for each 'onExceptionUnwind' hook that has been registered, and we also
1260   // only do it if the debuggerList contains items in order to avoid extra work.
1261   RootedValue exc(cx);
1262   RootedSavedFrame stack(cx, cx->getPendingExceptionStack());
1263   if (!cx->getPendingException(&exc)) {
1264     return false;
1265   }
1266   cx->clearPendingException();
1267 
1268   bool result = debuggerList.dispatchResumptionHook(
1269       cx, frame,
1270       [&](Debugger* dbg, ResumeMode& resumeMode,
1271           MutableHandleValue vp) -> bool {
1272         return dbg->fireExceptionUnwind(cx, exc, resumeMode, vp);
1273       });
1274   if (!result) {
1275     return false;
1276   }
1277 
1278   cx->setPendingException(exc, stack);
1279   return true;
1280 }
1281 
1282 // TODO: Remove Remove this function when all properties/methods returning a
1283 ///      DebuggerEnvironment have been given a C++ interface (bug 1271649).
wrapEnvironment(JSContext * cx,Handle<Env * > env,MutableHandleValue rval)1284 bool Debugger::wrapEnvironment(JSContext* cx, Handle<Env*> env,
1285                                MutableHandleValue rval) {
1286   if (!env) {
1287     rval.setNull();
1288     return true;
1289   }
1290 
1291   RootedDebuggerEnvironment envobj(cx);
1292 
1293   if (!wrapEnvironment(cx, env, &envobj)) {
1294     return false;
1295   }
1296 
1297   rval.setObject(*envobj);
1298   return true;
1299 }
1300 
wrapEnvironment(JSContext * cx,Handle<Env * > env,MutableHandleDebuggerEnvironment result)1301 bool Debugger::wrapEnvironment(JSContext* cx, Handle<Env*> env,
1302                                MutableHandleDebuggerEnvironment result) {
1303   MOZ_ASSERT(env);
1304 
1305   // DebuggerEnv should only wrap a debug scope chain obtained (transitively)
1306   // from GetDebugEnvironmentFor(Frame|Function).
1307   MOZ_ASSERT(!IsSyntacticEnvironment(env));
1308 
1309   DependentAddPtr<EnvironmentWeakMap> p(cx, environments, env);
1310   if (p) {
1311     result.set(&p->value()->as<DebuggerEnvironment>());
1312   } else {
1313     // Create a new Debugger.Environment for env.
1314     RootedObject proto(
1315         cx, &object->getReservedSlot(JSSLOT_DEBUG_ENV_PROTO).toObject());
1316     RootedNativeObject debugger(cx, object);
1317 
1318     RootedDebuggerEnvironment envobj(
1319         cx, DebuggerEnvironment::create(cx, proto, env, debugger));
1320     if (!envobj) {
1321       return false;
1322     }
1323 
1324     if (!p.add(cx, environments, env, envobj)) {
1325       NukeDebuggerWrapper(envobj);
1326       return false;
1327     }
1328 
1329     result.set(envobj);
1330   }
1331 
1332   return true;
1333 }
1334 
wrapDebuggeeValue(JSContext * cx,MutableHandleValue vp)1335 bool Debugger::wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp) {
1336   cx->check(object.get());
1337 
1338   if (vp.isObject()) {
1339     RootedObject obj(cx, &vp.toObject());
1340     RootedDebuggerObject dobj(cx);
1341 
1342     if (!wrapDebuggeeObject(cx, obj, &dobj)) {
1343       return false;
1344     }
1345 
1346     vp.setObject(*dobj);
1347   } else if (vp.isMagic()) {
1348     RootedPlainObject optObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
1349     if (!optObj) {
1350       return false;
1351     }
1352 
1353     // We handle three sentinel values: missing arguments (overloading
1354     // JS_OPTIMIZED_ARGUMENTS), optimized out slots (JS_OPTIMIZED_OUT),
1355     // and uninitialized bindings (JS_UNINITIALIZED_LEXICAL).
1356     //
1357     // Other magic values should not have escaped.
1358     PropertyName* name;
1359     switch (vp.whyMagic()) {
1360       case JS_OPTIMIZED_ARGUMENTS:
1361         name = cx->names().missingArguments;
1362         break;
1363       case JS_OPTIMIZED_OUT:
1364         name = cx->names().optimizedOut;
1365         break;
1366       case JS_UNINITIALIZED_LEXICAL:
1367         name = cx->names().uninitialized;
1368         break;
1369       default:
1370         MOZ_CRASH("Unsupported magic value escaped to Debugger");
1371     }
1372 
1373     RootedValue trueVal(cx, BooleanValue(true));
1374     if (!DefineDataProperty(cx, optObj, name, trueVal)) {
1375       return false;
1376     }
1377 
1378     vp.setObject(*optObj);
1379   } else if (!cx->compartment()->wrap(cx, vp)) {
1380     vp.setUndefined();
1381     return false;
1382   }
1383 
1384   return true;
1385 }
1386 
wrapNullableDebuggeeObject(JSContext * cx,HandleObject obj,MutableHandleDebuggerObject result)1387 bool Debugger::wrapNullableDebuggeeObject(JSContext* cx, HandleObject obj,
1388                                           MutableHandleDebuggerObject result) {
1389   if (!obj) {
1390     result.set(nullptr);
1391     return true;
1392   }
1393 
1394   return wrapDebuggeeObject(cx, obj, result);
1395 }
1396 
wrapDebuggeeObject(JSContext * cx,HandleObject obj,MutableHandleDebuggerObject result)1397 bool Debugger::wrapDebuggeeObject(JSContext* cx, HandleObject obj,
1398                                   MutableHandleDebuggerObject result) {
1399   MOZ_ASSERT(obj);
1400 
1401   DependentAddPtr<ObjectWeakMap> p(cx, objects, obj);
1402   if (p) {
1403     result.set(&p->value()->as<DebuggerObject>());
1404   } else {
1405     // Create a new Debugger.Object for obj.
1406     RootedNativeObject debugger(cx, object);
1407     RootedObject proto(
1408         cx, &object->getReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO).toObject());
1409     RootedDebuggerObject dobj(cx,
1410                               DebuggerObject::create(cx, proto, obj, debugger));
1411     if (!dobj) {
1412       return false;
1413     }
1414 
1415     if (!p.add(cx, objects, obj, dobj)) {
1416       NukeDebuggerWrapper(dobj);
1417       return false;
1418     }
1419 
1420     result.set(dobj);
1421   }
1422 
1423   return true;
1424 }
1425 
ToNativeDebuggerObject(JSContext * cx,MutableHandleObject obj)1426 static NativeObject* ToNativeDebuggerObject(JSContext* cx,
1427                                             MutableHandleObject obj) {
1428   if (obj->getClass() != &DebuggerObject::class_) {
1429     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1430                               JSMSG_NOT_EXPECTED_TYPE, "Debugger",
1431                               "Debugger.Object", obj->getClass()->name);
1432     return nullptr;
1433   }
1434 
1435   NativeObject* ndobj = &obj->as<NativeObject>();
1436 
1437   Value owner = ndobj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER);
1438   if (owner.isUndefined()) {
1439     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_PROTO,
1440                               "Debugger.Object", "Debugger.Object");
1441     return nullptr;
1442   }
1443 
1444   return ndobj;
1445 }
1446 
unwrapDebuggeeObject(JSContext * cx,MutableHandleObject obj)1447 bool Debugger::unwrapDebuggeeObject(JSContext* cx, MutableHandleObject obj) {
1448   NativeObject* ndobj = ToNativeDebuggerObject(cx, obj);
1449   if (!ndobj) {
1450     return false;
1451   }
1452 
1453   Value owner = ndobj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER);
1454   if (&owner.toObject() != object) {
1455     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1456                               JSMSG_DEBUG_WRONG_OWNER, "Debugger.Object");
1457     return false;
1458   }
1459 
1460   obj.set(static_cast<JSObject*>(ndobj->getPrivate()));
1461   return true;
1462 }
1463 
unwrapDebuggeeValue(JSContext * cx,MutableHandleValue vp)1464 bool Debugger::unwrapDebuggeeValue(JSContext* cx, MutableHandleValue vp) {
1465   cx->check(object.get(), vp);
1466   if (vp.isObject()) {
1467     RootedObject dobj(cx, &vp.toObject());
1468     if (!unwrapDebuggeeObject(cx, &dobj)) {
1469       return false;
1470     }
1471     vp.setObject(*dobj);
1472   }
1473   return true;
1474 }
1475 
CheckArgCompartment(JSContext * cx,JSObject * obj,JSObject * arg,const char * methodname,const char * propname)1476 static bool CheckArgCompartment(JSContext* cx, JSObject* obj, JSObject* arg,
1477                                 const char* methodname, const char* propname) {
1478   if (arg->compartment() != obj->compartment()) {
1479     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1480                               JSMSG_DEBUG_COMPARTMENT_MISMATCH, methodname,
1481                               propname);
1482     return false;
1483   }
1484   return true;
1485 }
1486 
CheckArgCompartment(JSContext * cx,JSObject * obj,HandleValue v,const char * methodname,const char * propname)1487 static bool CheckArgCompartment(JSContext* cx, JSObject* obj, HandleValue v,
1488                                 const char* methodname, const char* propname) {
1489   if (v.isObject()) {
1490     return CheckArgCompartment(cx, obj, &v.toObject(), methodname, propname);
1491   }
1492   return true;
1493 }
1494 
unwrapPropertyDescriptor(JSContext * cx,HandleObject obj,MutableHandle<PropertyDescriptor> desc)1495 bool Debugger::unwrapPropertyDescriptor(
1496     JSContext* cx, HandleObject obj, MutableHandle<PropertyDescriptor> desc) {
1497   if (desc.hasValue()) {
1498     RootedValue value(cx, desc.value());
1499     if (!unwrapDebuggeeValue(cx, &value) ||
1500         !CheckArgCompartment(cx, obj, value, "defineProperty", "value")) {
1501       return false;
1502     }
1503     desc.setValue(value);
1504   }
1505 
1506   if (desc.hasGetterObject()) {
1507     RootedObject get(cx, desc.getterObject());
1508     if (get) {
1509       if (!unwrapDebuggeeObject(cx, &get)) {
1510         return false;
1511       }
1512       if (!CheckArgCompartment(cx, obj, get, "defineProperty", "get")) {
1513         return false;
1514       }
1515     }
1516     desc.setGetterObject(get);
1517   }
1518 
1519   if (desc.hasSetterObject()) {
1520     RootedObject set(cx, desc.setterObject());
1521     if (set) {
1522       if (!unwrapDebuggeeObject(cx, &set)) {
1523         return false;
1524       }
1525       if (!CheckArgCompartment(cx, obj, set, "defineProperty", "set")) {
1526         return false;
1527       }
1528     }
1529     desc.setSetterObject(set);
1530   }
1531 
1532   return true;
1533 }
1534 
1535 /*** Debuggee resumption values and debugger error handling *****************/
1536 
GetResumptionProperty(JSContext * cx,HandleObject obj,HandlePropertyName name,ResumeMode namedMode,ResumeMode & resumeMode,MutableHandleValue vp,int * hits)1537 static bool GetResumptionProperty(JSContext* cx, HandleObject obj,
1538                                   HandlePropertyName name, ResumeMode namedMode,
1539                                   ResumeMode& resumeMode, MutableHandleValue vp,
1540                                   int* hits) {
1541   bool found;
1542   if (!HasProperty(cx, obj, name, &found)) {
1543     return false;
1544   }
1545   if (found) {
1546     ++*hits;
1547     resumeMode = namedMode;
1548     if (!GetProperty(cx, obj, obj, name, vp)) {
1549       return false;
1550     }
1551   }
1552   return true;
1553 }
1554 
ParseResumptionValue(JSContext * cx,HandleValue rval,ResumeMode & resumeMode,MutableHandleValue vp)1555 bool js::ParseResumptionValue(JSContext* cx, HandleValue rval,
1556                               ResumeMode& resumeMode, MutableHandleValue vp) {
1557   if (rval.isUndefined()) {
1558     resumeMode = ResumeMode::Continue;
1559     vp.setUndefined();
1560     return true;
1561   }
1562   if (rval.isNull()) {
1563     resumeMode = ResumeMode::Terminate;
1564     vp.setUndefined();
1565     return true;
1566   }
1567 
1568   int hits = 0;
1569   if (rval.isObject()) {
1570     RootedObject obj(cx, &rval.toObject());
1571     if (!GetResumptionProperty(cx, obj, cx->names().return_, ResumeMode::Return,
1572                                resumeMode, vp, &hits)) {
1573       return false;
1574     }
1575     if (!GetResumptionProperty(cx, obj, cx->names().throw_, ResumeMode::Throw,
1576                                resumeMode, vp, &hits)) {
1577       return false;
1578     }
1579   }
1580 
1581   if (hits != 1) {
1582     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1583                               JSMSG_DEBUG_BAD_RESUMPTION);
1584     return false;
1585   }
1586   return true;
1587 }
1588 
CheckResumptionValue(JSContext * cx,AbstractFramePtr frame,jsbytecode * pc,ResumeMode resumeMode,MutableHandleValue vp)1589 static bool CheckResumptionValue(JSContext* cx, AbstractFramePtr frame,
1590                                  jsbytecode* pc, ResumeMode resumeMode,
1591                                  MutableHandleValue vp) {
1592   // Only forced returns from a frame need to be validated because forced
1593   // throw values behave just like debuggee `throw` statements. Since
1594   // forced-return is all custom logic within SpiderMonkey itself, we need
1595   // our own custom validation for it to conform with what is expected.
1596   if (resumeMode != ResumeMode::Return || !frame) {
1597     return true;
1598   }
1599 
1600   // This replicates the ECMA spec's behavior for [[Construct]] in derived
1601   // class constructors (section 9.2.2 of ECMA262-2020), where returning a
1602   // non-undefined primitive causes an exception tobe thrown.
1603   if (frame.debuggerNeedsCheckPrimitiveReturn() && vp.isPrimitive()) {
1604     if (!vp.isUndefined()) {
1605       ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, vp,
1606                        nullptr);
1607       return false;
1608     }
1609 
1610     RootedValue thisv(cx);
1611     {
1612       AutoRealm ar(cx, frame.environmentChain());
1613       if (!GetThisValueForDebuggerFrameMaybeOptimizedOut(cx, frame, pc,
1614                                                          &thisv)) {
1615         return false;
1616       }
1617     }
1618 
1619     if (thisv.isMagic(JS_UNINITIALIZED_LEXICAL)) {
1620       return ThrowUninitializedThis(cx);
1621     }
1622     MOZ_ASSERT(!thisv.isMagic());
1623 
1624     if (!cx->compartment()->wrap(cx, &thisv)) {
1625       return false;
1626     }
1627     vp.set(thisv);
1628   }
1629 
1630   // Check for forcing return from a generator before the initial yield. This
1631   // is not supported because some engine-internal code assumes a call to a
1632   // generator will return a GeneratorObject; see bug 1477084.
1633   if (frame.isFunctionFrame() && frame.callee()->isGenerator()) {
1634     Rooted<AbstractGeneratorObject*> genObj(cx);
1635     {
1636       AutoRealm ar(cx, frame.callee());
1637       genObj = GetGeneratorObjectForFrame(cx, frame);
1638     }
1639 
1640     if (!genObj || genObj->isBeforeInitialYield()) {
1641       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1642                                 JSMSG_DEBUG_FORCED_RETURN_DISALLOWED);
1643       return false;
1644     }
1645   }
1646 
1647   return true;
1648 }
1649 
1650 // Last-minute sanity adjustments to resumption.
1651 //
1652 // This is called last, as we leave the debugger. It must happen outside the
1653 // control of the uncaughtExceptionHook, because this code assumes we won't
1654 // change our minds and continue execution--we must not close the generator
1655 // object unless we're really going to force-return.
AdjustGeneratorResumptionValue(JSContext * cx,AbstractFramePtr frame,ResumeMode & resumeMode,MutableHandleValue vp)1656 static MOZ_MUST_USE bool AdjustGeneratorResumptionValue(JSContext* cx,
1657                                                         AbstractFramePtr frame,
1658                                                         ResumeMode& resumeMode,
1659                                                         MutableHandleValue vp) {
1660   if (resumeMode != ResumeMode::Return && resumeMode != ResumeMode::Throw) {
1661     return true;
1662   }
1663   if (!frame || !frame.isFunctionFrame()) {
1664     return true;
1665   }
1666 
1667   // Treat `{return: <value>}` like a `return` statement. Simulate what the
1668   // debuggee would do for an ordinary `return` statement, using a few bytecode
1669   // instructions. It's simpler to do the work manually than to count on that
1670   // bytecode sequence existing in the debuggee, somehow jump to it, and then
1671   // avoid re-entering the debugger from it.
1672   //
1673   // Similarly treat `{throw: <value>}` like a `throw` statement.
1674   if (frame.callee()->isGenerator()) {
1675     // Throw doesn't require any special processing for (async) generators.
1676     if (resumeMode == ResumeMode::Throw) {
1677       return true;
1678     }
1679 
1680     // Forcing return from a (possibly async) generator.
1681     Rooted<AbstractGeneratorObject*> genObj(
1682         cx, GetGeneratorObjectForFrame(cx, frame));
1683 
1684     // We already went through CheckResumptionValue, which would have replaced
1685     // this invalid resumption value with an error if we were trying to force
1686     // return before the initial yield.
1687     MOZ_RELEASE_ASSERT(genObj && !genObj->isBeforeInitialYield());
1688 
1689     // 1.  `return <value>` creates and returns a new object,
1690     //     `{value: <value>, done: true}`.
1691     //
1692     // For non-async generators, the iterator result object is created in
1693     // bytecode, so we have to simulate that here. For async generators, our
1694     // C++ implementation of AsyncGeneratorResolve will do this. So don't do it
1695     // twice:
1696     if (!genObj->is<AsyncGeneratorObject>()) {
1697       PlainObject* pair = CreateIterResultObject(cx, vp, true);
1698       if (!pair) {
1699         return false;
1700       }
1701       vp.setObject(*pair);
1702     }
1703 
1704     // 2.  The generator must be closed.
1705     genObj->setClosed();
1706 
1707     // Async generators have additionally bookkeeping which must be adjusted
1708     // when switching over to the closed state.
1709     if (genObj->is<AsyncGeneratorObject>()) {
1710       genObj->as<AsyncGeneratorObject>().setCompleted();
1711     }
1712   } else if (frame.callee()->isAsync()) {
1713     if (AbstractGeneratorObject* genObj =
1714             GetGeneratorObjectForFrame(cx, frame)) {
1715       // Throw doesn't require any special processing for async functions when
1716       // the internal generator object is already present.
1717       if (resumeMode == ResumeMode::Throw) {
1718         return true;
1719       }
1720 
1721       Rooted<AsyncFunctionGeneratorObject*> asyncGenObj(
1722           cx, &genObj->as<AsyncFunctionGeneratorObject>());
1723 
1724       // 1.  `return <value>` fulfills and returns the async function's promise.
1725       Rooted<PromiseObject*> promise(cx, asyncGenObj->promise());
1726       if (promise->state() == JS::PromiseState::Pending) {
1727         if (!AsyncFunctionResolve(cx, asyncGenObj, vp,
1728                                   AsyncFunctionResolveKind::Fulfill)) {
1729           return false;
1730         }
1731       }
1732       vp.setObject(*promise);
1733 
1734       // 2.  The generator must be closed.
1735       asyncGenObj->setClosed();
1736     } else {
1737       // We're before entering the actual function code.
1738 
1739       // 1.  `throw <value>` creates a promise rejected with the value *vp.
1740       // 1.  `return <value>` creates a promise resolved with the value *vp.
1741       JSObject* promise = resumeMode == ResumeMode::Throw
1742                               ? PromiseObject::unforgeableReject(cx, vp)
1743                               : PromiseObject::unforgeableResolve(cx, vp);
1744       if (!promise) {
1745         return false;
1746       }
1747       vp.setObject(*promise);
1748 
1749       // 2.  Return normally in both cases.
1750       resumeMode = ResumeMode::Return;
1751     }
1752   }
1753 
1754   return true;
1755 }
1756 
processParsedHandlerResult(JSContext * cx,AbstractFramePtr frame,jsbytecode * pc,bool success,ResumeMode resumeMode,HandleValue value,ResumeMode & resultMode,MutableHandleValue vp)1757 bool Debugger::processParsedHandlerResult(JSContext* cx, AbstractFramePtr frame,
1758                                           jsbytecode* pc, bool success,
1759                                           ResumeMode resumeMode,
1760                                           HandleValue value,
1761                                           ResumeMode& resultMode,
1762                                           MutableHandleValue vp) {
1763   RootedValue rootValue(cx, value);
1764   if (!success || !prepareResumption(cx, frame, pc, resumeMode, &rootValue)) {
1765     RootedValue exceptionRv(cx);
1766     if (!callUncaughtExceptionHandler(cx, &exceptionRv) ||
1767         !ParseResumptionValue(cx, exceptionRv, resumeMode, &rootValue) ||
1768         !prepareResumption(cx, frame, pc, resumeMode, &rootValue)) {
1769       return false;
1770     }
1771   }
1772 
1773   // Since debugger hooks accumulate into the same final value handle, we
1774   // use that to throw if multiple hooks try to set a resumption value.
1775   if (resumeMode != ResumeMode::Continue) {
1776     if (resultMode != ResumeMode::Continue) {
1777       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1778                                 JSMSG_DEBUG_RESUMPTION_CONFLICT);
1779       return false;
1780     }
1781 
1782     vp.set(rootValue);
1783     resultMode = resumeMode;
1784   }
1785 
1786   return true;
1787 }
1788 
processHandlerResult(JSContext * cx,bool success,HandleValue rv,AbstractFramePtr frame,jsbytecode * pc,ResumeMode & resultMode,MutableHandleValue vp)1789 bool Debugger::processHandlerResult(JSContext* cx, bool success, HandleValue rv,
1790                                     AbstractFramePtr frame, jsbytecode* pc,
1791                                     ResumeMode& resultMode,
1792                                     MutableHandleValue vp) {
1793   ResumeMode resumeMode = ResumeMode::Continue;
1794   RootedValue value(cx);
1795   if (success) {
1796     success = ParseResumptionValue(cx, rv, resumeMode, &value);
1797   }
1798   return processParsedHandlerResult(cx, frame, pc, success, resumeMode, value,
1799                                     resultMode, vp);
1800 }
1801 
prepareResumption(JSContext * cx,AbstractFramePtr frame,jsbytecode * pc,ResumeMode & resumeMode,MutableHandleValue vp)1802 bool Debugger::prepareResumption(JSContext* cx, AbstractFramePtr frame,
1803                                  jsbytecode* pc, ResumeMode& resumeMode,
1804                                  MutableHandleValue vp) {
1805   return unwrapDebuggeeValue(cx, vp) &&
1806          CheckResumptionValue(cx, frame, pc, resumeMode, vp);
1807 }
1808 
callUncaughtExceptionHandler(JSContext * cx,MutableHandleValue vp)1809 bool Debugger::callUncaughtExceptionHandler(JSContext* cx,
1810                                             MutableHandleValue vp) {
1811   // Uncaught exceptions arise from Debugger code, and so we must already be in
1812   // an NX section. This also establishes that we are already within the scope
1813   // of an AutoDebuggerJobQueueInterruption object.
1814   MOZ_ASSERT(EnterDebuggeeNoExecute::isLockedInStack(cx, *this));
1815 
1816   if (cx->isExceptionPending() && uncaughtExceptionHook) {
1817     RootedValue exc(cx);
1818     if (!cx->getPendingException(&exc)) {
1819       return false;
1820     }
1821     cx->clearPendingException();
1822 
1823     RootedValue fval(cx, ObjectValue(*uncaughtExceptionHook));
1824     if (js::Call(cx, fval, object, exc, vp)) {
1825       return true;
1826     }
1827   }
1828   return false;
1829 }
1830 
handleUncaughtException(JSContext * cx)1831 bool Debugger::handleUncaughtException(JSContext* cx) {
1832   RootedValue rv(cx);
1833 
1834   return callUncaughtExceptionHandler(cx, &rv);
1835 }
1836 
reportUncaughtException(JSContext * cx)1837 void Debugger::reportUncaughtException(JSContext* cx) {
1838   // Uncaught exceptions arise from Debugger code, and so we must already be
1839   // in an NX section.
1840   MOZ_ASSERT(EnterDebuggeeNoExecute::isLockedInStack(cx, *this));
1841 
1842   if (cx->isExceptionPending()) {
1843     // We want to report the pending exception, but we want to let the
1844     // embedding handle it however it wants to.  So pretend like we're
1845     // starting a new script execution on our current compartment (which
1846     // is the debugger compartment, so reported errors won't get
1847     // reported to various onerror handlers in debuggees) and as part of
1848     // that "execution" simply throw our exception so the embedding can
1849     // deal.
1850     RootedValue exn(cx);
1851     if (cx->getPendingException(&exn)) {
1852       // Clear the exception, because ReportErrorToGlobal will assert that
1853       // we don't have one.
1854       cx->clearPendingException();
1855       ReportErrorToGlobal(cx, cx->global(), exn);
1856     }
1857 
1858     // And if not, or if PrepareScriptEnvironmentAndInvoke somehow left an
1859     // exception on cx (which it totally shouldn't do), just give up.
1860     cx->clearPendingException();
1861   }
1862 }
1863 
1864 /*** Debuggee completion values *********************************************/
1865 
1866 /* static */
fromJSResult(JSContext * cx,bool ok,const Value & rv)1867 Completion Completion::fromJSResult(JSContext* cx, bool ok, const Value& rv) {
1868   MOZ_ASSERT_IF(ok, !cx->isExceptionPending());
1869 
1870   if (ok) {
1871     return Completion(Return(rv));
1872   }
1873 
1874   if (!cx->isExceptionPending()) {
1875     return Completion(Terminate());
1876   }
1877 
1878   RootedValue exception(cx);
1879   RootedSavedFrame stack(cx, cx->getPendingExceptionStack());
1880   bool getSucceeded = cx->getPendingException(&exception);
1881   cx->clearPendingException();
1882   if (!getSucceeded) {
1883     return Completion(Terminate());
1884   }
1885 
1886   return Completion(Throw(exception, stack));
1887 }
1888 
1889 /* static */
fromJSFramePop(JSContext * cx,AbstractFramePtr frame,const jsbytecode * pc,bool ok)1890 Completion Completion::fromJSFramePop(JSContext* cx, AbstractFramePtr frame,
1891                                       const jsbytecode* pc, bool ok) {
1892   // Only Wasm frames get a null pc.
1893   MOZ_ASSERT_IF(!frame.isWasmDebugFrame(), pc);
1894 
1895   // If this isn't a generator suspension, then that's already handled above.
1896   if (!ok || !frame.isGeneratorFrame()) {
1897     return fromJSResult(cx, ok, frame.returnValue());
1898   }
1899 
1900   // A generator is being suspended or returning.
1901 
1902   // Since generators are never wasm, we can assume pc is not nullptr, and
1903   // that analyzing bytecode is meaningful.
1904   MOZ_ASSERT(!frame.isWasmDebugFrame());
1905 
1906   // If we're leaving successfully at a yield opcode, we're probably
1907   // suspending; the `isClosed()` check detects a debugger forced return from
1908   // an `onStep` handler, which looks almost the same.
1909   //
1910   // GetGeneratorObjectForFrame can return nullptr even when a generator
1911   // object does exist, if the frame is paused between the Generator and
1912   // SetAliasedVar opcodes. But by checking the opcode first we eliminate that
1913   // possibility, so it's fine to call genObj->isClosed().
1914   Rooted<AbstractGeneratorObject*> generatorObj(
1915       cx, GetGeneratorObjectForFrame(cx, frame));
1916   switch (JSOp(*pc)) {
1917     case JSOp::InitialYield:
1918       MOZ_ASSERT(!generatorObj->isClosed());
1919       return Completion(InitialYield(generatorObj));
1920 
1921     case JSOp::Yield:
1922       MOZ_ASSERT(!generatorObj->isClosed());
1923       return Completion(Yield(generatorObj, frame.returnValue()));
1924 
1925     case JSOp::Await:
1926       MOZ_ASSERT(!generatorObj->isClosed());
1927       return Completion(Await(generatorObj, frame.returnValue()));
1928 
1929     default:
1930       return Completion(Return(frame.returnValue()));
1931   }
1932 }
1933 
trace(JSTracer * trc)1934 void Completion::trace(JSTracer* trc) {
1935   variant.match([=](auto& var) { var.trace(trc); });
1936 }
1937 
1938 struct MOZ_STACK_CLASS Completion::BuildValueMatcher {
1939   JSContext* cx;
1940   Debugger* dbg;
1941   MutableHandleValue result;
1942 
BuildValueMatcherCompletion::BuildValueMatcher1943   BuildValueMatcher(JSContext* cx, Debugger* dbg, MutableHandleValue result)
1944       : cx(cx), dbg(dbg), result(result) {
1945     cx->check(dbg->toJSObject());
1946   }
1947 
operator ()Completion::BuildValueMatcher1948   bool operator()(const Completion::Return& ret) {
1949     RootedNativeObject obj(cx, newObject());
1950     RootedValue retval(cx, ret.value);
1951     if (!obj || !wrap(&retval) || !add(obj, cx->names().return_, retval)) {
1952       return false;
1953     }
1954     result.setObject(*obj);
1955     return true;
1956   }
1957 
operator ()Completion::BuildValueMatcher1958   bool operator()(const Completion::Throw& thr) {
1959     RootedNativeObject obj(cx, newObject());
1960     RootedValue exc(cx, thr.exception);
1961     if (!obj || !wrap(&exc) || !add(obj, cx->names().throw_, exc)) {
1962       return false;
1963     }
1964     if (thr.stack) {
1965       RootedValue stack(cx, ObjectValue(*thr.stack));
1966       if (!wrapStack(&stack) || !add(obj, cx->names().stack, stack)) {
1967         return false;
1968       }
1969     }
1970     result.setObject(*obj);
1971     return true;
1972   }
1973 
operator ()Completion::BuildValueMatcher1974   bool operator()(const Completion::Terminate& term) {
1975     result.setNull();
1976     return true;
1977   }
1978 
operator ()Completion::BuildValueMatcher1979   bool operator()(const Completion::InitialYield& initialYield) {
1980     RootedNativeObject obj(cx, newObject());
1981     RootedValue gen(cx, ObjectValue(*initialYield.generatorObject));
1982     if (!obj || !wrap(&gen) || !add(obj, cx->names().return_, gen) ||
1983         !add(obj, cx->names().yield, TrueHandleValue) ||
1984         !add(obj, cx->names().initial, TrueHandleValue)) {
1985       return false;
1986     }
1987     result.setObject(*obj);
1988     return true;
1989   }
1990 
operator ()Completion::BuildValueMatcher1991   bool operator()(const Completion::Yield& yield) {
1992     RootedNativeObject obj(cx, newObject());
1993     RootedValue iteratorResult(cx, yield.iteratorResult);
1994     if (!obj || !wrap(&iteratorResult) ||
1995         !add(obj, cx->names().return_, iteratorResult) ||
1996         !add(obj, cx->names().yield, TrueHandleValue)) {
1997       return false;
1998     }
1999     result.setObject(*obj);
2000     return true;
2001   }
2002 
operator ()Completion::BuildValueMatcher2003   bool operator()(const Completion::Await& await) {
2004     RootedNativeObject obj(cx, newObject());
2005     RootedValue awaitee(cx, await.awaitee);
2006     if (!obj || !wrap(&awaitee) || !add(obj, cx->names().return_, awaitee) ||
2007         !add(obj, cx->names().await, TrueHandleValue)) {
2008       return false;
2009     }
2010     result.setObject(*obj);
2011     return true;
2012   }
2013 
2014  private:
newObjectCompletion::BuildValueMatcher2015   NativeObject* newObject() const {
2016     return NewBuiltinClassInstance<PlainObject>(cx);
2017   }
2018 
addCompletion::BuildValueMatcher2019   bool add(HandleNativeObject obj, PropertyName* name,
2020            HandleValue value) const {
2021     return NativeDefineDataProperty(cx, obj, name, value, JSPROP_ENUMERATE);
2022   }
2023 
wrapCompletion::BuildValueMatcher2024   bool wrap(MutableHandleValue v) const {
2025     return dbg->wrapDebuggeeValue(cx, v);
2026   }
2027 
2028   // Saved stacks are wrapped for direct consumption by debugger code.
wrapStackCompletion::BuildValueMatcher2029   bool wrapStack(MutableHandleValue stack) const {
2030     return cx->compartment()->wrap(cx, stack);
2031   }
2032 };
2033 
buildCompletionValue(JSContext * cx,Debugger * dbg,MutableHandleValue result) const2034 bool Completion::buildCompletionValue(JSContext* cx, Debugger* dbg,
2035                                       MutableHandleValue result) const {
2036   return variant.match(BuildValueMatcher(cx, dbg, result));
2037 }
2038 
updateFromHookResult(ResumeMode resumeMode,HandleValue value)2039 void Completion::updateFromHookResult(ResumeMode resumeMode,
2040                                       HandleValue value) {
2041   switch (resumeMode) {
2042     case ResumeMode::Continue:
2043       // No change to how we'll resume.
2044       break;
2045 
2046     case ResumeMode::Throw:
2047       // Since this is a new exception, the stack for the old one may not apply.
2048       // If we extend resumption values to specify stacks, we could revisit
2049       // this.
2050       variant = Variant(Throw(value, nullptr));
2051       break;
2052 
2053     case ResumeMode::Terminate:
2054       variant = Variant(Terminate());
2055       break;
2056 
2057     case ResumeMode::Return:
2058       variant = Variant(Return(value));
2059       break;
2060 
2061     default:
2062       MOZ_CRASH("invalid resumeMode value");
2063   }
2064 }
2065 
2066 struct MOZ_STACK_CLASS Completion::ToResumeModeMatcher {
2067   MutableHandleValue value;
2068   MutableHandleSavedFrame exnStack;
ToResumeModeMatcherCompletion::ToResumeModeMatcher2069   ToResumeModeMatcher(MutableHandleValue value,
2070                       MutableHandleSavedFrame exnStack)
2071       : value(value), exnStack(exnStack) {}
2072 
operator ()Completion::ToResumeModeMatcher2073   ResumeMode operator()(const Return& ret) {
2074     value.set(ret.value);
2075     return ResumeMode::Return;
2076   }
2077 
operator ()Completion::ToResumeModeMatcher2078   ResumeMode operator()(const Throw& thr) {
2079     value.set(thr.exception);
2080     exnStack.set(thr.stack);
2081     return ResumeMode::Throw;
2082   }
2083 
operator ()Completion::ToResumeModeMatcher2084   ResumeMode operator()(const Terminate& term) {
2085     value.setUndefined();
2086     return ResumeMode::Terminate;
2087   }
2088 
operator ()Completion::ToResumeModeMatcher2089   ResumeMode operator()(const InitialYield& initialYield) {
2090     value.setObject(*initialYield.generatorObject);
2091     return ResumeMode::Return;
2092   }
2093 
operator ()Completion::ToResumeModeMatcher2094   ResumeMode operator()(const Yield& yield) {
2095     value.set(yield.iteratorResult);
2096     return ResumeMode::Return;
2097   }
2098 
operator ()Completion::ToResumeModeMatcher2099   ResumeMode operator()(const Await& await) {
2100     value.set(await.awaitee);
2101     return ResumeMode::Return;
2102   }
2103 };
2104 
toResumeMode(ResumeMode & resumeMode,MutableHandleValue value,MutableHandleSavedFrame exnStack) const2105 void Completion::toResumeMode(ResumeMode& resumeMode, MutableHandleValue value,
2106                               MutableHandleSavedFrame exnStack) const {
2107   resumeMode = variant.match(ToResumeModeMatcher(value, exnStack));
2108 }
2109 
2110 /*** Firing debugger hooks **************************************************/
2111 
CallMethodIfPresent(JSContext * cx,HandleObject obj,const char * name,size_t argc,Value * argv,MutableHandleValue rval)2112 static bool CallMethodIfPresent(JSContext* cx, HandleObject obj,
2113                                 const char* name, size_t argc, Value* argv,
2114                                 MutableHandleValue rval) {
2115   rval.setUndefined();
2116   JSAtom* atom = Atomize(cx, name, strlen(name));
2117   if (!atom) {
2118     return false;
2119   }
2120 
2121   RootedId id(cx, AtomToId(atom));
2122   RootedValue fval(cx);
2123   if (!GetProperty(cx, obj, obj, id, &fval)) {
2124     return false;
2125   }
2126 
2127   if (!IsCallable(fval)) {
2128     return true;
2129   }
2130 
2131   InvokeArgs args(cx);
2132   if (!args.init(cx, argc)) {
2133     return false;
2134   }
2135 
2136   for (size_t i = 0; i < argc; i++) {
2137     args[i].set(argv[i]);
2138   }
2139 
2140   rval.setObject(*obj);  // overwritten by successful Call
2141   return js::Call(cx, fval, rval, args, rval);
2142 }
2143 
fireDebuggerStatement(JSContext * cx,ResumeMode & resumeMode,MutableHandleValue vp)2144 bool Debugger::fireDebuggerStatement(JSContext* cx, ResumeMode& resumeMode,
2145                                      MutableHandleValue vp) {
2146   RootedObject hook(cx, getHook(OnDebuggerStatement));
2147   MOZ_ASSERT(hook);
2148   MOZ_ASSERT(hook->isCallable());
2149 
2150   ScriptFrameIter iter(cx);
2151   RootedValue scriptFrame(cx);
2152   if (!getFrame(cx, iter, &scriptFrame)) {
2153     return false;
2154   }
2155 
2156   RootedValue fval(cx, ObjectValue(*hook));
2157   RootedValue rv(cx);
2158   bool ok = js::Call(cx, fval, object, scriptFrame, &rv);
2159   return processHandlerResult(cx, ok, rv, iter.abstractFramePtr(), iter.pc(),
2160                               resumeMode, vp);
2161 }
2162 
fireExceptionUnwind(JSContext * cx,HandleValue exc,ResumeMode & resumeMode,MutableHandleValue vp)2163 bool Debugger::fireExceptionUnwind(JSContext* cx, HandleValue exc,
2164                                    ResumeMode& resumeMode,
2165                                    MutableHandleValue vp) {
2166   RootedObject hook(cx, getHook(OnExceptionUnwind));
2167   MOZ_ASSERT(hook);
2168   MOZ_ASSERT(hook->isCallable());
2169 
2170   RootedValue scriptFrame(cx);
2171   RootedValue wrappedExc(cx, exc);
2172 
2173   FrameIter iter(cx);
2174   if (!getFrame(cx, iter, &scriptFrame) ||
2175       !wrapDebuggeeValue(cx, &wrappedExc)) {
2176     return false;
2177   }
2178 
2179   RootedValue fval(cx, ObjectValue(*hook));
2180   RootedValue rv(cx);
2181   bool ok = js::Call(cx, fval, object, scriptFrame, wrappedExc, &rv);
2182   return processHandlerResult(cx, ok, rv, iter.abstractFramePtr(), iter.pc(),
2183                               resumeMode, vp);
2184 }
2185 
fireEnterFrame(JSContext * cx,ResumeMode & resumeMode,MutableHandleValue vp)2186 bool Debugger::fireEnterFrame(JSContext* cx, ResumeMode& resumeMode,
2187                               MutableHandleValue vp) {
2188   RootedObject hook(cx, getHook(OnEnterFrame));
2189   MOZ_ASSERT(hook);
2190   MOZ_ASSERT(hook->isCallable());
2191 
2192   RootedValue scriptFrame(cx);
2193 
2194   FrameIter iter(cx);
2195 
2196 #if DEBUG
2197   // Assert that the hook won't be able to re-enter the generator.
2198   if (iter.hasScript() && JSOp(*iter.pc()) == JSOp::AfterYield) {
2199     AutoRealm ar(cx, iter.script());
2200     auto* genObj = GetGeneratorObjectForFrame(cx, iter.abstractFramePtr());
2201     MOZ_ASSERT(genObj->isRunning());
2202   }
2203 #endif
2204 
2205   if (!getFrame(cx, iter, &scriptFrame)) {
2206     return false;
2207   }
2208 
2209   RootedValue fval(cx, ObjectValue(*hook));
2210   RootedValue rv(cx);
2211   bool ok = js::Call(cx, fval, object, scriptFrame, &rv);
2212 
2213   return processHandlerResult(cx, ok, rv, iter.abstractFramePtr(), iter.pc(),
2214                               resumeMode, vp);
2215 }
2216 
fireNativeCall(JSContext * cx,const CallArgs & args,CallReason reason,ResumeMode & resumeMode,MutableHandleValue vp)2217 bool Debugger::fireNativeCall(JSContext* cx, const CallArgs& args,
2218                               CallReason reason, ResumeMode& resumeMode,
2219                               MutableHandleValue vp) {
2220   RootedObject hook(cx, getHook(OnNativeCall));
2221   MOZ_ASSERT(hook);
2222   MOZ_ASSERT(hook->isCallable());
2223 
2224   RootedValue fval(cx, ObjectValue(*hook));
2225   RootedValue calleeval(cx, args.calleev());
2226   if (!wrapDebuggeeValue(cx, &calleeval)) {
2227     return false;
2228   }
2229 
2230   JSAtom* reasonAtom = nullptr;
2231   switch (reason) {
2232     case CallReason::Call:
2233       reasonAtom = cx->names().call;
2234       break;
2235     case CallReason::Getter:
2236       reasonAtom = cx->names().get;
2237       break;
2238     case CallReason::Setter:
2239       reasonAtom = cx->names().set;
2240       break;
2241   }
2242   cx->markAtom(reasonAtom);
2243 
2244   RootedValue reasonval(cx, StringValue(reasonAtom));
2245 
2246   RootedValue rv(cx);
2247   bool ok = js::Call(cx, fval, object, calleeval, reasonval, &rv);
2248 
2249   return processHandlerResult(cx, ok, rv, NullFramePtr(), nullptr, resumeMode,
2250                               vp);
2251 }
2252 
fireNewScript(JSContext * cx,Handle<DebuggerScriptReferent> scriptReferent)2253 bool Debugger::fireNewScript(JSContext* cx,
2254                              Handle<DebuggerScriptReferent> scriptReferent) {
2255   RootedObject hook(cx, getHook(OnNewScript));
2256   MOZ_ASSERT(hook);
2257   MOZ_ASSERT(hook->isCallable());
2258 
2259   JSObject* dsobj = wrapVariantReferent(cx, scriptReferent);
2260   if (!dsobj) {
2261     return false;
2262   }
2263 
2264   RootedValue fval(cx, ObjectValue(*hook));
2265   RootedValue dsval(cx, ObjectValue(*dsobj));
2266   RootedValue rv(cx);
2267   return js::Call(cx, fval, object, dsval, &rv) || handleUncaughtException(cx);
2268 }
2269 
fireOnGarbageCollectionHook(JSContext * cx,const JS::dbg::GarbageCollectionEvent::Ptr & gcData)2270 bool Debugger::fireOnGarbageCollectionHook(
2271     JSContext* cx, const JS::dbg::GarbageCollectionEvent::Ptr& gcData) {
2272   MOZ_ASSERT(observedGC(gcData->majorGCNumber()));
2273   observedGCs.remove(gcData->majorGCNumber());
2274 
2275   RootedObject hook(cx, getHook(OnGarbageCollection));
2276   MOZ_ASSERT(hook);
2277   MOZ_ASSERT(hook->isCallable());
2278 
2279   JSObject* dataObj = gcData->toJSObject(cx);
2280   if (!dataObj) {
2281     return false;
2282   }
2283 
2284   RootedValue fval(cx, ObjectValue(*hook));
2285   RootedValue dataVal(cx, ObjectValue(*dataObj));
2286   RootedValue rv(cx);
2287   return js::Call(cx, fval, object, dataVal, &rv) ||
2288          handleUncaughtException(cx);
2289 }
2290 
2291 template <typename HookIsEnabledFun /* bool (Debugger*) */,
2292           typename FireHookFun /* bool (Debugger*) */>
2293 /* static */
dispatchQuietHook(JSContext * cx,HookIsEnabledFun hookIsEnabled,FireHookFun fireHook)2294 void Debugger::dispatchQuietHook(JSContext* cx, HookIsEnabledFun hookIsEnabled,
2295                                  FireHookFun fireHook) {
2296   DebuggerList<HookIsEnabledFun> debuggerList(cx, hookIsEnabled);
2297 
2298   if (!debuggerList.init(cx)) {
2299     // init may fail due to OOM. This OOM is not handlable at the
2300     // callsites of dispatchQuietHook in the engine.
2301     cx->clearPendingException();
2302     return;
2303   }
2304 
2305   debuggerList.dispatchQuietHook(cx, fireHook);
2306 }
2307 
2308 template <typename HookIsEnabledFun /* bool (Debugger*) */, typename FireHookFun /* bool (Debugger*, ResumeMode&, MutableHandleValue vp) */>
2309 /* static */
dispatchResumptionHook(JSContext * cx,AbstractFramePtr frame,HookIsEnabledFun hookIsEnabled,FireHookFun fireHook)2310 bool Debugger::dispatchResumptionHook(JSContext* cx, AbstractFramePtr frame,
2311                                       HookIsEnabledFun hookIsEnabled,
2312                                       FireHookFun fireHook) {
2313   DebuggerList<HookIsEnabledFun> debuggerList(cx, hookIsEnabled);
2314 
2315   if (!debuggerList.init(cx)) {
2316     return false;
2317   }
2318 
2319   return debuggerList.dispatchResumptionHook(cx, frame, fireHook);
2320 }
2321 
2322 // Maximum length for source URLs that can be remembered.
2323 static const size_t SourceURLMaxLength = 1024;
2324 
2325 // Maximum number of source URLs that can be remembered in a realm.
2326 static const size_t SourceURLRealmLimit = 100;
2327 
RememberSourceURL(JSContext * cx,HandleScript script)2328 static bool RememberSourceURL(JSContext* cx, HandleScript script) {
2329   cx->check(script);
2330 
2331   // Sources introduced dynamically are not remembered.
2332   if (script->sourceObject()->unwrappedIntroductionScript()) {
2333     return true;
2334   }
2335 
2336   const char* filename = script->filename();
2337   if (!filename ||
2338       strnlen(filename, SourceURLMaxLength + 1) > SourceURLMaxLength) {
2339     return true;
2340   }
2341 
2342   RootedObject holder(cx, script->global().getSourceURLsHolder());
2343   if (!holder) {
2344     holder = NewDenseEmptyArray(cx);
2345     if (!holder) {
2346       return false;
2347     }
2348     script->global().setSourceURLsHolder(holder);
2349   }
2350 
2351   if (holder->as<ArrayObject>().length() >= SourceURLRealmLimit) {
2352     return true;
2353   }
2354 
2355   RootedString filenameString(cx, JS_AtomizeString(cx, filename));
2356   if (!filenameString) {
2357     return false;
2358   }
2359 
2360   // The source URLs holder never escapes to script, so we can treat it as a
2361   // newborn array for the purpose of adding elements.
2362   return NewbornArrayPush(cx, holder, StringValue(filenameString));
2363 }
2364 
onNewScript(JSContext * cx,HandleScript script)2365 void DebugAPI::onNewScript(JSContext* cx, HandleScript script) {
2366   if (!script->realm()->isDebuggee()) {
2367     // Remember the URLs associated with scripts in non-system realms,
2368     // in case the debugger is attached later.
2369     if (!script->realm()->isSystem()) {
2370       if (!RememberSourceURL(cx, script)) {
2371         cx->clearPendingException();
2372       }
2373     }
2374     return;
2375   }
2376 
2377   Debugger::dispatchQuietHook(
2378       cx,
2379       [script](Debugger* dbg) -> bool {
2380         return dbg->observesNewScript() && dbg->observesScript(script);
2381       },
2382       [&](Debugger* dbg) -> bool {
2383         BaseScript* base = script.get();
2384         Rooted<DebuggerScriptReferent> scriptReferent(cx, base);
2385         return dbg->fireNewScript(cx, scriptReferent);
2386       });
2387 }
2388 
slowPathOnNewWasmInstance(JSContext * cx,Handle<WasmInstanceObject * > wasmInstance)2389 void DebugAPI::slowPathOnNewWasmInstance(
2390     JSContext* cx, Handle<WasmInstanceObject*> wasmInstance) {
2391   Debugger::dispatchQuietHook(
2392       cx,
2393       [wasmInstance](Debugger* dbg) -> bool {
2394         return dbg->observesNewScript() &&
2395                dbg->observesGlobal(&wasmInstance->global());
2396       },
2397       [&](Debugger* dbg) -> bool {
2398         Rooted<DebuggerScriptReferent> scriptReferent(cx, wasmInstance.get());
2399         return dbg->fireNewScript(cx, scriptReferent);
2400       });
2401 }
2402 
2403 /* static */
onTrap(JSContext * cx)2404 bool DebugAPI::onTrap(JSContext* cx) {
2405   FrameIter iter(cx);
2406   JS::AutoSaveExceptionState savedExc(cx);
2407   Rooted<GlobalObject*> global(cx);
2408   BreakpointSite* site;
2409   bool isJS;       // true when iter.hasScript(), false when iter.isWasm()
2410   jsbytecode* pc;  // valid when isJS == true
2411   uint32_t bytecodeOffset;  // valid when isJS == false
2412   if (iter.hasScript()) {
2413     RootedScript script(cx, iter.script());
2414     MOZ_ASSERT(script->isDebuggee());
2415     global.set(&script->global());
2416     isJS = true;
2417     pc = iter.pc();
2418     bytecodeOffset = 0;
2419     site = DebugScript::getBreakpointSite(script, pc);
2420   } else {
2421     MOZ_ASSERT(iter.isWasm());
2422     global.set(&iter.wasmInstance()->object()->global());
2423     isJS = false;
2424     pc = nullptr;
2425     bytecodeOffset = iter.wasmBytecodeOffset();
2426     site = iter.wasmInstance()->debug().getBreakpointSite(bytecodeOffset);
2427   }
2428 
2429   // Build list of breakpoint handlers.
2430   //
2431   // This does not need to be rooted: since the JSScript/WasmInstance is on the
2432   // stack, the Breakpoints will not be GC'd. However, they may be deleted, and
2433   // we check for that case below.
2434   Vector<Breakpoint*> triggered(cx);
2435   for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
2436     if (!triggered.append(bp)) {
2437       return false;
2438     }
2439   }
2440 
2441   ResumeMode resumeMode = ResumeMode::Continue;
2442   RootedValue rval(cx);
2443 
2444   if (triggered.length() > 0) {
2445     // Preserve the debuggee's microtask event queue while we run the hooks, so
2446     // the debugger's microtask checkpoints don't run from the debuggee's
2447     // microtasks, and vice versa.
2448     JS::AutoDebuggerJobQueueInterruption adjqi;
2449     if (!adjqi.init(cx)) {
2450       return false;
2451     }
2452 
2453     for (Breakpoint* bp : triggered) {
2454       // Handlers can clear breakpoints. Check that bp still exists.
2455       if (!site || !site->hasBreakpoint(bp)) {
2456         continue;
2457       }
2458 
2459       // There are two reasons we have to check whether dbg is debugging
2460       // global.
2461       //
2462       // One is just that one breakpoint handler can disable other Debuggers
2463       // or remove debuggees.
2464       //
2465       // The other has to do with non-compile-and-go scripts, which have no
2466       // specific global--until they are executed. Only now do we know which
2467       // global the script is running against.
2468       Debugger* dbg = bp->debugger;
2469       if (dbg->debuggees.has(global)) {
2470         EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);
2471 
2472         bool result = dbg->enterDebuggerHook(cx, [&]() -> bool {
2473           RootedValue scriptFrame(cx);
2474           if (!dbg->getFrame(cx, iter, &scriptFrame)) {
2475             return false;
2476           }
2477 
2478           // Re-wrap the breakpoint's handler for the Debugger's compartment.
2479           // When the handler and the Debugger are in the same compartment (the
2480           // usual case), this actually unwraps it, but there's no requirement
2481           // that they be in the same compartment, so we can't be sure.
2482           Rooted<JSObject*> handler(cx, bp->handler);
2483           if (!cx->compartment()->wrap(cx, &handler)) {
2484             return false;
2485           }
2486 
2487           RootedValue rv(cx);
2488           bool ok = CallMethodIfPresent(cx, handler, "hit", 1,
2489                                         scriptFrame.address(), &rv);
2490 
2491           return dbg->processHandlerResult(cx, ok, rv, iter.abstractFramePtr(),
2492                                            iter.pc(), resumeMode, &rval);
2493         });
2494         adjqi.runJobs();
2495 
2496         if (!result) {
2497           return false;
2498         }
2499 
2500         // Calling JS code invalidates site. Reload it.
2501         if (isJS) {
2502           site = DebugScript::getBreakpointSite(iter.script(), pc);
2503         } else {
2504           site = iter.wasmInstance()->debug().getBreakpointSite(bytecodeOffset);
2505         }
2506       }
2507     }
2508   }
2509 
2510   if (!ApplyFrameResumeMode(cx, iter.abstractFramePtr(), resumeMode, rval)) {
2511     savedExc.drop();
2512     return false;
2513   }
2514   return true;
2515 }
2516 
2517 /* static */
onSingleStep(JSContext * cx)2518 bool DebugAPI::onSingleStep(JSContext* cx) {
2519   FrameIter iter(cx);
2520 
2521   // We may be stepping over a JSOp::Exception, that pushes the context's
2522   // pending exception for a 'catch' clause to handle. Don't let the onStep
2523   // handlers mess with that (other than by returning a resumption value).
2524   JS::AutoSaveExceptionState savedExc(cx);
2525 
2526   // Build list of Debugger.Frame instances referring to this frame with
2527   // onStep handlers.
2528   Rooted<Debugger::DebuggerFrameVector> frames(
2529       cx, Debugger::DebuggerFrameVector(cx));
2530   if (!Debugger::getDebuggerFrames(iter.abstractFramePtr(), &frames)) {
2531     return false;
2532   }
2533 
2534 #ifdef DEBUG
2535   // Validate the single-step count on this frame's script, to ensure that
2536   // we're not receiving traps we didn't ask for. Even when frames is
2537   // non-empty (and thus we know this trap was requested), do the check
2538   // anyway, to make sure the count has the correct non-zero value.
2539   //
2540   // The converse --- ensuring that we do receive traps when we should --- can
2541   // be done with unit tests.
2542   if (iter.hasScript()) {
2543     uint32_t liveStepperCount = 0;
2544     uint32_t suspendedStepperCount = 0;
2545     JSScript* trappingScript = iter.script();
2546     for (Realm::DebuggerVectorEntry& entry : cx->global()->getDebuggers()) {
2547       Debugger* dbg = entry.dbg;
2548       for (Debugger::FrameMap::Range r = dbg->frames.all(); !r.empty();
2549            r.popFront()) {
2550         AbstractFramePtr frame = r.front().key();
2551         NativeObject* frameobj = r.front().value();
2552         if (frame.isWasmDebugFrame()) {
2553           continue;
2554         }
2555         if (frame.script() == trappingScript &&
2556             !frameobj->getReservedSlot(DebuggerFrame::ONSTEP_HANDLER_SLOT)
2557                  .isUndefined()) {
2558           liveStepperCount++;
2559         }
2560       }
2561 
2562       // Also count hooks set on suspended generator frames.
2563       for (Debugger::GeneratorWeakMap::Range r = dbg->generatorFrames.all();
2564            !r.empty(); r.popFront()) {
2565         AbstractGeneratorObject& genObj = *r.front().key();
2566         DebuggerFrame& frameObj = *r.front().value();
2567         MOZ_ASSERT(&frameObj.unwrappedGenerator() == &genObj);
2568 
2569         // Live Debugger.Frames were already counted in dbg->frames loop.
2570         if (frameObj.isOnStack()) {
2571           continue;
2572         }
2573 
2574         // If a frame isn't live, but it has an entry in generatorFrames,
2575         // it had better be suspended.
2576         MOZ_ASSERT(genObj.isSuspended());
2577 
2578         if (genObj.callee().hasBaseScript() &&
2579             genObj.callee().baseScript() == trappingScript &&
2580             !frameObj.getReservedSlot(DebuggerFrame::ONSTEP_HANDLER_SLOT)
2581                  .isUndefined()) {
2582           suspendedStepperCount++;
2583         }
2584       }
2585     }
2586 
2587     MOZ_ASSERT(liveStepperCount + suspendedStepperCount ==
2588                DebugScript::getStepperCount(trappingScript));
2589   }
2590 #endif
2591 
2592   RootedValue rval(cx);
2593   ResumeMode resumeMode = ResumeMode::Continue;
2594 
2595   if (frames.length() > 0) {
2596     // Preserve the debuggee's microtask event queue while we run the hooks, so
2597     // the debugger's microtask checkpoints don't run from the debuggee's
2598     // microtasks, and vice versa.
2599     JS::AutoDebuggerJobQueueInterruption adjqi;
2600     if (!adjqi.init(cx)) {
2601       return false;
2602     }
2603 
2604     // Call onStep for frames that have the handler set.
2605     for (size_t i = 0; i < frames.length(); i++) {
2606       HandleDebuggerFrame frame = frames[i];
2607       OnStepHandler* handler = frame->onStepHandler();
2608       if (!handler) {
2609         continue;
2610       }
2611 
2612       Debugger* dbg = Debugger::fromChildJSObject(frame);
2613       EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);
2614 
2615       bool result = dbg->enterDebuggerHook(cx, [&]() -> bool {
2616         ResumeMode nextResumeMode = ResumeMode::Continue;
2617         RootedValue nextValue(cx);
2618 
2619         bool success = handler->onStep(cx, frame, nextResumeMode, &nextValue);
2620         return dbg->processParsedHandlerResult(
2621             cx, iter.abstractFramePtr(), iter.pc(), success, nextResumeMode,
2622             nextValue, resumeMode, &rval);
2623       });
2624       adjqi.runJobs();
2625 
2626       if (!result) {
2627         return false;
2628       }
2629     }
2630   }
2631 
2632   if (!ApplyFrameResumeMode(cx, iter.abstractFramePtr(), resumeMode, rval)) {
2633     savedExc.drop();
2634     return false;
2635   }
2636   return true;
2637 }
2638 
fireNewGlobalObject(JSContext * cx,Handle<GlobalObject * > global)2639 bool Debugger::fireNewGlobalObject(JSContext* cx,
2640                                    Handle<GlobalObject*> global) {
2641   RootedObject hook(cx, getHook(OnNewGlobalObject));
2642   MOZ_ASSERT(hook);
2643   MOZ_ASSERT(hook->isCallable());
2644 
2645   RootedValue wrappedGlobal(cx, ObjectValue(*global));
2646   if (!wrapDebuggeeValue(cx, &wrappedGlobal)) {
2647     return false;
2648   }
2649 
2650   // onNewGlobalObject is infallible, and thus is only allowed to return
2651   // undefined as a resumption value. If it returns anything else, we throw.
2652   // And if that happens, or if the hook itself throws, we invoke the
2653   // uncaughtExceptionHook so that we never leave an exception pending on the
2654   // cx. This allows JS_NewGlobalObject to avoid handling failures from
2655   // debugger hooks.
2656   RootedValue rv(cx);
2657   RootedValue fval(cx, ObjectValue(*hook));
2658   bool ok = js::Call(cx, fval, object, wrappedGlobal, &rv);
2659   if (ok && !rv.isUndefined()) {
2660     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2661                               JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED);
2662     ok = false;
2663   }
2664 
2665   return ok || handleUncaughtException(cx);
2666 }
2667 
slowPathOnNewGlobalObject(JSContext * cx,Handle<GlobalObject * > global)2668 void DebugAPI::slowPathOnNewGlobalObject(JSContext* cx,
2669                                          Handle<GlobalObject*> global) {
2670   MOZ_ASSERT(!cx->runtime()->onNewGlobalObjectWatchers().isEmpty());
2671   if (global->realm()->creationOptions().invisibleToDebugger()) {
2672     return;
2673   }
2674 
2675   // Make a copy of the runtime's onNewGlobalObjectWatchers before running the
2676   // handlers. Since one Debugger's handler can disable another's, the list
2677   // can be mutated while we're walking it.
2678   RootedObjectVector watchers(cx);
2679   for (auto& dbg : cx->runtime()->onNewGlobalObjectWatchers()) {
2680     MOZ_ASSERT(dbg.observesNewGlobalObject());
2681     JSObject* obj = dbg.object;
2682     JS::ExposeObjectToActiveJS(obj);
2683     if (!watchers.append(obj)) {
2684       if (cx->isExceptionPending()) {
2685         cx->clearPendingException();
2686       }
2687       return;
2688     }
2689   }
2690 
2691   // Preserve the debuggee's microtask event queue while we run the hooks, so
2692   // the debugger's microtask checkpoints don't run from the debuggee's
2693   // microtasks, and vice versa.
2694   JS::AutoDebuggerJobQueueInterruption adjqi;
2695   if (!adjqi.init(cx)) {
2696     cx->clearPendingException();
2697     return;
2698   }
2699 
2700   for (size_t i = 0; i < watchers.length(); i++) {
2701     Debugger* dbg = Debugger::fromJSObject(watchers[i]);
2702     EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);
2703 
2704     if (dbg->observesNewGlobalObject()) {
2705       bool result = dbg->enterDebuggerHook(
2706           cx, [&]() -> bool { return dbg->fireNewGlobalObject(cx, global); });
2707       adjqi.runJobs();
2708 
2709       if (!result) {
2710         // Like other quiet hooks using dispatchQuietHook, this hook
2711         // silently ignores all errors that propagate out of it and aren't
2712         // already handled by the hook error reporting.
2713         cx->clearPendingException();
2714         break;
2715       }
2716     }
2717   }
2718   MOZ_ASSERT(!cx->isExceptionPending());
2719 }
2720 
2721 /* static */
slowPathNotifyParticipatesInGC(uint64_t majorGCNumber,Realm::DebuggerVector & dbgs)2722 void DebugAPI::slowPathNotifyParticipatesInGC(uint64_t majorGCNumber,
2723                                               Realm::DebuggerVector& dbgs) {
2724   for (Realm::DebuggerVector::Range r = dbgs.all(); !r.empty(); r.popFront()) {
2725     if (!r.front().dbg.unbarrieredGet()->debuggeeIsBeingCollected(
2726             majorGCNumber)) {
2727 #ifdef DEBUG
2728       fprintf(stderr,
2729               "OOM while notifying observing Debuggers of a GC: The "
2730               "onGarbageCollection\n"
2731               "hook will not be fired for this GC for some Debuggers!\n");
2732 #endif
2733       return;
2734     }
2735   }
2736 }
2737 
2738 /* static */
allocationSamplingProbability(GlobalObject * global)2739 Maybe<double> DebugAPI::allocationSamplingProbability(GlobalObject* global) {
2740   Realm::DebuggerVector& dbgs = global->getDebuggers();
2741   if (dbgs.empty()) {
2742     return Nothing();
2743   }
2744 
2745   DebugOnly<Realm::DebuggerVectorEntry*> begin = dbgs.begin();
2746 
2747   double probability = 0;
2748   bool foundAnyDebuggers = false;
2749   for (auto p = dbgs.begin(); p < dbgs.end(); p++) {
2750     // The set of debuggers had better not change while we're iterating,
2751     // such that the vector gets reallocated.
2752     MOZ_ASSERT(dbgs.begin() == begin);
2753     // Use unbarrieredGet() to prevent triggering read barrier while collecting,
2754     // this is safe as long as dbgp does not escape.
2755     Debugger* dbgp = p->dbg.unbarrieredGet();
2756 
2757     if (dbgp->trackingAllocationSites) {
2758       foundAnyDebuggers = true;
2759       probability = std::max(dbgp->allocationSamplingProbability, probability);
2760     }
2761   }
2762 
2763   return foundAnyDebuggers ? Some(probability) : Nothing();
2764 }
2765 
2766 /* static */
slowPathOnLogAllocationSite(JSContext * cx,HandleObject obj,HandleSavedFrame frame,mozilla::TimeStamp when,Realm::DebuggerVector & dbgs)2767 bool DebugAPI::slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj,
2768                                            HandleSavedFrame frame,
2769                                            mozilla::TimeStamp when,
2770                                            Realm::DebuggerVector& dbgs) {
2771   MOZ_ASSERT(!dbgs.empty());
2772   mozilla::DebugOnly<Realm::DebuggerVectorEntry*> begin = dbgs.begin();
2773 
2774   // Root all the Debuggers while we're iterating over them;
2775   // appendAllocationSite calls Compartment::wrap, and thus can GC.
2776   //
2777   // SpiderMonkey protocol is generally for the caller to prove that it has
2778   // rooted the stuff it's asking you to operate on (i.e. by passing a
2779   // Handle), but in this case, we're iterating over a global's list of
2780   // Debuggers, and globals only hold their Debuggers weakly.
2781   Rooted<GCVector<JSObject*>> activeDebuggers(cx, GCVector<JSObject*>(cx));
2782   for (auto p = dbgs.begin(); p < dbgs.end(); p++) {
2783     if (!activeDebuggers.append(p->dbg->object)) {
2784       return false;
2785     }
2786   }
2787 
2788   for (auto p = dbgs.begin(); p < dbgs.end(); p++) {
2789     // The set of debuggers had better not change while we're iterating,
2790     // such that the vector gets reallocated.
2791     MOZ_ASSERT(dbgs.begin() == begin);
2792 
2793     if (p->dbg->trackingAllocationSites &&
2794         !p->dbg->appendAllocationSite(cx, obj, frame, when)) {
2795       return false;
2796     }
2797   }
2798 
2799   return true;
2800 }
2801 
isDebuggeeUnbarriered(const Realm * realm) const2802 bool Debugger::isDebuggeeUnbarriered(const Realm* realm) const {
2803   MOZ_ASSERT(realm);
2804   return realm->isDebuggee() &&
2805          debuggees.has(realm->unsafeUnbarrieredMaybeGlobal());
2806 }
2807 
appendAllocationSite(JSContext * cx,HandleObject obj,HandleSavedFrame frame,mozilla::TimeStamp when)2808 bool Debugger::appendAllocationSite(JSContext* cx, HandleObject obj,
2809                                     HandleSavedFrame frame,
2810                                     mozilla::TimeStamp when) {
2811   MOZ_ASSERT(trackingAllocationSites);
2812 
2813   AutoRealm ar(cx, object);
2814   RootedObject wrappedFrame(cx, frame);
2815   if (!cx->compartment()->wrap(cx, &wrappedFrame)) {
2816     return false;
2817   }
2818 
2819   // Try to get the constructor name from the ObjectGroup's TypeNewScript.
2820   // This is only relevant for native objects.
2821   RootedAtom ctorName(cx);
2822   if (obj->is<NativeObject>()) {
2823     AutoRealm ar(cx, obj);
2824     if (!JSObject::constructorDisplayAtom(cx, obj, &ctorName)) {
2825       return false;
2826     }
2827   }
2828   if (ctorName) {
2829     cx->markAtom(ctorName);
2830   }
2831 
2832   auto className = obj->getClass()->name;
2833   auto size =
2834       JS::ubi::Node(obj.get()).size(cx->runtime()->debuggerMallocSizeOf);
2835   auto inNursery = gc::IsInsideNursery(obj);
2836 
2837   if (!allocationsLog.emplaceBack(wrappedFrame, when, className, ctorName, size,
2838                                   inNursery)) {
2839     ReportOutOfMemory(cx);
2840     return false;
2841   }
2842 
2843   if (allocationsLog.length() > maxAllocationsLogLength) {
2844     allocationsLog.popFront();
2845     MOZ_ASSERT(allocationsLog.length() == maxAllocationsLogLength);
2846     allocationsLogOverflowed = true;
2847   }
2848 
2849   return true;
2850 }
2851 
firePromiseHook(JSContext * cx,Hook hook,HandleObject promise)2852 bool Debugger::firePromiseHook(JSContext* cx, Hook hook, HandleObject promise) {
2853   MOZ_ASSERT(hook == OnNewPromise || hook == OnPromiseSettled);
2854 
2855   RootedObject hookObj(cx, getHook(hook));
2856   MOZ_ASSERT(hookObj);
2857   MOZ_ASSERT(hookObj->isCallable());
2858 
2859   RootedValue dbgObj(cx, ObjectValue(*promise));
2860   if (!wrapDebuggeeValue(cx, &dbgObj)) {
2861     return false;
2862   }
2863 
2864   // Like onNewGlobalObject, the Promise hooks are infallible and the comments
2865   // in |Debugger::fireNewGlobalObject| apply here as well.
2866   RootedValue fval(cx, ObjectValue(*hookObj));
2867   RootedValue rv(cx);
2868   bool ok = js::Call(cx, fval, object, dbgObj, &rv);
2869   if (ok && !rv.isUndefined()) {
2870     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2871                               JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED);
2872     ok = false;
2873   }
2874 
2875   return ok || handleUncaughtException(cx);
2876 }
2877 
2878 /* static */
slowPathPromiseHook(JSContext * cx,Hook hook,Handle<PromiseObject * > promise)2879 void Debugger::slowPathPromiseHook(JSContext* cx, Hook hook,
2880                                    Handle<PromiseObject*> promise) {
2881   MOZ_ASSERT(hook == OnNewPromise || hook == OnPromiseSettled);
2882 
2883   if (hook == OnPromiseSettled) {
2884     // We should be in the right compartment, but for simplicity always enter
2885     // the promise's realm below.
2886     cx->check(promise);
2887   }
2888 
2889   AutoRealm ar(cx, promise);
2890 
2891   Debugger::dispatchQuietHook(
2892       cx, [hook](Debugger* dbg) -> bool { return dbg->getHook(hook); },
2893       [&](Debugger* dbg) -> bool {
2894         return dbg->firePromiseHook(cx, hook, promise);
2895       });
2896 }
2897 
2898 /* static */
slowPathOnNewPromise(JSContext * cx,Handle<PromiseObject * > promise)2899 void DebugAPI::slowPathOnNewPromise(JSContext* cx,
2900                                     Handle<PromiseObject*> promise) {
2901   Debugger::slowPathPromiseHook(cx, Debugger::OnNewPromise, promise);
2902 }
2903 
2904 /* static */
slowPathOnPromiseSettled(JSContext * cx,Handle<PromiseObject * > promise)2905 void DebugAPI::slowPathOnPromiseSettled(JSContext* cx,
2906                                         Handle<PromiseObject*> promise) {
2907   Debugger::slowPathPromiseHook(cx, Debugger::OnPromiseSettled, promise);
2908 }
2909 
2910 /*** Debugger code invalidation for observing execution *********************/
2911 
2912 class MOZ_RAII ExecutionObservableRealms
2913     : public DebugAPI::ExecutionObservableSet {
2914   HashSet<Realm*> realms_;
2915   HashSet<Zone*> zones_;
2916 
2917  public:
ExecutionObservableRealms(JSContext * cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)2918   explicit ExecutionObservableRealms(
2919       JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
2920       : realms_(cx), zones_(cx) {
2921     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
2922   }
2923 
add(Realm * realm)2924   bool add(Realm* realm) {
2925     return realms_.put(realm) && zones_.put(realm->zone());
2926   }
2927 
2928   using RealmRange = HashSet<Realm*>::Range;
realms() const2929   const HashSet<Realm*>* realms() const { return &realms_; }
2930 
zones() const2931   const HashSet<Zone*>* zones() const override { return &zones_; }
shouldRecompileOrInvalidate(JSScript * script) const2932   bool shouldRecompileOrInvalidate(JSScript* script) const override {
2933     return script->hasBaselineScript() && realms_.has(script->realm());
2934   }
shouldMarkAsDebuggee(FrameIter & iter) const2935   bool shouldMarkAsDebuggee(FrameIter& iter) const override {
2936     // AbstractFramePtr can't refer to non-remateralized Ion frames or
2937     // non-debuggee wasm frames, so if iter refers to one such, we know we
2938     // don't match.
2939     return iter.hasUsableAbstractFramePtr() && realms_.has(iter.realm());
2940   }
2941 
2942   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
2943 };
2944 
2945 // Given a particular AbstractFramePtr F that has become observable, this
2946 // represents the stack frames that need to be bailed out or marked as
2947 // debuggees, and the scripts that need to be recompiled, taking inlining into
2948 // account.
2949 class MOZ_RAII ExecutionObservableFrame
2950     : public DebugAPI::ExecutionObservableSet {
2951   AbstractFramePtr frame_;
2952 
2953  public:
ExecutionObservableFrame(AbstractFramePtr frame MOZ_GUARD_OBJECT_NOTIFIER_PARAM)2954   explicit ExecutionObservableFrame(
2955       AbstractFramePtr frame MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
2956       : frame_(frame) {
2957     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
2958   }
2959 
singleZone() const2960   Zone* singleZone() const override {
2961     // We never inline across realms, let alone across zones, so
2962     // frames_'s script's zone is the only one of interest.
2963     return frame_.script()->zone();
2964   }
2965 
singleScriptForZoneInvalidation() const2966   JSScript* singleScriptForZoneInvalidation() const override {
2967     MOZ_CRASH(
2968         "ExecutionObservableFrame shouldn't need zone-wide invalidation.");
2969     return nullptr;
2970   }
2971 
shouldRecompileOrInvalidate(JSScript * script) const2972   bool shouldRecompileOrInvalidate(JSScript* script) const override {
2973     // Normally, *this represents exactly one script: the one frame_ is
2974     // running.
2975     //
2976     // However, debug-mode OSR uses *this for both invalidating Ion frames,
2977     // and recompiling the Baseline scripts that those Ion frames will bail
2978     // out into. Suppose frame_ is an inline frame, executing a copy of its
2979     // JSScript, S_inner, that has been inlined into the IonScript of some
2980     // other JSScript, S_outer. We must match S_outer, to decide which Ion
2981     // frame to invalidate; and we must match S_inner, to decide which
2982     // Baseline script to recompile.
2983     //
2984     // Note that this does not, by design, invalidate *all* inliners of
2985     // frame_.script(), as only frame_ is made observable, not
2986     // frame_.script().
2987     if (!script->hasBaselineScript()) {
2988       return false;
2989     }
2990 
2991     if (frame_.hasScript() && script == frame_.script()) {
2992       return true;
2993     }
2994 
2995     return frame_.isRematerializedFrame() &&
2996            script == frame_.asRematerializedFrame()->outerScript();
2997   }
2998 
shouldMarkAsDebuggee(FrameIter & iter) const2999   bool shouldMarkAsDebuggee(FrameIter& iter) const override {
3000     // AbstractFramePtr can't refer to non-remateralized Ion frames or
3001     // non-debuggee wasm frames, so if iter refers to one such, we know we
3002     // don't match.
3003     //
3004     // We never use this 'has' overload for frame invalidation, only for
3005     // frame debuggee marking; so this overload doesn't need a parallel to
3006     // the just-so inlining logic above.
3007     return iter.hasUsableAbstractFramePtr() &&
3008            iter.abstractFramePtr() == frame_;
3009   }
3010 
3011   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
3012 };
3013 
3014 class MOZ_RAII ExecutionObservableScript
3015     : public DebugAPI::ExecutionObservableSet {
3016   RootedScript script_;
3017 
3018  public:
ExecutionObservableScript(JSContext * cx,JSScript * script MOZ_GUARD_OBJECT_NOTIFIER_PARAM)3019   ExecutionObservableScript(JSContext* cx,
3020                             JSScript* script MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
3021       : script_(cx, script) {
3022     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
3023   }
3024 
singleZone() const3025   Zone* singleZone() const override { return script_->zone(); }
singleScriptForZoneInvalidation() const3026   JSScript* singleScriptForZoneInvalidation() const override { return script_; }
shouldRecompileOrInvalidate(JSScript * script) const3027   bool shouldRecompileOrInvalidate(JSScript* script) const override {
3028     return script->hasBaselineScript() && script == script_;
3029   }
shouldMarkAsDebuggee(FrameIter & iter) const3030   bool shouldMarkAsDebuggee(FrameIter& iter) const override {
3031     // AbstractFramePtr can't refer to non-remateralized Ion frames, and
3032     // while a non-rematerialized Ion frame may indeed be running script_,
3033     // we cannot mark them as debuggees until they bail out.
3034     //
3035     // Upon bailing out, any newly constructed Baseline frames that came
3036     // from Ion frames with scripts that are isDebuggee() is marked as
3037     // debuggee. This is correct in that the only other way a frame may be
3038     // marked as debuggee is via Debugger.Frame reflection, which would
3039     // have rematerialized any Ion frames.
3040     //
3041     // Also AbstractFramePtr can't refer to non-debuggee wasm frames, so if
3042     // iter refers to one such, we know we don't match.
3043     return iter.hasUsableAbstractFramePtr() && !iter.isWasm() &&
3044            iter.abstractFramePtr().script() == script_;
3045   }
3046 
3047   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
3048 };
3049 
3050 /* static */
updateExecutionObservabilityOfFrames(JSContext * cx,const DebugAPI::ExecutionObservableSet & obs,IsObserving observing)3051 bool Debugger::updateExecutionObservabilityOfFrames(
3052     JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
3053     IsObserving observing) {
3054   AutoSuppressProfilerSampling suppressProfilerSampling(cx);
3055 
3056   {
3057     jit::JitContext jctx(cx, nullptr);
3058     if (!jit::RecompileOnStackBaselineScriptsForDebugMode(cx, obs, observing)) {
3059       ReportOutOfMemory(cx);
3060       return false;
3061     }
3062   }
3063 
3064   AbstractFramePtr oldestEnabledFrame;
3065   for (AllFramesIter iter(cx); !iter.done(); ++iter) {
3066     if (obs.shouldMarkAsDebuggee(iter)) {
3067       if (observing) {
3068         if (!iter.abstractFramePtr().isDebuggee()) {
3069           oldestEnabledFrame = iter.abstractFramePtr();
3070           oldestEnabledFrame.setIsDebuggee();
3071         }
3072         if (iter.abstractFramePtr().isWasmDebugFrame()) {
3073           iter.abstractFramePtr().asWasmDebugFrame()->observe(cx);
3074         }
3075       } else {
3076 #ifdef DEBUG
3077         // Debugger.Frame lifetimes are managed by the debug epilogue,
3078         // so in general it's unsafe to unmark a frame if it has a
3079         // Debugger.Frame associated with it.
3080         MOZ_ASSERT(!DebugAPI::inFrameMaps(iter.abstractFramePtr()));
3081 #endif
3082         iter.abstractFramePtr().unsetIsDebuggee();
3083       }
3084     }
3085   }
3086 
3087   // See comment in unsetPrevUpToDateUntil.
3088   if (oldestEnabledFrame) {
3089     AutoRealm ar(cx, oldestEnabledFrame.environmentChain());
3090     DebugEnvironments::unsetPrevUpToDateUntil(cx, oldestEnabledFrame);
3091   }
3092 
3093   return true;
3094 }
3095 
MarkJitScriptActiveIfObservable(JSScript * script,const DebugAPI::ExecutionObservableSet & obs)3096 static inline void MarkJitScriptActiveIfObservable(
3097     JSScript* script, const DebugAPI::ExecutionObservableSet& obs) {
3098   if (obs.shouldRecompileOrInvalidate(script)) {
3099     script->jitScript()->setActive();
3100   }
3101 }
3102 
AppendAndInvalidateScript(JSContext * cx,Zone * zone,JSScript * script,Vector<JSScript * > & scripts)3103 static bool AppendAndInvalidateScript(JSContext* cx, Zone* zone,
3104                                       JSScript* script,
3105                                       Vector<JSScript*>& scripts) {
3106   // Enter the script's realm as addPendingRecompile attempts to
3107   // cancel off-thread compilations, whose books are kept on the
3108   // script's realm.
3109   MOZ_ASSERT(script->zone() == zone);
3110   AutoRealm ar(cx, script);
3111   zone->types.addPendingRecompile(cx, script);
3112   return scripts.append(script);
3113 }
3114 
UpdateExecutionObservabilityOfScriptsInZone(JSContext * cx,Zone * zone,const DebugAPI::ExecutionObservableSet & obs,Debugger::IsObserving observing)3115 static bool UpdateExecutionObservabilityOfScriptsInZone(
3116     JSContext* cx, Zone* zone, const DebugAPI::ExecutionObservableSet& obs,
3117     Debugger::IsObserving observing) {
3118   using namespace js::jit;
3119 
3120   AutoSuppressProfilerSampling suppressProfilerSampling(cx);
3121 
3122   JSFreeOp* fop = cx->runtime()->defaultFreeOp();
3123 
3124   Vector<JSScript*> scripts(cx);
3125 
3126   // Iterate through observable scripts, invalidating their Ion scripts and
3127   // appending them to a vector for discarding their baseline scripts later.
3128   {
3129     AutoEnterAnalysis enter(fop, zone);
3130     if (JSScript* script = obs.singleScriptForZoneInvalidation()) {
3131       if (obs.shouldRecompileOrInvalidate(script)) {
3132         if (!AppendAndInvalidateScript(cx, zone, script, scripts)) {
3133           return false;
3134         }
3135       }
3136     } else {
3137       for (auto base = zone->cellIter<BaseScript>(); !base.done();
3138            base.next()) {
3139         if (!base->hasJitScript()) {
3140           continue;
3141         }
3142         JSScript* script = base->asJSScript();
3143         if (obs.shouldRecompileOrInvalidate(script)) {
3144           if (!AppendAndInvalidateScript(cx, zone, script, scripts)) {
3145             return false;
3146           }
3147         }
3148       }
3149     }
3150   }
3151 
3152   // Code below this point must be infallible to ensure the active bit of
3153   // BaselineScripts is in a consistent state.
3154   //
3155   // Mark active baseline scripts in the observable set so that they don't
3156   // get discarded. They will be recompiled.
3157   for (JitActivationIterator actIter(cx); !actIter.done(); ++actIter) {
3158     if (actIter->compartment()->zone() != zone) {
3159       continue;
3160     }
3161 
3162     for (OnlyJSJitFrameIter iter(actIter); !iter.done(); ++iter) {
3163       const JSJitFrameIter& frame = iter.frame();
3164       switch (frame.type()) {
3165         case FrameType::BaselineJS:
3166           MarkJitScriptActiveIfObservable(frame.script(), obs);
3167           break;
3168         case FrameType::IonJS:
3169           MarkJitScriptActiveIfObservable(frame.script(), obs);
3170           for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more();
3171                ++inlineIter) {
3172             MarkJitScriptActiveIfObservable(inlineIter.script(), obs);
3173           }
3174           break;
3175         default:;
3176       }
3177     }
3178   }
3179 
3180   // Iterate through the scripts again and finish discarding
3181   // BaselineScripts. This must be done as a separate phase as we can only
3182   // discard the BaselineScript on scripts that have no IonScript.
3183   for (size_t i = 0; i < scripts.length(); i++) {
3184     MOZ_ASSERT_IF(scripts[i]->isDebuggee(), observing);
3185     if (!scripts[i]->jitScript()->active()) {
3186       FinishDiscardBaselineScript(fop, scripts[i]);
3187     }
3188     scripts[i]->jitScript()->resetActive();
3189   }
3190 
3191   // Iterate through all wasm instances to find ones that need to be updated.
3192   for (RealmsInZoneIter r(zone); !r.done(); r.next()) {
3193     for (wasm::Instance* instance : r->wasm.instances()) {
3194       if (!instance->debugEnabled()) {
3195         continue;
3196       }
3197 
3198       bool enableTrap = observing == Debugger::Observing;
3199       instance->debug().ensureEnterFrameTrapsState(cx, enableTrap);
3200     }
3201   }
3202 
3203   return true;
3204 }
3205 
3206 /* static */
updateExecutionObservabilityOfScripts(JSContext * cx,const DebugAPI::ExecutionObservableSet & obs,IsObserving observing)3207 bool Debugger::updateExecutionObservabilityOfScripts(
3208     JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
3209     IsObserving observing) {
3210   if (Zone* zone = obs.singleZone()) {
3211     return UpdateExecutionObservabilityOfScriptsInZone(cx, zone, obs,
3212                                                        observing);
3213   }
3214 
3215   using ZoneRange = DebugAPI::ExecutionObservableSet::ZoneRange;
3216   for (ZoneRange r = obs.zones()->all(); !r.empty(); r.popFront()) {
3217     if (!UpdateExecutionObservabilityOfScriptsInZone(cx, r.front(), obs,
3218                                                      observing)) {
3219       return false;
3220     }
3221   }
3222 
3223   return true;
3224 }
3225 
3226 template <typename FrameFn>
3227 /* static */
forEachDebuggerFrame(AbstractFramePtr frame,FrameFn fn)3228 void Debugger::forEachDebuggerFrame(AbstractFramePtr frame, FrameFn fn) {
3229   for (Realm::DebuggerVectorEntry& entry : frame.global()->getDebuggers()) {
3230     Debugger* dbg = entry.dbg;
3231     if (FrameMap::Ptr frameEntry = dbg->frames.lookup(frame)) {
3232       fn(frameEntry->value());
3233     }
3234   }
3235 }
3236 
3237 /* static */
getDebuggerFrames(AbstractFramePtr frame,MutableHandle<DebuggerFrameVector> frames)3238 bool Debugger::getDebuggerFrames(AbstractFramePtr frame,
3239                                  MutableHandle<DebuggerFrameVector> frames) {
3240   bool hadOOM = false;
3241   forEachDebuggerFrame(frame, [&](DebuggerFrame* frameobj) {
3242     if (!hadOOM && !frames.append(frameobj)) {
3243       hadOOM = true;
3244     }
3245   });
3246   return !hadOOM;
3247 }
3248 
3249 /* static */
updateExecutionObservability(JSContext * cx,DebugAPI::ExecutionObservableSet & obs,IsObserving observing)3250 bool Debugger::updateExecutionObservability(
3251     JSContext* cx, DebugAPI::ExecutionObservableSet& obs,
3252     IsObserving observing) {
3253   if (!obs.singleZone() && obs.zones()->empty()) {
3254     return true;
3255   }
3256 
3257   // Invalidate scripts first so we can set the needsArgsObj flag on scripts
3258   // before patching frames.
3259   return updateExecutionObservabilityOfScripts(cx, obs, observing) &&
3260          updateExecutionObservabilityOfFrames(cx, obs, observing);
3261 }
3262 
3263 /* static */
ensureExecutionObservabilityOfScript(JSContext * cx,JSScript * script)3264 bool Debugger::ensureExecutionObservabilityOfScript(JSContext* cx,
3265                                                     JSScript* script) {
3266   if (script->isDebuggee()) {
3267     return true;
3268   }
3269   ExecutionObservableScript obs(cx, script);
3270   return updateExecutionObservability(cx, obs, Observing);
3271 }
3272 
3273 /* static */
ensureExecutionObservabilityOfOsrFrame(JSContext * cx,AbstractFramePtr osrSourceFrame)3274 bool DebugAPI::ensureExecutionObservabilityOfOsrFrame(
3275     JSContext* cx, AbstractFramePtr osrSourceFrame) {
3276   MOZ_ASSERT(osrSourceFrame.isDebuggee());
3277   if (osrSourceFrame.script()->hasBaselineScript() &&
3278       osrSourceFrame.script()->baselineScript()->hasDebugInstrumentation()) {
3279     return true;
3280   }
3281   ExecutionObservableFrame obs(osrSourceFrame);
3282   return Debugger::updateExecutionObservabilityOfFrames(cx, obs, Observing);
3283 }
3284 
3285 /* static */
ensureExecutionObservabilityOfFrame(JSContext * cx,AbstractFramePtr frame)3286 bool Debugger::ensureExecutionObservabilityOfFrame(JSContext* cx,
3287                                                    AbstractFramePtr frame) {
3288   MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(),
3289                 frame.isDebuggee());
3290   MOZ_ASSERT_IF(frame.isWasmDebugFrame(), frame.wasmInstance()->debugEnabled());
3291   if (frame.isDebuggee()) {
3292     return true;
3293   }
3294   ExecutionObservableFrame obs(frame);
3295   return updateExecutionObservabilityOfFrames(cx, obs, Observing);
3296 }
3297 
3298 /* static */
ensureExecutionObservabilityOfRealm(JSContext * cx,Realm * realm)3299 bool Debugger::ensureExecutionObservabilityOfRealm(JSContext* cx,
3300                                                    Realm* realm) {
3301   if (realm->debuggerObservesAllExecution()) {
3302     return true;
3303   }
3304   ExecutionObservableRealms obs(cx);
3305   if (!obs.add(realm)) {
3306     return false;
3307   }
3308   realm->updateDebuggerObservesAllExecution();
3309   return updateExecutionObservability(cx, obs, Observing);
3310 }
3311 
3312 /* static */
hookObservesAllExecution(Hook which)3313 bool Debugger::hookObservesAllExecution(Hook which) {
3314   return which == OnEnterFrame;
3315 }
3316 
observesAllExecution() const3317 Debugger::IsObserving Debugger::observesAllExecution() const {
3318   if (!!getHook(OnEnterFrame)) {
3319     return Observing;
3320   }
3321   return NotObserving;
3322 }
3323 
observesAsmJS() const3324 Debugger::IsObserving Debugger::observesAsmJS() const {
3325   if (!allowUnobservedAsmJS) {
3326     return Observing;
3327   }
3328   return NotObserving;
3329 }
3330 
observesCoverage() const3331 Debugger::IsObserving Debugger::observesCoverage() const {
3332   if (collectCoverageInfo) {
3333     return Observing;
3334   }
3335   return NotObserving;
3336 }
3337 
observesNativeCalls() const3338 Debugger::IsObserving Debugger::observesNativeCalls() const {
3339   if (getHook(Debugger::OnNativeCall)) {
3340     return Observing;
3341   }
3342   return NotObserving;
3343 }
3344 
3345 // Toggle whether this Debugger's debuggees observe all execution. This is
3346 // called when a hook that observes all execution is set or unset. See
3347 // hookObservesAllExecution.
updateObservesAllExecutionOnDebuggees(JSContext * cx,IsObserving observing)3348 bool Debugger::updateObservesAllExecutionOnDebuggees(JSContext* cx,
3349                                                      IsObserving observing) {
3350   ExecutionObservableRealms obs(cx);
3351 
3352   for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
3353        r.popFront()) {
3354     GlobalObject* global = r.front();
3355     JS::Realm* realm = global->realm();
3356 
3357     if (realm->debuggerObservesAllExecution() == observing) {
3358       continue;
3359     }
3360 
3361     // It's expensive to eagerly invalidate and recompile a realm,
3362     // so add the realm to the set only if we are observing.
3363     if (observing && !obs.add(realm)) {
3364       return false;
3365     }
3366   }
3367 
3368   if (!updateExecutionObservability(cx, obs, observing)) {
3369     return false;
3370   }
3371 
3372   using RealmRange = ExecutionObservableRealms::RealmRange;
3373   for (RealmRange r = obs.realms()->all(); !r.empty(); r.popFront()) {
3374     r.front()->updateDebuggerObservesAllExecution();
3375   }
3376 
3377   return true;
3378 }
3379 
updateObservesCoverageOnDebuggees(JSContext * cx,IsObserving observing)3380 bool Debugger::updateObservesCoverageOnDebuggees(JSContext* cx,
3381                                                  IsObserving observing) {
3382   ExecutionObservableRealms obs(cx);
3383 
3384   for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
3385        r.popFront()) {
3386     GlobalObject* global = r.front();
3387     Realm* realm = global->realm();
3388 
3389     if (realm->debuggerObservesCoverage() == observing) {
3390       continue;
3391     }
3392 
3393     // Invalidate and recompile a realm to add or remove PCCounts
3394     // increments. We have to eagerly invalidate, as otherwise we might have
3395     // dangling pointers to freed PCCounts.
3396     if (!obs.add(realm)) {
3397       return false;
3398     }
3399   }
3400 
3401   // If any frame on the stack belongs to the debuggee, then we cannot update
3402   // the ScriptCounts, because this would imply to invalidate a Debugger.Frame
3403   // to recompile it with/without ScriptCount support.
3404   for (FrameIter iter(cx); !iter.done(); ++iter) {
3405     if (obs.shouldMarkAsDebuggee(iter)) {
3406       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3407                                 JSMSG_DEBUG_NOT_IDLE);
3408       return false;
3409     }
3410   }
3411 
3412   if (!updateExecutionObservability(cx, obs, observing)) {
3413     return false;
3414   }
3415 
3416   // All realms can safely be toggled, and all scripts will be recompiled.
3417   // Thus we can update each realm accordingly.
3418   using RealmRange = ExecutionObservableRealms::RealmRange;
3419   for (RealmRange r = obs.realms()->all(); !r.empty(); r.popFront()) {
3420     r.front()->updateDebuggerObservesCoverage();
3421   }
3422 
3423   return true;
3424 }
3425 
updateObservesAsmJSOnDebuggees(IsObserving observing)3426 void Debugger::updateObservesAsmJSOnDebuggees(IsObserving observing) {
3427   for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
3428        r.popFront()) {
3429     GlobalObject* global = r.front();
3430     Realm* realm = global->realm();
3431 
3432     if (realm->debuggerObservesAsmJS() == observing) {
3433       continue;
3434     }
3435 
3436     realm->updateDebuggerObservesAsmJS();
3437   }
3438 }
3439 
3440 /*** Allocations Tracking ***************************************************/
3441 
3442 /* static */
cannotTrackAllocations(const GlobalObject & global)3443 bool Debugger::cannotTrackAllocations(const GlobalObject& global) {
3444   auto existingCallback = global.realm()->getAllocationMetadataBuilder();
3445   return existingCallback && existingCallback != &SavedStacks::metadataBuilder;
3446 }
3447 
3448 /* static */
isObservedByDebuggerTrackingAllocations(const GlobalObject & debuggee)3449 bool DebugAPI::isObservedByDebuggerTrackingAllocations(
3450     const GlobalObject& debuggee) {
3451   for (Realm::DebuggerVectorEntry& entry : debuggee.getDebuggers()) {
3452     // Use unbarrieredGet() to prevent triggering read barrier while
3453     // collecting, this is safe as long as dbg does not escape.
3454     Debugger* dbg = entry.dbg.unbarrieredGet();
3455     if (dbg->trackingAllocationSites) {
3456       return true;
3457     }
3458   }
3459 
3460   return false;
3461 }
3462 
3463 /* static */
addAllocationsTracking(JSContext * cx,Handle<GlobalObject * > debuggee)3464 bool Debugger::addAllocationsTracking(JSContext* cx,
3465                                       Handle<GlobalObject*> debuggee) {
3466   // Precondition: the given global object is being observed by at least one
3467   // Debugger that is tracking allocations.
3468   MOZ_ASSERT(DebugAPI::isObservedByDebuggerTrackingAllocations(*debuggee));
3469 
3470   if (Debugger::cannotTrackAllocations(*debuggee)) {
3471     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3472                               JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET);
3473     return false;
3474   }
3475 
3476   debuggee->realm()->setAllocationMetadataBuilder(
3477       &SavedStacks::metadataBuilder);
3478   debuggee->realm()->chooseAllocationSamplingProbability();
3479   return true;
3480 }
3481 
3482 /* static */
removeAllocationsTracking(GlobalObject & global)3483 void Debugger::removeAllocationsTracking(GlobalObject& global) {
3484   // If there are still Debuggers that are observing allocations, we cannot
3485   // remove the metadata callback yet. Recompute the sampling probability
3486   // based on the remaining debuggers' needs.
3487   if (DebugAPI::isObservedByDebuggerTrackingAllocations(global)) {
3488     global.realm()->chooseAllocationSamplingProbability();
3489     return;
3490   }
3491 
3492   if (!global.realm()->runtimeFromMainThread()->recordAllocationCallback) {
3493     // Something like the Gecko Profiler could request from the the JS runtime
3494     // to record allocations. If it is recording allocations, then do not
3495     // destroy the allocation metadata builder at this time.
3496     global.realm()->forgetAllocationMetadataBuilder();
3497   }
3498 }
3499 
addAllocationsTrackingForAllDebuggees(JSContext * cx)3500 bool Debugger::addAllocationsTrackingForAllDebuggees(JSContext* cx) {
3501   MOZ_ASSERT(trackingAllocationSites);
3502 
3503   // We don't want to end up in a state where we added allocations
3504   // tracking to some of our debuggees, but failed to do so for
3505   // others. Before attempting to start tracking allocations in *any* of
3506   // our debuggees, ensure that we will be able to track allocations for
3507   // *all* of our debuggees.
3508   for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
3509        r.popFront()) {
3510     if (Debugger::cannotTrackAllocations(*r.front().get())) {
3511       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3512                                 JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET);
3513       return false;
3514     }
3515   }
3516 
3517   Rooted<GlobalObject*> g(cx);
3518   for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
3519        r.popFront()) {
3520     // This should always succeed, since we already checked for the
3521     // error case above.
3522     g = r.front().get();
3523     MOZ_ALWAYS_TRUE(Debugger::addAllocationsTracking(cx, g));
3524   }
3525 
3526   return true;
3527 }
3528 
removeAllocationsTrackingForAllDebuggees()3529 void Debugger::removeAllocationsTrackingForAllDebuggees() {
3530   for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty();
3531        r.popFront()) {
3532     Debugger::removeAllocationsTracking(*r.front().get());
3533   }
3534 
3535   allocationsLog.clear();
3536 }
3537 
3538 /*** Debugger JSObjects *****************************************************/
3539 
3540 template <typename F>
forEachWeakMap(const F & f)3541 inline void Debugger::forEachWeakMap(const F& f) {
3542   f(generatorFrames);
3543   f(objects);
3544   f(environments);
3545   f(scripts);
3546   f(sources);
3547   f(wasmInstanceScripts);
3548   f(wasmInstanceSources);
3549 }
3550 
traceCrossCompartmentEdges(JSTracer * trc)3551 void Debugger::traceCrossCompartmentEdges(JSTracer* trc) {
3552   forEachWeakMap(
3553       [trc](auto& weakMap) { weakMap.traceCrossCompartmentEdges(trc); });
3554 }
3555 
3556 /*
3557  * Ordinarily, WeakMap keys and values are marked because at some point it was
3558  * discovered that the WeakMap was live; that is, some object containing the
3559  * WeakMap was marked during mark phase.
3560  *
3561  * However, during zone GC, we have to do something about cross-compartment
3562  * edges in non-GC'd compartments. Since the source may be live, we
3563  * conservatively assume it is and mark the edge.
3564  *
3565  * Each Debugger object keeps five cross-compartment WeakMaps: objects, scripts,
3566  * lazy scripts, script source objects, and environments. They have the property
3567  * that all their values are in the same compartment as the Debugger object,
3568  * but we have to mark the keys and the private pointer in the wrapper object.
3569  *
3570  * We must scan all Debugger objects regardless of whether they *currently* have
3571  * any debuggees in a compartment being GC'd, because the WeakMap entries
3572  * persist even when debuggees are removed.
3573  *
3574  * This happens during the initial mark phase, not iterative marking, because
3575  * all the edges being reported here are strong references.
3576  *
3577  * This method is also used during compacting GC to update cross compartment
3578  * pointers into zones that are being compacted.
3579  */
3580 /* static */
traceCrossCompartmentEdges(JSTracer * trc)3581 void DebugAPI::traceCrossCompartmentEdges(JSTracer* trc) {
3582   JSRuntime* rt = trc->runtime();
3583   gc::State state = rt->gc.state();
3584 
3585   for (Debugger* dbg : rt->debuggerList()) {
3586     Zone* zone = MaybeForwarded(dbg->object.get())->zone();
3587     if (!zone->isCollecting() || state == gc::State::Compact) {
3588       dbg->traceCrossCompartmentEdges(trc);
3589     }
3590   }
3591 }
3592 
3593 #ifdef DEBUG
3594 
RuntimeHasDebugger(JSRuntime * rt,Debugger * dbg)3595 static bool RuntimeHasDebugger(JSRuntime* rt, Debugger* dbg) {
3596   for (Debugger* d : rt->debuggerList()) {
3597     if (d == dbg) {
3598       return true;
3599     }
3600   }
3601   return false;
3602 }
3603 
3604 /* static */
edgeIsInDebuggerWeakmap(JSRuntime * rt,JSObject * src,JS::GCCellPtr dst)3605 bool DebugAPI::edgeIsInDebuggerWeakmap(JSRuntime* rt, JSObject* src,
3606                                        JS::GCCellPtr dst) {
3607   if (!Debugger::isChildJSObject(src)) {
3608     return false;
3609   }
3610 
3611   Debugger* dbg = Debugger::fromChildJSObject(src);
3612   MOZ_ASSERT(RuntimeHasDebugger(rt, dbg));
3613 
3614   if (src->is<DebuggerFrame>()) {
3615     if (dst.is<BaseScript>()) {
3616       // The generatorFrames map is not keyed on the associated JSScript. Get
3617       // the key from the source object and check everything matches.
3618       DebuggerFrame* frame = &src->as<DebuggerFrame>();
3619       AbstractGeneratorObject* genObj = &frame->unwrappedGenerator();
3620       return frame->generatorScript() == &dst.as<BaseScript>() &&
3621              dbg->generatorFrames.hasEntry(genObj, src);
3622     }
3623     return dst.is<JSObject>() &&
3624            dst.as<JSObject>().is<AbstractGeneratorObject>() &&
3625            dbg->generatorFrames.hasEntry(
3626                &dst.as<JSObject>().as<AbstractGeneratorObject>(), src);
3627   }
3628   if (src->is<DebuggerObject>()) {
3629     return dst.is<JSObject>() &&
3630            dbg->objects.hasEntry(&dst.as<JSObject>(), src);
3631   }
3632   if (src->is<DebuggerEnvironment>()) {
3633     return dst.is<JSObject>() &&
3634            dbg->environments.hasEntry(&dst.as<JSObject>(), src);
3635   }
3636   if (src->is<DebuggerScript>()) {
3637     return src->as<DebuggerScript>().getReferent().match(
3638         [=](BaseScript* script) {
3639           return dst.is<BaseScript>() && script == &dst.as<BaseScript>() &&
3640                  dbg->scripts.hasEntry(script, src);
3641         },
3642         [=](WasmInstanceObject* instance) {
3643           return dst.is<JSObject>() && instance == &dst.as<JSObject>() &&
3644                  dbg->wasmInstanceScripts.hasEntry(instance, src);
3645         });
3646   }
3647   if (src->is<DebuggerSource>()) {
3648     return src->as<DebuggerSource>().getReferent().match(
3649         [=](ScriptSourceObject* sso) {
3650           return dst.is<JSObject>() && sso == &dst.as<JSObject>() &&
3651                  dbg->sources.hasEntry(sso, src);
3652         },
3653         [=](WasmInstanceObject* instance) {
3654           return dst.is<JSObject>() && instance == &dst.as<JSObject>() &&
3655                  dbg->wasmInstanceSources.hasEntry(instance, src);
3656         });
3657   }
3658   MOZ_ASSERT_UNREACHABLE("Unhandled cross-compartment edge");
3659 }
3660 
3661 #endif
3662 
3663 /* See comments in DebugAPI.h. */
traceFramesWithLiveHooks(JSTracer * tracer)3664 void DebugAPI::traceFramesWithLiveHooks(JSTracer* tracer) {
3665   JSRuntime* rt = tracer->runtime();
3666 
3667   // Note that we must loop over all Debuggers here, not just those known to be
3668   // reachable from JavaScript. The existence of hooks set on a Debugger.Frame
3669   // for a live stack frame makes the Debuger.Frame (and hence its Debugger)
3670   // reachable.
3671   for (Debugger* dbg : rt->debuggerList()) {
3672     // Callback tracers set their own traversal boundaries, but otherwise we're
3673     // only interested in Debugger.Frames participating in the collection.
3674     if (!dbg->zone()->isGCMarking() && !tracer->isCallbackTracer()) {
3675       continue;
3676     }
3677 
3678     for (Debugger::FrameMap::Range r = dbg->frames.all(); !r.empty();
3679          r.popFront()) {
3680       HeapPtr<DebuggerFrame*>& frameobj = r.front().value();
3681       MOZ_ASSERT(frameobj->isOnStackMaybeForwarded());
3682       if (frameobj->hasAnyHooks()) {
3683         TraceEdge(tracer, &frameobj, "Debugger.Frame with live hooks");
3684       }
3685     }
3686   }
3687 }
3688 
slowPathTraceGeneratorFrame(JSTracer * tracer,AbstractGeneratorObject * generator)3689 void DebugAPI::slowPathTraceGeneratorFrame(JSTracer* tracer,
3690                                            AbstractGeneratorObject* generator) {
3691   MOZ_ASSERT(generator->realm()->isDebuggee());
3692 
3693   // Ignore callback tracers.
3694   //
3695   // There are two kinds of callback tracers we need to bar: MovingTracers used
3696   // by compacting GC; and CompartmentCheckTracers.
3697   //
3698   // MovingTracers are used by the compacting GC to update pointers to objects
3699   // that have been moved: the MovingTracer checks each outgoing pointer to see
3700   // if it refers to a forwarding pointer, and if so, updates the pointer stored
3701   // in the object.
3702   //
3703   // Generator objects are background finalized, so the compacting GC assumes it
3704   // can update their pointers in the background as well. Since we treat
3705   // generator objects as having an owning edge to their Debugger.Frame objects,
3706   // a helper thread trying to update a generator object will end up calling
3707   // this function. However, it is verboten to do weak map lookups (e.g., in
3708   // Debugger::generatorFrames) off the main thread, since MovableCellHasher
3709   // must consult the Zone to find the key's unique id.
3710   //
3711   // Fortunately, it's not necessary for compacting GC to worry about that edge
3712   // in the first place: the edge isn't a literal pointer stored on the
3713   // generator object, it's only inferred from the realm's debuggee status and
3714   // its Debuggers' generatorFrames weak maps. Those get relocated when the
3715   // Debugger itself is visited, so compacting GC can just ignore this edge.
3716   //
3717   // CompartmentCheckTracers walk the graph and verify that all
3718   // cross-compartment edges are recorded in the cross-compartment wrapper
3719   // tables. But edges between Debugger.Foo objects and their referents are not
3720   // in the CCW tables, so a CrossCompartmentCheckTracers also calls
3721   // DebugAPI::edgeIsInDebuggerWeakmap to see if a given cross-compartment edge
3722   // is accounted for there. However, edgeIsInDebuggerWeakmap only handles
3723   // debugger -> debuggee edges, so it won't recognize the edge we're
3724   // potentially traversing here, from a generator object to its Debugger.Frame.
3725   //
3726   // But since the purpose of this function is to retrieve such edges, if they
3727   // exist, from the very tables that edgeIsInDebuggerWeakmap would consult,
3728   // we're at no risk of reporting edges that they do not cover. So we can
3729   // safely hide the edges from CompartmentCheckTracers.
3730   //
3731   // We can't quite recognize MovingTracers and CompartmentCheckTracers
3732   // precisely, but they're both callback tracers, so we just show them all the
3733   // door. This means the generator -> Debugger.Frame edge is going to be
3734   // invisible to some traversals. We'll cope with that when it's a problem.
3735   if (tracer->isCallbackTracer()) {
3736     return;
3737   }
3738 
3739   for (Realm::DebuggerVectorEntry& entry : generator->realm()->getDebuggers()) {
3740     Debugger* dbg = entry.dbg.unbarrieredGet();
3741 
3742     if (Debugger::GeneratorWeakMap::Ptr entry =
3743             dbg->generatorFrames.lookupUnbarriered(generator)) {
3744       HeapPtr<DebuggerFrame*>& frameObj = entry->value();
3745       if (frameObj->hasAnyHooks()) {
3746         // See comment above.
3747         TraceCrossCompartmentEdge(tracer, generator, &frameObj,
3748                                   "Debugger.Frame with hooks for generator");
3749       }
3750     }
3751   }
3752 }
3753 
3754 /* static */
traceAllForMovingGC(JSTracer * trc)3755 void DebugAPI::traceAllForMovingGC(JSTracer* trc) {
3756   JSRuntime* rt = trc->runtime();
3757   for (Debugger* dbg : rt->debuggerList()) {
3758     dbg->traceForMovingGC(trc);
3759   }
3760 }
3761 
3762 /*
3763  * Trace all debugger-owned GC things unconditionally. This is used during
3764  * compacting GC and in minor GC: the minor GC cannot apply the weak constraints
3765  * of the full GC because it visits only part of the heap.
3766  */
traceForMovingGC(JSTracer * trc)3767 void Debugger::traceForMovingGC(JSTracer* trc) {
3768   trace(trc);
3769 
3770   for (WeakGlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
3771     TraceEdge(trc, &e.mutableFront(), "Global Object");
3772   }
3773 }
3774 
3775 /* static */
traceObject(JSTracer * trc,JSObject * obj)3776 void Debugger::traceObject(JSTracer* trc, JSObject* obj) {
3777   if (Debugger* dbg = Debugger::fromJSObject(obj)) {
3778     dbg->trace(trc);
3779   }
3780 }
3781 
trace(JSTracer * trc)3782 void Debugger::trace(JSTracer* trc) {
3783   TraceEdge(trc, &object, "Debugger Object");
3784 
3785   TraceNullableEdge(trc, &uncaughtExceptionHook, "hooks");
3786 
3787   // Mark Debugger.Frame objects. Since the Debugger is reachable, JS could call
3788   // getNewestFrame and then walk the stack, so these are all reachable from JS.
3789   //
3790   // Note that if a Debugger.Frame has hooks set, it must be retained even if
3791   // its Debugger is unreachable, since JS could observe that its hooks did not
3792   // fire. That case is handled by DebugAPI::traceFrames.
3793   //
3794   // (We have weakly-referenced Debugger.Frame objects as well, for suspended
3795   // generator frames; these are traced via generatorFrames just below.)
3796   for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
3797     HeapPtr<DebuggerFrame*>& frameobj = r.front().value();
3798     TraceEdge(trc, &frameobj, "live Debugger.Frame");
3799     MOZ_ASSERT(frameobj->isOnStackMaybeForwarded());
3800   }
3801 
3802   allocationsLog.trace(trc);
3803 
3804   forEachWeakMap([trc](auto& weakMap) { weakMap.trace(trc); });
3805 }
3806 
3807 /* static */
traceFromRealm(JSTracer * trc,Realm * realm)3808 void DebugAPI::traceFromRealm(JSTracer* trc, Realm* realm) {
3809   for (Realm::DebuggerVectorEntry& entry : realm->getDebuggers()) {
3810     TraceEdge(trc, &entry.debuggerLink, "realm debugger");
3811   }
3812 }
3813 
3814 /* static */
sweepAll(JSFreeOp * fop)3815 void DebugAPI::sweepAll(JSFreeOp* fop) {
3816   JSRuntime* rt = fop->runtime();
3817 
3818   Debugger* next;
3819   for (Debugger* dbg = rt->debuggerList().getFirst(); dbg; dbg = next) {
3820     next = dbg->getNext();
3821 
3822     // Debugger.Frames for generator calls bump the JSScript's
3823     // generatorObserverCount, so the JIT will instrument the code to notify
3824     // Debugger when the generator is resumed. When a Debugger.Frame gets GC'd,
3825     // generatorObserverCount needs to be decremented. It's much easier to do
3826     // this when we know that all parties involved - the Debugger.Frame, the
3827     // generator object, and the JSScript - have not yet been finalized.
3828     //
3829     // Since DebugAPI::sweepAll is called after everything is marked, but before
3830     // anything has been finalized, this is the perfect place to drop the count.
3831     if (dbg->zone()->isGCSweeping()) {
3832       for (Debugger::GeneratorWeakMap::Enum e(dbg->generatorFrames); !e.empty();
3833            e.popFront()) {
3834         DebuggerFrame* frameObj = e.front().value();
3835         if (IsAboutToBeFinalizedUnbarriered(&frameObj)) {
3836           frameObj->clearGenerator(fop, dbg, &e);
3837         }
3838       }
3839     }
3840 
3841     // Detach dying debuggers and debuggees from each other. Since this
3842     // requires access to both objects it must be done before either
3843     // object is finalized.
3844     bool debuggerDying = IsAboutToBeFinalized(&dbg->object);
3845     for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty();
3846          e.popFront()) {
3847       GlobalObject* global = e.front().unbarrieredGet();
3848       if (debuggerDying || IsAboutToBeFinalizedUnbarriered(&global)) {
3849         dbg->removeDebuggeeGlobal(fop, e.front().unbarrieredGet(), &e,
3850                                   Debugger::FromSweep::Yes);
3851       }
3852     }
3853 
3854     if (debuggerDying) {
3855       fop->delete_(dbg->object, dbg, MemoryUse::Debugger);
3856     }
3857 
3858     dbg = next;
3859   }
3860 }
3861 
3862 /* static */
detachAllDebuggersFromGlobal(JSFreeOp * fop,GlobalObject * global)3863 void Debugger::detachAllDebuggersFromGlobal(JSFreeOp* fop,
3864                                             GlobalObject* global) {
3865   const Realm::DebuggerVector& debuggers = global->getDebuggers();
3866   MOZ_ASSERT(!debuggers.empty());
3867   while (!debuggers.empty()) {
3868     debuggers.back().dbg->removeDebuggeeGlobal(fop, global, nullptr,
3869                                                Debugger::FromSweep::No);
3870   }
3871 }
3872 
SweepZonesInSameGroup(Zone * a,Zone * b)3873 static inline bool SweepZonesInSameGroup(Zone* a, Zone* b) {
3874   // Ensure two zones are swept in the same sweep group by adding an edge
3875   // between them in each direction.
3876   return a->addSweepGroupEdgeTo(b) && b->addSweepGroupEdgeTo(a);
3877 }
3878 
3879 /* static */
findSweepGroupEdges(JSRuntime * rt)3880 bool DebugAPI::findSweepGroupEdges(JSRuntime* rt) {
3881   // Ensure that debuggers and their debuggees are finalized in the same group
3882   // by adding edges in both directions for debuggee zones. These are weak
3883   // references that are not in the cross compartment wrapper map.
3884 
3885   for (Debugger* dbg : rt->debuggerList()) {
3886     Zone* debuggerZone = dbg->object->zone();
3887     if (!debuggerZone->isGCMarking()) {
3888       continue;
3889     }
3890 
3891     for (auto e = dbg->debuggeeZones.all(); !e.empty(); e.popFront()) {
3892       Zone* debuggeeZone = e.front();
3893       if (!debuggeeZone->isGCMarking()) {
3894         continue;
3895       }
3896 
3897       if (!SweepZonesInSameGroup(debuggerZone, debuggeeZone)) {
3898         return false;
3899       }
3900     }
3901   }
3902 
3903   return true;
3904 }
3905 
3906 template <class UnbarrieredKey, class Wrapper, bool InvisibleKeysOk>
3907 bool DebuggerWeakMap<UnbarrieredKey, Wrapper,
findSweepGroupEdges()3908                      InvisibleKeysOk>::findSweepGroupEdges() {
3909   Zone* debuggerZone = zone();
3910   MOZ_ASSERT(debuggerZone->isGCMarking());
3911   for (Enum e(*this); !e.empty(); e.popFront()) {
3912     MOZ_ASSERT(e.front().value()->zone() == debuggerZone);
3913 
3914     Zone* keyZone = e.front().key()->zone();
3915     if (keyZone->isGCMarking() &&
3916         !SweepZonesInSameGroup(debuggerZone, keyZone)) {
3917       return false;
3918     }
3919   }
3920 
3921   return true;
3922 }
3923 
3924 const JSClassOps DebuggerInstanceObject::classOps_ = {
3925     nullptr,                // addProperty
3926     nullptr,                // delProperty
3927     nullptr,                // enumerate
3928     nullptr,                // newEnumerate
3929     nullptr,                // resolve
3930     nullptr,                // mayResolve
3931     nullptr,                // finalize
3932     nullptr,                // call
3933     nullptr,                // hasInstance
3934     nullptr,                // construct
3935     Debugger::traceObject,  // trace
3936 };
3937 
3938 const JSClass DebuggerInstanceObject::class_ = {
3939     "Debugger",
3940     JSCLASS_HAS_PRIVATE |
3941         JSCLASS_HAS_RESERVED_SLOTS(Debugger::JSSLOT_DEBUG_COUNT),
3942     &classOps_};
3943 
Debugger_fromThisValue(JSContext * cx,const CallArgs & args,const char * fnname)3944 static Debugger* Debugger_fromThisValue(JSContext* cx, const CallArgs& args,
3945                                         const char* fnname) {
3946   JSObject* thisobj = RequireObject(cx, args.thisv());
3947   if (!thisobj) {
3948     return nullptr;
3949   }
3950   if (!thisobj->is<DebuggerInstanceObject>()) {
3951     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3952                               JSMSG_INCOMPATIBLE_PROTO, "Debugger", fnname,
3953                               thisobj->getClass()->name);
3954     return nullptr;
3955   }
3956 
3957   // Forbid Debugger.prototype, which is of the Debugger JSClass but isn't
3958   // really a Debugger object. The prototype object is distinguished by
3959   // having a nullptr private value.
3960   Debugger* dbg = Debugger::fromJSObject(thisobj);
3961   if (!dbg) {
3962     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3963                               JSMSG_INCOMPATIBLE_PROTO, "Debugger", fnname,
3964                               "prototype object");
3965   }
3966   return dbg;
3967 }
3968 
3969 struct MOZ_STACK_CLASS Debugger::CallData {
3970   JSContext* cx;
3971   const CallArgs& args;
3972 
3973   Debugger* dbg;
3974 
CallDataDebugger::CallData3975   CallData(JSContext* cx, const CallArgs& args, Debugger* dbg)
3976       : cx(cx), args(args), dbg(dbg) {}
3977 
3978   bool getOnDebuggerStatement();
3979   bool setOnDebuggerStatement();
3980   bool getOnExceptionUnwind();
3981   bool setOnExceptionUnwind();
3982   bool getOnNewScript();
3983   bool setOnNewScript();
3984   bool getOnEnterFrame();
3985   bool setOnEnterFrame();
3986   bool getOnNativeCall();
3987   bool setOnNativeCall();
3988   bool getOnNewGlobalObject();
3989   bool setOnNewGlobalObject();
3990   bool getOnNewPromise();
3991   bool setOnNewPromise();
3992   bool getOnPromiseSettled();
3993   bool setOnPromiseSettled();
3994   bool getUncaughtExceptionHook();
3995   bool setUncaughtExceptionHook();
3996   bool getAllowUnobservedAsmJS();
3997   bool setAllowUnobservedAsmJS();
3998   bool getCollectCoverageInfo();
3999   bool setCollectCoverageInfo();
4000   bool getMemory();
4001   bool addDebuggee();
4002   bool addAllGlobalsAsDebuggees();
4003   bool removeDebuggee();
4004   bool removeAllDebuggees();
4005   bool hasDebuggee();
4006   bool getDebuggees();
4007   bool getNewestFrame();
4008   bool clearAllBreakpoints();
4009   bool findScripts();
4010   bool findSources();
4011   bool findObjects();
4012   bool findAllGlobals();
4013   bool findSourceURLs();
4014   bool makeGlobalObjectReference();
4015   bool adoptDebuggeeValue();
4016   bool adoptFrame();
4017   bool adoptSource();
4018 
4019   using Method = bool (CallData::*)();
4020 
4021   template <Method MyMethod>
4022   static bool ToNative(JSContext* cx, unsigned argc, Value* vp);
4023 };
4024 
4025 template <Debugger::CallData::Method MyMethod>
4026 /* static */
ToNative(JSContext * cx,unsigned argc,Value * vp)4027 bool Debugger::CallData::ToNative(JSContext* cx, unsigned argc, Value* vp) {
4028   CallArgs args = CallArgsFromVp(argc, vp);
4029 
4030   Debugger* dbg = Debugger_fromThisValue(cx, args, "method");
4031   if (!dbg) {
4032     return false;
4033   }
4034 
4035   CallData data(cx, args, dbg);
4036   return (data.*MyMethod)();
4037 }
4038 
4039 /* static */
getHookImpl(JSContext * cx,const CallArgs & args,Debugger & dbg,Hook which)4040 bool Debugger::getHookImpl(JSContext* cx, const CallArgs& args, Debugger& dbg,
4041                            Hook which) {
4042   MOZ_ASSERT(which >= 0 && which < HookCount);
4043   args.rval().set(dbg.object->getReservedSlot(JSSLOT_DEBUG_HOOK_START + which));
4044   return true;
4045 }
4046 
4047 /* static */
setHookImpl(JSContext * cx,const CallArgs & args,Debugger & dbg,Hook which)4048 bool Debugger::setHookImpl(JSContext* cx, const CallArgs& args, Debugger& dbg,
4049                            Hook which) {
4050   MOZ_ASSERT(which >= 0 && which < HookCount);
4051   if (!args.requireAtLeast(cx, "Debugger.setHook", 1)) {
4052     return false;
4053   }
4054   if (args[0].isObject()) {
4055     if (!args[0].toObject().isCallable()) {
4056       return ReportIsNotFunction(cx, args[0], args.length() - 1);
4057     }
4058   } else if (!args[0].isUndefined()) {
4059     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4060                               JSMSG_NOT_CALLABLE_OR_UNDEFINED);
4061     return false;
4062   }
4063   uint32_t slot = JSSLOT_DEBUG_HOOK_START + which;
4064   RootedValue oldHook(cx, dbg.object->getReservedSlot(slot));
4065   dbg.object->setReservedSlot(slot, args[0]);
4066   if (hookObservesAllExecution(which)) {
4067     if (!dbg.updateObservesAllExecutionOnDebuggees(
4068             cx, dbg.observesAllExecution())) {
4069       dbg.object->setReservedSlot(slot, oldHook);
4070       return false;
4071     }
4072   }
4073 
4074   Rooted<DebuggerDebuggeeLink*> debuggeeLink(cx, dbg.getDebuggeeLink());
4075   if (dbg.hasAnyLiveHooks()) {
4076     debuggeeLink->setLinkSlot(dbg);
4077   } else {
4078     debuggeeLink->clearLinkSlot();
4079   }
4080 
4081   args.rval().setUndefined();
4082   return true;
4083 }
4084 
getOnDebuggerStatement()4085 bool Debugger::CallData::getOnDebuggerStatement() {
4086   return getHookImpl(cx, args, *dbg, OnDebuggerStatement);
4087 }
4088 
setOnDebuggerStatement()4089 bool Debugger::CallData::setOnDebuggerStatement() {
4090   return setHookImpl(cx, args, *dbg, OnDebuggerStatement);
4091 }
4092 
getOnExceptionUnwind()4093 bool Debugger::CallData::getOnExceptionUnwind() {
4094   return getHookImpl(cx, args, *dbg, OnExceptionUnwind);
4095 }
4096 
setOnExceptionUnwind()4097 bool Debugger::CallData::setOnExceptionUnwind() {
4098   return setHookImpl(cx, args, *dbg, OnExceptionUnwind);
4099 }
4100 
getOnNewScript()4101 bool Debugger::CallData::getOnNewScript() {
4102   return getHookImpl(cx, args, *dbg, OnNewScript);
4103 }
4104 
setOnNewScript()4105 bool Debugger::CallData::setOnNewScript() {
4106   return setHookImpl(cx, args, *dbg, OnNewScript);
4107 }
4108 
getOnNewPromise()4109 bool Debugger::CallData::getOnNewPromise() {
4110   return getHookImpl(cx, args, *dbg, OnNewPromise);
4111 }
4112 
setOnNewPromise()4113 bool Debugger::CallData::setOnNewPromise() {
4114   return setHookImpl(cx, args, *dbg, OnNewPromise);
4115 }
4116 
getOnPromiseSettled()4117 bool Debugger::CallData::getOnPromiseSettled() {
4118   return getHookImpl(cx, args, *dbg, OnPromiseSettled);
4119 }
4120 
setOnPromiseSettled()4121 bool Debugger::CallData::setOnPromiseSettled() {
4122   return setHookImpl(cx, args, *dbg, OnPromiseSettled);
4123 }
4124 
getOnEnterFrame()4125 bool Debugger::CallData::getOnEnterFrame() {
4126   return getHookImpl(cx, args, *dbg, OnEnterFrame);
4127 }
4128 
setOnEnterFrame()4129 bool Debugger::CallData::setOnEnterFrame() {
4130   return setHookImpl(cx, args, *dbg, OnEnterFrame);
4131 }
4132 
getOnNativeCall()4133 bool Debugger::CallData::getOnNativeCall() {
4134   return getHookImpl(cx, args, *dbg, OnNativeCall);
4135 }
4136 
setOnNativeCall()4137 bool Debugger::CallData::setOnNativeCall() {
4138   return setHookImpl(cx, args, *dbg, OnNativeCall);
4139 }
4140 
getOnNewGlobalObject()4141 bool Debugger::CallData::getOnNewGlobalObject() {
4142   return getHookImpl(cx, args, *dbg, OnNewGlobalObject);
4143 }
4144 
setOnNewGlobalObject()4145 bool Debugger::CallData::setOnNewGlobalObject() {
4146   RootedObject oldHook(cx, dbg->getHook(OnNewGlobalObject));
4147 
4148   if (!setHookImpl(cx, args, *dbg, OnNewGlobalObject)) {
4149     return false;
4150   }
4151 
4152   // Add or remove ourselves from the runtime's list of Debuggers that care
4153   // about new globals.
4154   JSObject* newHook = dbg->getHook(OnNewGlobalObject);
4155   if (!oldHook && newHook) {
4156     cx->runtime()->onNewGlobalObjectWatchers().pushBack(dbg);
4157   } else if (oldHook && !newHook) {
4158     cx->runtime()->onNewGlobalObjectWatchers().remove(dbg);
4159   }
4160 
4161   return true;
4162 }
4163 
getUncaughtExceptionHook()4164 bool Debugger::CallData::getUncaughtExceptionHook() {
4165   args.rval().setObjectOrNull(dbg->uncaughtExceptionHook);
4166   return true;
4167 }
4168 
setUncaughtExceptionHook()4169 bool Debugger::CallData::setUncaughtExceptionHook() {
4170   if (!args.requireAtLeast(cx, "Debugger.set uncaughtExceptionHook", 1)) {
4171     return false;
4172   }
4173   if (!args[0].isNull() &&
4174       (!args[0].isObject() || !args[0].toObject().isCallable())) {
4175     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4176                               JSMSG_ASSIGN_FUNCTION_OR_NULL,
4177                               "uncaughtExceptionHook");
4178     return false;
4179   }
4180   dbg->uncaughtExceptionHook = args[0].toObjectOrNull();
4181   args.rval().setUndefined();
4182   return true;
4183 }
4184 
getAllowUnobservedAsmJS()4185 bool Debugger::CallData::getAllowUnobservedAsmJS() {
4186   args.rval().setBoolean(dbg->allowUnobservedAsmJS);
4187   return true;
4188 }
4189 
setAllowUnobservedAsmJS()4190 bool Debugger::CallData::setAllowUnobservedAsmJS() {
4191   if (!args.requireAtLeast(cx, "Debugger.set allowUnobservedAsmJS", 1)) {
4192     return false;
4193   }
4194   dbg->allowUnobservedAsmJS = ToBoolean(args[0]);
4195 
4196   for (WeakGlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty();
4197        r.popFront()) {
4198     GlobalObject* global = r.front();
4199     Realm* realm = global->realm();
4200     realm->updateDebuggerObservesAsmJS();
4201   }
4202 
4203   args.rval().setUndefined();
4204   return true;
4205 }
4206 
getCollectCoverageInfo()4207 bool Debugger::CallData::getCollectCoverageInfo() {
4208   args.rval().setBoolean(dbg->collectCoverageInfo);
4209   return true;
4210 }
4211 
setCollectCoverageInfo()4212 bool Debugger::CallData::setCollectCoverageInfo() {
4213   if (!args.requireAtLeast(cx, "Debugger.set collectCoverageInfo", 1)) {
4214     return false;
4215   }
4216   dbg->collectCoverageInfo = ToBoolean(args[0]);
4217 
4218   IsObserving observing = dbg->collectCoverageInfo ? Observing : NotObserving;
4219   if (!dbg->updateObservesCoverageOnDebuggees(cx, observing)) {
4220     return false;
4221   }
4222 
4223   args.rval().setUndefined();
4224   return true;
4225 }
4226 
getMemory()4227 bool Debugger::CallData::getMemory() {
4228   Value memoryValue =
4229       dbg->object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE);
4230 
4231   if (!memoryValue.isObject()) {
4232     RootedObject memory(cx, DebuggerMemory::create(cx, dbg));
4233     if (!memory) {
4234       return false;
4235     }
4236     memoryValue = ObjectValue(*memory);
4237   }
4238 
4239   args.rval().set(memoryValue);
4240   return true;
4241 }
4242 
4243 /*
4244  * Given a value used to designate a global (there's quite a variety; see the
4245  * docs), return the actual designee.
4246  *
4247  * Note that this does not check whether the designee is marked "invisible to
4248  * Debugger" or not; different callers need to handle invisible-to-Debugger
4249  * globals in different ways.
4250  */
unwrapDebuggeeArgument(JSContext * cx,const Value & v)4251 GlobalObject* Debugger::unwrapDebuggeeArgument(JSContext* cx, const Value& v) {
4252   if (!v.isObject()) {
4253     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4254                               JSMSG_UNEXPECTED_TYPE, "argument",
4255                               "not a global object");
4256     return nullptr;
4257   }
4258 
4259   RootedObject obj(cx, &v.toObject());
4260 
4261   // If it's a Debugger.Object belonging to this debugger, dereference that.
4262   if (obj->getClass() == &DebuggerObject::class_) {
4263     RootedValue rv(cx, v);
4264     if (!unwrapDebuggeeValue(cx, &rv)) {
4265       return nullptr;
4266     }
4267     obj = &rv.toObject();
4268   }
4269 
4270   // If we have a cross-compartment wrapper, dereference as far as is secure.
4271   //
4272   // Since we're dealing with globals, we may have a WindowProxy here.  So we
4273   // have to make sure to do a dynamic unwrap, and we want to unwrap the
4274   // WindowProxy too, if we have one.
4275   obj = CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false);
4276   if (!obj) {
4277     ReportAccessDenied(cx);
4278     return nullptr;
4279   }
4280 
4281   // If that didn't produce a global object, it's an error.
4282   if (!obj->is<GlobalObject>()) {
4283     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4284                               JSMSG_UNEXPECTED_TYPE, "argument",
4285                               "not a global object");
4286     return nullptr;
4287   }
4288 
4289   return &obj->as<GlobalObject>();
4290 }
4291 
addDebuggee()4292 bool Debugger::CallData::addDebuggee() {
4293   if (!args.requireAtLeast(cx, "Debugger.addDebuggee", 1)) {
4294     return false;
4295   }
4296   Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
4297   if (!global) {
4298     return false;
4299   }
4300 
4301   if (!dbg->addDebuggeeGlobal(cx, global)) {
4302     return false;
4303   }
4304 
4305   RootedValue v(cx, ObjectValue(*global));
4306   if (!dbg->wrapDebuggeeValue(cx, &v)) {
4307     return false;
4308   }
4309   args.rval().set(v);
4310   return true;
4311 }
4312 
addAllGlobalsAsDebuggees()4313 bool Debugger::CallData::addAllGlobalsAsDebuggees() {
4314   for (CompartmentsIter comp(cx->runtime()); !comp.done(); comp.next()) {
4315     if (comp == dbg->object->compartment()) {
4316       continue;
4317     }
4318     for (RealmsInCompartmentIter r(comp); !r.done(); r.next()) {
4319       if (r->creationOptions().invisibleToDebugger()) {
4320         continue;
4321       }
4322       r->compartment()->gcState.scheduledForDestruction = false;
4323       GlobalObject* global = r->maybeGlobal();
4324       if (global) {
4325         Rooted<GlobalObject*> rg(cx, global);
4326         if (!dbg->addDebuggeeGlobal(cx, rg)) {
4327           return false;
4328         }
4329       }
4330     }
4331   }
4332 
4333   args.rval().setUndefined();
4334   return true;
4335 }
4336 
removeDebuggee()4337 bool Debugger::CallData::removeDebuggee() {
4338   if (!args.requireAtLeast(cx, "Debugger.removeDebuggee", 1)) {
4339     return false;
4340   }
4341   Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
4342   if (!global) {
4343     return false;
4344   }
4345 
4346   ExecutionObservableRealms obs(cx);
4347 
4348   if (dbg->debuggees.has(global)) {
4349     dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), global, nullptr,
4350                               FromSweep::No);
4351 
4352     // Only update the realm if there are no Debuggers left, as it's
4353     // expensive to check if no other Debugger has a live script or frame
4354     // hook on any of the current on-stack debuggee frames.
4355     if (global->getDebuggers().empty() && !obs.add(global->realm())) {
4356       return false;
4357     }
4358     if (!updateExecutionObservability(cx, obs, NotObserving)) {
4359       return false;
4360     }
4361   }
4362 
4363   args.rval().setUndefined();
4364   return true;
4365 }
4366 
removeAllDebuggees()4367 bool Debugger::CallData::removeAllDebuggees() {
4368   ExecutionObservableRealms obs(cx);
4369 
4370   for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
4371     Rooted<GlobalObject*> global(cx, e.front());
4372     dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), global, &e,
4373                               FromSweep::No);
4374 
4375     // See note about adding to the observable set in removeDebuggee.
4376     if (global->getDebuggers().empty() && !obs.add(global->realm())) {
4377       return false;
4378     }
4379   }
4380 
4381   if (!updateExecutionObservability(cx, obs, NotObserving)) {
4382     return false;
4383   }
4384 
4385   args.rval().setUndefined();
4386   return true;
4387 }
4388 
hasDebuggee()4389 bool Debugger::CallData::hasDebuggee() {
4390   if (!args.requireAtLeast(cx, "Debugger.hasDebuggee", 1)) {
4391     return false;
4392   }
4393   GlobalObject* global = dbg->unwrapDebuggeeArgument(cx, args[0]);
4394   if (!global) {
4395     return false;
4396   }
4397   args.rval().setBoolean(!!dbg->debuggees.lookup(global));
4398   return true;
4399 }
4400 
getDebuggees()4401 bool Debugger::CallData::getDebuggees() {
4402   // Obtain the list of debuggees before wrapping each debuggee, as a GC could
4403   // update the debuggees set while we are iterating it.
4404   unsigned count = dbg->debuggees.count();
4405   RootedValueVector debuggees(cx);
4406   if (!debuggees.resize(count)) {
4407     return false;
4408   }
4409   unsigned i = 0;
4410   {
4411     JS::AutoCheckCannotGC nogc;
4412     for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty();
4413          e.popFront()) {
4414       debuggees[i++].setObject(*e.front().get());
4415     }
4416   }
4417 
4418   RootedArrayObject arrobj(cx, NewDenseFullyAllocatedArray(cx, count));
4419   if (!arrobj) {
4420     return false;
4421   }
4422   arrobj->ensureDenseInitializedLength(cx, 0, count);
4423   for (i = 0; i < count; i++) {
4424     RootedValue v(cx, debuggees[i]);
4425     if (!dbg->wrapDebuggeeValue(cx, &v)) {
4426       return false;
4427     }
4428     arrobj->setDenseElement(i, v);
4429   }
4430 
4431   args.rval().setObject(*arrobj);
4432   return true;
4433 }
4434 
getNewestFrame()4435 bool Debugger::CallData::getNewestFrame() {
4436   // Since there may be multiple contexts, use AllFramesIter.
4437   for (AllFramesIter i(cx); !i.done(); ++i) {
4438     if (dbg->observesFrame(i)) {
4439       // Ensure that Ion frames are rematerialized. Only rematerialized
4440       // Ion frames may be used as AbstractFramePtrs.
4441       if (i.isIon() && !i.ensureHasRematerializedFrame(cx)) {
4442         return false;
4443       }
4444       AbstractFramePtr frame = i.abstractFramePtr();
4445       FrameIter iter(i.activation()->cx());
4446       while (!iter.hasUsableAbstractFramePtr() ||
4447              iter.abstractFramePtr() != frame) {
4448         ++iter;
4449       }
4450       return dbg->getFrame(cx, iter, args.rval());
4451     }
4452   }
4453   args.rval().setNull();
4454   return true;
4455 }
4456 
clearAllBreakpoints()4457 bool Debugger::CallData::clearAllBreakpoints() {
4458   for (WeakGlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty();
4459        r.popFront()) {
4460     DebugScript::clearBreakpointsIn(cx->runtime()->defaultFreeOp(),
4461                                     r.front()->realm(), dbg, nullptr);
4462   }
4463   return true;
4464 }
4465 
4466 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)4467 bool Debugger::construct(JSContext* cx, unsigned argc, Value* vp) {
4468   CallArgs args = CallArgsFromVp(argc, vp);
4469 
4470   // Check that the arguments, if any, are cross-compartment wrappers.
4471   for (unsigned i = 0; i < args.length(); i++) {
4472     JSObject* argobj = RequireObject(cx, args[i]);
4473     if (!argobj) {
4474       return false;
4475     }
4476     if (!argobj->is<CrossCompartmentWrapperObject>()) {
4477       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4478                                 JSMSG_DEBUG_CCW_REQUIRED, "Debugger");
4479       return false;
4480     }
4481   }
4482 
4483   // Get Debugger.prototype.
4484   RootedValue v(cx);
4485   RootedObject callee(cx, &args.callee());
4486   if (!GetProperty(cx, callee, callee, cx->names().prototype, &v)) {
4487     return false;
4488   }
4489   RootedNativeObject proto(cx, &v.toObject().as<NativeObject>());
4490   MOZ_ASSERT(proto->is<DebuggerInstanceObject>());
4491 
4492   // Make the new Debugger object. Each one has a reference to
4493   // Debugger.{Frame,Object,Script,Memory}.prototype in reserved slots. The
4494   // rest of the reserved slots are for hooks; they default to undefined.
4495   Rooted<DebuggerInstanceObject*> obj(
4496       cx, NewTenuredObjectWithGivenProto<DebuggerInstanceObject>(cx, proto));
4497   if (!obj) {
4498     return false;
4499   }
4500   for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP;
4501        slot++) {
4502     obj->setReservedSlot(slot, proto->getReservedSlot(slot));
4503   }
4504   obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, NullValue());
4505 
4506   RootedNativeObject livenessLink(
4507       cx, NewObjectWithGivenProto<DebuggerDebuggeeLink>(cx, nullptr));
4508   if (!livenessLink) {
4509     return false;
4510   }
4511   obj->setReservedSlot(JSSLOT_DEBUG_DEBUGGEE_LINK, ObjectValue(*livenessLink));
4512 
4513   Debugger* debugger;
4514   {
4515     // Construct the underlying C++ object.
4516     auto dbg = cx->make_unique<Debugger>(cx, obj.get());
4517     if (!dbg) {
4518       return false;
4519     }
4520 
4521     // The object owns the released pointer.
4522     debugger = dbg.release();
4523     InitObjectPrivate(obj, debugger, MemoryUse::Debugger);
4524   }
4525 
4526   // Add the initial debuggees, if any.
4527   for (unsigned i = 0; i < args.length(); i++) {
4528     JSObject& wrappedObj =
4529         args[i].toObject().as<ProxyObject>().private_().toObject();
4530     Rooted<GlobalObject*> debuggee(cx, &wrappedObj.nonCCWGlobal());
4531     if (!debugger->addDebuggeeGlobal(cx, debuggee)) {
4532       return false;
4533     }
4534   }
4535 
4536   args.rval().setObject(*obj);
4537   return true;
4538 }
4539 
addDebuggeeGlobal(JSContext * cx,Handle<GlobalObject * > global)4540 bool Debugger::addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> global) {
4541   if (debuggees.has(global)) {
4542     return true;
4543   }
4544 
4545   // Callers should generally be unable to get a reference to a debugger-
4546   // invisible global in order to pass it to addDebuggee. But this is possible
4547   // with certain testing aides we expose in the shell, so just make addDebuggee
4548   // throw in that case.
4549   Realm* debuggeeRealm = global->realm();
4550   if (debuggeeRealm->creationOptions().invisibleToDebugger()) {
4551     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4552                               JSMSG_DEBUG_CANT_DEBUG_GLOBAL);
4553     return false;
4554   }
4555 
4556   // Debugger and debuggee must be in different compartments.
4557   if (debuggeeRealm->compartment() == object->compartment()) {
4558     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4559                               JSMSG_DEBUG_SAME_COMPARTMENT);
4560     return false;
4561   }
4562 
4563   // Check for cycles. If global's realm is reachable from this Debugger
4564   // object's realm by following debuggee-to-debugger links, then adding
4565   // global would create a cycle. (Typically nobody is debugging the
4566   // debugger, in which case we zip through this code without looping.)
4567   Vector<Realm*> visited(cx);
4568   if (!visited.append(object->realm())) {
4569     return false;
4570   }
4571   for (size_t i = 0; i < visited.length(); i++) {
4572     Realm* realm = visited[i];
4573     if (realm == debuggeeRealm) {
4574       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_LOOP);
4575       return false;
4576     }
4577 
4578     // Find all realms containing debuggers debugging realm's global object.
4579     // Add those realms to visited.
4580     if (realm->isDebuggee()) {
4581       for (Realm::DebuggerVectorEntry& entry : realm->getDebuggers()) {
4582         Realm* next = entry.dbg->object->realm();
4583         if (std::find(visited.begin(), visited.end(), next) == visited.end()) {
4584           if (!visited.append(next)) {
4585             return false;
4586           }
4587         }
4588       }
4589     }
4590   }
4591 
4592   // For global to become this js::Debugger's debuggee:
4593   //
4594   // 1. this js::Debugger must be in global->getDebuggers(),
4595   // 2. global must be in this->debuggees,
4596   // 3. the debuggee's zone must be in this->debuggeeZones,
4597   // 4. if we are tracking allocations, the SavedStacksMetadataBuilder must be
4598   //    installed for this realm, and
4599   // 5. Realm::isDebuggee()'s bit must be set.
4600   //
4601   // All five indications must be kept consistent.
4602 
4603   AutoRealm ar(cx, global);
4604   Zone* zone = global->zone();
4605 
4606   RootedObject debuggeeLink(cx, getDebuggeeLink());
4607   if (!cx->compartment()->wrap(cx, &debuggeeLink)) {
4608     return false;
4609   }
4610 
4611   // (1)
4612   auto& globalDebuggers = global->getDebuggers();
4613   if (!globalDebuggers.append(Realm::DebuggerVectorEntry(this, debuggeeLink))) {
4614     ReportOutOfMemory(cx);
4615     return false;
4616   }
4617   auto globalDebuggersGuard = MakeScopeExit([&] { globalDebuggers.popBack(); });
4618 
4619   // (2)
4620   if (!debuggees.put(global)) {
4621     ReportOutOfMemory(cx);
4622     return false;
4623   }
4624   auto debuggeesGuard = MakeScopeExit([&] { debuggees.remove(global); });
4625 
4626   bool addingZoneRelation = !debuggeeZones.has(zone);
4627 
4628   // (3)
4629   if (addingZoneRelation && !debuggeeZones.put(zone)) {
4630     ReportOutOfMemory(cx);
4631     return false;
4632   }
4633   auto debuggeeZonesGuard = MakeScopeExit([&] {
4634     if (addingZoneRelation) {
4635       debuggeeZones.remove(zone);
4636     }
4637   });
4638 
4639   // (4)
4640   if (trackingAllocationSites &&
4641       !Debugger::addAllocationsTracking(cx, global)) {
4642     return false;
4643   }
4644 
4645   auto allocationsTrackingGuard = MakeScopeExit([&] {
4646     if (trackingAllocationSites) {
4647       Debugger::removeAllocationsTracking(*global);
4648     }
4649   });
4650 
4651   // (5)
4652   AutoRestoreRealmDebugMode debugModeGuard(debuggeeRealm);
4653   debuggeeRealm->setIsDebuggee();
4654   debuggeeRealm->updateDebuggerObservesAsmJS();
4655   debuggeeRealm->updateDebuggerObservesCoverage();
4656   if (observesAllExecution() &&
4657       !ensureExecutionObservabilityOfRealm(cx, debuggeeRealm)) {
4658     return false;
4659   }
4660 
4661   globalDebuggersGuard.release();
4662   debuggeesGuard.release();
4663   debuggeeZonesGuard.release();
4664   allocationsTrackingGuard.release();
4665   debugModeGuard.release();
4666   return true;
4667 }
4668 
recomputeDebuggeeZoneSet()4669 void Debugger::recomputeDebuggeeZoneSet() {
4670   AutoEnterOOMUnsafeRegion oomUnsafe;
4671   debuggeeZones.clear();
4672   for (auto range = debuggees.all(); !range.empty(); range.popFront()) {
4673     if (!debuggeeZones.put(range.front().unbarrieredGet()->zone())) {
4674       oomUnsafe.crash("Debugger::removeDebuggeeGlobal");
4675     }
4676   }
4677 }
4678 
4679 template <typename T, typename AP>
findDebuggerInVector(Debugger * dbg,Vector<T,0,AP> * vec)4680 static T* findDebuggerInVector(Debugger* dbg, Vector<T, 0, AP>* vec) {
4681   T* p;
4682   for (p = vec->begin(); p != vec->end(); p++) {
4683     if (p->dbg == dbg) {
4684       break;
4685     }
4686   }
4687   MOZ_ASSERT(p != vec->end());
4688   return p;
4689 }
4690 
removeDebuggeeGlobal(JSFreeOp * fop,GlobalObject * global,WeakGlobalObjectSet::Enum * debugEnum,FromSweep fromSweep)4691 void Debugger::removeDebuggeeGlobal(JSFreeOp* fop, GlobalObject* global,
4692                                     WeakGlobalObjectSet::Enum* debugEnum,
4693                                     FromSweep fromSweep) {
4694   // The caller might have found global by enumerating this->debuggees; if
4695   // so, use HashSet::Enum::removeFront rather than HashSet::remove below,
4696   // to avoid invalidating the live enumerator.
4697   MOZ_ASSERT(debuggees.has(global));
4698   MOZ_ASSERT(debuggeeZones.has(global->zone()));
4699   MOZ_ASSERT_IF(debugEnum, debugEnum->front().unbarrieredGet() == global);
4700 
4701   // FIXME Debugger::slowPathOnLeaveFrame needs to kill all Debugger.Frame
4702   // objects referring to a particular JS stack frame. This is hard if
4703   // Debugger objects that are no longer debugging the relevant global might
4704   // have live Frame objects. So we take the easy way out and kill them here.
4705   // This is a bug, since it's observable and contrary to the spec. One
4706   // possible fix would be to put such objects into a compartment-wide bag
4707   // which slowPathOnLeaveFrame would have to examine.
4708   for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
4709     AbstractFramePtr frame = e.front().key();
4710     DebuggerFrame* frameobj = e.front().value();
4711     if (frame.hasGlobal(global)) {
4712       frameobj->freeFrameIterData(fop);
4713       frameobj->maybeDecrementStepperCounter(fop, frame);
4714       e.removeFront();
4715     }
4716   }
4717 
4718   // Clear this global's generators from generatorFrames as well.
4719   //
4720   // This method can be called either from script (dbg.removeDebuggee) or during
4721   // GC sweeping, because the Debugger, debuggee global, or both are being GC'd.
4722   //
4723   // When called from script, it's okay to iterate over generatorFrames and
4724   // touch its keys and values (even when an incremental GC is in progress).
4725   // When called from GC, it's not okay; the keys and values may be dying. But
4726   // in that case, we can actually just skip the loop entirely! If the Debugger
4727   // is going away, it doesn't care about the state of its generatorFrames
4728   // table, and the Debugger.Frame finalizer will fix up the generator observer
4729   // counts.
4730   if (fromSweep == FromSweep::No) {
4731     for (GeneratorWeakMap::Enum e(generatorFrames); !e.empty(); e.popFront()) {
4732       AbstractGeneratorObject& genObj = *e.front().key();
4733       DebuggerFrame& frameObj = *e.front().value();
4734       if (genObj.isClosed() || &genObj.callee().global() == global) {
4735         frameObj.clearGenerator(fop, this, &e);
4736       }
4737     }
4738   }
4739 
4740   auto& globalDebuggersVector = global->getDebuggers();
4741 
4742   // The relation must be removed from up to three places:
4743   // globalDebuggersVector and debuggees for sure, and possibly the
4744   // compartment's debuggee set.
4745   //
4746   // The debuggee zone set is recomputed on demand. This avoids refcounting
4747   // and in practice we have relatively few debuggees that tend to all be in
4748   // the same zone. If after recomputing the debuggee zone set, this global's
4749   // zone is not in the set, then we must remove ourselves from the zone's
4750   // vector of observing debuggers.
4751   globalDebuggersVector.erase(
4752       findDebuggerInVector(this, &globalDebuggersVector));
4753 
4754   if (debugEnum) {
4755     debugEnum->removeFront();
4756   } else {
4757     debuggees.remove(global);
4758   }
4759 
4760   recomputeDebuggeeZoneSet();
4761 
4762   // Remove all breakpoints for the debuggee.
4763   Breakpoint* nextbp;
4764   for (Breakpoint* bp = firstBreakpoint(); bp; bp = nextbp) {
4765     nextbp = bp->nextInDebugger();
4766 
4767     if (bp->site->realm() == global->realm()) {
4768       bp->remove(fop);
4769     }
4770   }
4771   MOZ_ASSERT_IF(debuggees.empty(), !firstBreakpoint());
4772 
4773   // If we are tracking allocation sites, we need to remove the object
4774   // metadata callback from this global's realm.
4775   if (trackingAllocationSites) {
4776     Debugger::removeAllocationsTracking(*global);
4777   }
4778 
4779   if (global->realm()->getDebuggers().empty()) {
4780     global->realm()->unsetIsDebuggee();
4781   } else {
4782     global->realm()->updateDebuggerObservesAllExecution();
4783     global->realm()->updateDebuggerObservesAsmJS();
4784     global->realm()->updateDebuggerObservesCoverage();
4785   }
4786 }
4787 
4788 class MOZ_STACK_CLASS Debugger::QueryBase {
4789  protected:
QueryBase(JSContext * cx,Debugger * dbg)4790   QueryBase(JSContext* cx, Debugger* dbg)
4791       : cx(cx),
4792         debugger(dbg),
4793         iterMarker(&cx->runtime()->gc),
4794         realms(cx->zone()),
4795         oom(false) {}
4796 
4797   // The context in which we should do our work.
4798   JSContext* cx;
4799 
4800   // The debugger for which we conduct queries.
4801   Debugger* debugger;
4802 
4803   // Require the set of realms to stay fixed while the query is alive.
4804   gc::AutoEnterIteration iterMarker;
4805 
4806   using RealmSet = HashSet<Realm*, DefaultHasher<Realm*>, ZoneAllocPolicy>;
4807 
4808   // A script must be in one of these realms to match the query.
4809   RealmSet realms;
4810 
4811   // Indicates whether OOM has occurred while matching.
4812   bool oom;
4813 
addRealm(Realm * realm)4814   bool addRealm(Realm* realm) { return realms.put(realm); }
4815 
4816   // Arrange for this query to match only scripts that run in |global|.
matchSingleGlobal(GlobalObject * global)4817   bool matchSingleGlobal(GlobalObject* global) {
4818     MOZ_ASSERT(realms.count() == 0);
4819     if (!addRealm(global->realm())) {
4820       ReportOutOfMemory(cx);
4821       return false;
4822     }
4823     return true;
4824   }
4825 
4826   // Arrange for this ScriptQuery to match all scripts running in debuggee
4827   // globals.
matchAllDebuggeeGlobals()4828   bool matchAllDebuggeeGlobals() {
4829     MOZ_ASSERT(realms.count() == 0);
4830     // Build our realm set from the debugger's set of debuggee globals.
4831     for (WeakGlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty();
4832          r.popFront()) {
4833       if (!addRealm(r.front()->realm())) {
4834         ReportOutOfMemory(cx);
4835         return false;
4836       }
4837     }
4838     return true;
4839   }
4840 };
4841 
4842 /*
4843  * A class for parsing 'findScripts' query arguments and searching for
4844  * scripts that match the criteria they represent.
4845  */
4846 class MOZ_STACK_CLASS Debugger::ScriptQuery : public Debugger::QueryBase {
4847  public:
4848   /* Construct a ScriptQuery to use matching scripts for |dbg|. */
ScriptQuery(JSContext * cx,Debugger * dbg)4849   ScriptQuery(JSContext* cx, Debugger* dbg)
4850       : QueryBase(cx, dbg),
4851         url(cx),
4852         displayURLString(cx),
4853         hasSource(false),
4854         source(cx, AsVariant(static_cast<ScriptSourceObject*>(nullptr))),
4855         hasLine(false),
4856         line(0),
4857         innermost(false),
4858         innermostForRealm(cx, cx->zone()),
4859         scriptVector(cx, BaseScriptVector(cx)),
4860         wasmInstanceVector(cx, WasmInstanceObjectVector(cx)) {}
4861 
4862   /*
4863    * Parse the query object |query|, and prepare to match only the scripts
4864    * it specifies.
4865    */
parseQuery(HandleObject query)4866   bool parseQuery(HandleObject query) {
4867     // Check for a 'global' property, which limits the results to those
4868     // scripts scoped to a particular global object.
4869     RootedValue global(cx);
4870     if (!GetProperty(cx, query, query, cx->names().global, &global)) {
4871       return false;
4872     }
4873     if (global.isUndefined()) {
4874       if (!matchAllDebuggeeGlobals()) {
4875         return false;
4876       }
4877     } else {
4878       GlobalObject* globalObject = debugger->unwrapDebuggeeArgument(cx, global);
4879       if (!globalObject) {
4880         return false;
4881       }
4882 
4883       // If the given global isn't a debuggee, just leave the set of
4884       // acceptable globals empty; we'll return no scripts.
4885       if (debugger->debuggees.has(globalObject)) {
4886         if (!matchSingleGlobal(globalObject)) {
4887           return false;
4888         }
4889       }
4890     }
4891 
4892     // Check for a 'url' property.
4893     if (!GetProperty(cx, query, query, cx->names().url, &url)) {
4894       return false;
4895     }
4896     if (!url.isUndefined() && !url.isString()) {
4897       JS_ReportErrorNumberASCII(
4898           cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
4899           "query object's 'url' property", "neither undefined nor a string");
4900       return false;
4901     }
4902 
4903     // Check for a 'source' property
4904     RootedValue debuggerSource(cx);
4905     if (!GetProperty(cx, query, query, cx->names().source, &debuggerSource)) {
4906       return false;
4907     }
4908     if (!debuggerSource.isUndefined()) {
4909       if (!debuggerSource.isObject() ||
4910           !debuggerSource.toObject().is<DebuggerSource>()) {
4911         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4912                                   JSMSG_UNEXPECTED_TYPE,
4913                                   "query object's 'source' property",
4914                                   "not undefined nor a Debugger.Source object");
4915         return false;
4916       }
4917 
4918       Value owner =
4919           debuggerSource.toObject().as<DebuggerSource>().getReservedSlot(
4920               DebuggerSource::OWNER_SLOT);
4921 
4922       // The given source must have an owner. Otherwise, it's a
4923       // Debugger.Source.prototype, which would match no scripts, and is
4924       // probably a mistake.
4925       if (!owner.isObject()) {
4926         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4927                                   JSMSG_DEBUG_PROTO, "Debugger.Source",
4928                                   "Debugger.Source");
4929         return false;
4930       }
4931 
4932       // If it does have an owner, it should match the Debugger we're
4933       // calling findScripts on. It would work fine even if it didn't,
4934       // but mixing Debugger.Sources is probably a sign of confusion.
4935       if (&owner.toObject() != debugger->object) {
4936         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4937                                   JSMSG_DEBUG_WRONG_OWNER, "Debugger.Source");
4938         return false;
4939       }
4940 
4941       hasSource = true;
4942       source = debuggerSource.toObject().as<DebuggerSource>().getReferent();
4943     }
4944 
4945     // Check for a 'displayURL' property.
4946     RootedValue displayURL(cx);
4947     if (!GetProperty(cx, query, query, cx->names().displayURL, &displayURL)) {
4948       return false;
4949     }
4950     if (!displayURL.isUndefined() && !displayURL.isString()) {
4951       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4952                                 JSMSG_UNEXPECTED_TYPE,
4953                                 "query object's 'displayURL' property",
4954                                 "neither undefined nor a string");
4955       return false;
4956     }
4957 
4958     if (displayURL.isString()) {
4959       displayURLString = displayURL.toString()->ensureLinear(cx);
4960       if (!displayURLString) {
4961         return false;
4962       }
4963     }
4964 
4965     // Check for a 'line' property.
4966     RootedValue lineProperty(cx);
4967     if (!GetProperty(cx, query, query, cx->names().line, &lineProperty)) {
4968       return false;
4969     }
4970     if (lineProperty.isUndefined()) {
4971       hasLine = false;
4972     } else if (lineProperty.isNumber()) {
4973       if (displayURL.isUndefined() && url.isUndefined() && !hasSource) {
4974         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4975                                   JSMSG_QUERY_LINE_WITHOUT_URL);
4976         return false;
4977       }
4978       double doubleLine = lineProperty.toNumber();
4979       if (doubleLine <= 0 || (unsigned int)doubleLine != doubleLine) {
4980         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4981                                   JSMSG_DEBUG_BAD_LINE);
4982         return false;
4983       }
4984       hasLine = true;
4985       line = doubleLine;
4986     } else {
4987       JS_ReportErrorNumberASCII(
4988           cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
4989           "query object's 'line' property", "neither undefined nor an integer");
4990       return false;
4991     }
4992 
4993     // Check for an 'innermost' property.
4994     PropertyName* innermostName = cx->names().innermost;
4995     RootedValue innermostProperty(cx);
4996     if (!GetProperty(cx, query, query, innermostName, &innermostProperty)) {
4997       return false;
4998     }
4999     innermost = ToBoolean(innermostProperty);
5000     if (innermost) {
5001       // Technically, we need only check hasLine, but this is clearer.
5002       if ((displayURL.isUndefined() && url.isUndefined() && !hasSource) ||
5003           !hasLine) {
5004         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5005                                   JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL);
5006         return false;
5007       }
5008     }
5009 
5010     return true;
5011   }
5012 
5013   /* Set up this ScriptQuery appropriately for a missing query argument. */
omittedQuery()5014   bool omittedQuery() {
5015     url.setUndefined();
5016     hasLine = false;
5017     innermost = false;
5018     displayURLString = nullptr;
5019     return matchAllDebuggeeGlobals();
5020   }
5021 
5022   /*
5023    * Search all relevant realms and the stack for scripts matching
5024    * this query, and append the matching scripts to |scriptVector|.
5025    */
findScripts()5026   bool findScripts() {
5027     if (!prepareQuery()) {
5028       return false;
5029     }
5030 
5031     bool delazified = false;
5032     if (needsDelazifyBeforeQuery()) {
5033       if (!delazifyScripts()) {
5034         return false;
5035       }
5036       delazified = true;
5037     }
5038 
5039     Realm* singletonRealm = nullptr;
5040     if (realms.count() == 1) {
5041       singletonRealm = realms.all().front();
5042     }
5043 
5044     // Search each realm for debuggee scripts.
5045     MOZ_ASSERT(scriptVector.empty());
5046     oom = false;
5047     IterateScripts(cx, singletonRealm, this, considerScript);
5048     if (!delazified) {
5049       IterateLazyScripts(cx, singletonRealm, this, considerLazyScript);
5050     }
5051     if (oom) {
5052       ReportOutOfMemory(cx);
5053       return false;
5054     }
5055 
5056     // For most queries, we just accumulate results in 'scriptVector' as we find
5057     // them. But if this is an 'innermost' query, then we've accumulated the
5058     // results in the 'innermostForRealm' map. In that case, we now need to walk
5059     // that map and populate 'scriptVector'.
5060     if (innermost) {
5061       for (RealmToScriptMap::Range r = innermostForRealm.all(); !r.empty();
5062            r.popFront()) {
5063         if (!scriptVector.append(r.front().value())) {
5064           ReportOutOfMemory(cx);
5065           return false;
5066         }
5067       }
5068     }
5069 
5070     // TODO: Until such time that wasm modules are real ES6 modules,
5071     // unconditionally consider all wasm toplevel instance scripts.
5072     for (WeakGlobalObjectSet::Range r = debugger->allDebuggees(); !r.empty();
5073          r.popFront()) {
5074       for (wasm::Instance* instance : r.front()->realm()->wasm.instances()) {
5075         consider(instance->object());
5076         if (oom) {
5077           ReportOutOfMemory(cx);
5078           return false;
5079         }
5080       }
5081     }
5082 
5083     return true;
5084   }
5085 
foundScripts() const5086   Handle<BaseScriptVector> foundScripts() const { return scriptVector; }
5087 
foundWasmInstances() const5088   Handle<WasmInstanceObjectVector> foundWasmInstances() const {
5089     return wasmInstanceVector;
5090   }
5091 
5092  private:
5093   /* If this is a string, matching scripts have urls equal to it. */
5094   RootedValue url;
5095 
5096   /* url as a C string. */
5097   UniqueChars urlCString;
5098 
5099   /* If this is a string, matching scripts' sources have displayURLs equal to
5100    * it. */
5101   RootedLinearString displayURLString;
5102 
5103   /*
5104    * If this is a source referent, matching scripts will have sources equal
5105    * to this instance. Ideally we'd use a Maybe here, but Maybe interacts
5106    * very badly with Rooted's LIFO invariant.
5107    */
5108   bool hasSource;
5109   Rooted<DebuggerSourceReferent> source;
5110 
5111   /* True if the query contained a 'line' property. */
5112   bool hasLine;
5113 
5114   /* The line matching scripts must cover. */
5115   unsigned int line;
5116 
5117   /* True if the query has an 'innermost' property whose value is true. */
5118   bool innermost;
5119 
5120   using RealmToScriptMap =
5121       GCHashMap<Realm*, JSScript*, DefaultHasher<Realm*>, ZoneAllocPolicy>;
5122 
5123   /*
5124    * For 'innermost' queries, a map from realms to the innermost script
5125    * we've seen so far in that realm. (Template instantiation code size
5126    * explosion ho!)
5127    */
5128   Rooted<RealmToScriptMap> innermostForRealm;
5129 
5130   /*
5131    * Accumulate the scripts in an Rooted<BaseScriptVector> instead of creating
5132    * the JS array as we go, because we mustn't allocate JS objects or GC while
5133    * we use the CellIter.
5134    */
5135   Rooted<BaseScriptVector> scriptVector;
5136 
5137   /*
5138    * Like above, but for wasm modules.
5139    */
5140   Rooted<WasmInstanceObjectVector> wasmInstanceVector;
5141 
5142   /*
5143    * Given that parseQuery or omittedQuery has been called, prepare to match
5144    * scripts. Set urlCString and displayURLChars as appropriate.
5145    */
prepareQuery()5146   bool prepareQuery() {
5147     // Compute urlCString and displayURLChars, if a url or displayURL was
5148     // given respectively.
5149     if (url.isString()) {
5150       urlCString = JS_EncodeStringToLatin1(cx, url.toString());
5151       if (!urlCString) {
5152         return false;
5153       }
5154     }
5155 
5156     return true;
5157   }
5158 
delazifyScripts()5159   bool delazifyScripts() {
5160     // All scripts in debuggee realms must be visible, so delazify
5161     // everything.
5162     for (auto r = realms.all(); !r.empty(); r.popFront()) {
5163       Realm* realm = r.front();
5164       if (!realm->ensureDelazifyScriptsForDebugger(cx)) {
5165         return false;
5166       }
5167     }
5168     return true;
5169   }
5170 
considerScript(JSRuntime * rt,void * data,BaseScript * script,const JS::AutoRequireNoGC & nogc)5171   static void considerScript(JSRuntime* rt, void* data, BaseScript* script,
5172                              const JS::AutoRequireNoGC& nogc) {
5173     ScriptQuery* self = static_cast<ScriptQuery*>(data);
5174     self->consider(script->asJSScript(), nogc);
5175   }
5176 
considerLazyScript(JSRuntime * rt,void * data,BaseScript * script,const JS::AutoRequireNoGC & nogc)5177   static void considerLazyScript(JSRuntime* rt, void* data, BaseScript* script,
5178                                  const JS::AutoRequireNoGC& nogc) {
5179     ScriptQuery* self = static_cast<ScriptQuery*>(data);
5180     self->considerLazy(script, nogc);
5181   }
5182 
needsDelazifyBeforeQuery() const5183   bool needsDelazifyBeforeQuery() const {
5184     // * innermost
5185     //   Currently not supported, since this is not used outside of test.
5186     //
5187     // * hasLine
5188     //   Only JSScript supports GetScriptLineExtent.
5189     return innermost || hasLine;
5190   }
5191 
5192   template <typename T>
commonFilter(T script,const JS::AutoRequireNoGC & nogc)5193   MOZ_MUST_USE bool commonFilter(T script, const JS::AutoRequireNoGC& nogc) {
5194     if (urlCString) {
5195       bool gotFilename = false;
5196       if (script->filename() &&
5197           strcmp(script->filename(), urlCString.get()) == 0) {
5198         gotFilename = true;
5199       }
5200 
5201       bool gotSourceURL = false;
5202       if (!gotFilename && script->scriptSource()->introducerFilename() &&
5203           strcmp(script->scriptSource()->introducerFilename(),
5204                  urlCString.get()) == 0) {
5205         gotSourceURL = true;
5206       }
5207       if (!gotFilename && !gotSourceURL) {
5208         return false;
5209       }
5210     }
5211     if (displayURLString) {
5212       if (!script->scriptSource() || !script->scriptSource()->hasDisplayURL()) {
5213         return false;
5214       }
5215 
5216       const char16_t* s = script->scriptSource()->displayURL();
5217       if (CompareChars(s, js_strlen(s), displayURLString) != 0) {
5218         return false;
5219       }
5220     }
5221     if (hasSource && !(source.is<ScriptSourceObject*>() &&
5222                        source.as<ScriptSourceObject*>()->source() ==
5223                            script->scriptSource())) {
5224       return false;
5225     }
5226     return true;
5227   }
5228 
5229   /*
5230    * If |script| matches this query, append it to |scriptVector| or place it
5231    * in |innermostForRealm|, as appropriate. Set |oom| if an out of memory
5232    * condition occurred.
5233    */
consider(JSScript * script,const JS::AutoRequireNoGC & nogc)5234   void consider(JSScript* script, const JS::AutoRequireNoGC& nogc) {
5235     if (oom || script->selfHosted()) {
5236       return;
5237     }
5238     Realm* realm = script->realm();
5239     if (!realms.has(realm)) {
5240       return;
5241     }
5242     if (!commonFilter(script, nogc)) {
5243       return;
5244     }
5245     if (hasLine) {
5246       if (line < script->lineno() ||
5247           script->lineno() + GetScriptLineExtent(script) < line) {
5248         return;
5249       }
5250     }
5251 
5252     if (innermost) {
5253       // For 'innermost' queries, we don't place scripts in
5254       // |scriptVector| right away; we may later find another script that
5255       // is nested inside this one. Instead, we record the innermost
5256       // script we've found so far for each realm in innermostForRealm,
5257       // and only populate |scriptVector| at the bottom of findScripts,
5258       // when we've traversed all the scripts.
5259       //
5260       // So: check this script against the innermost one we've found so
5261       // far (if any), as recorded in innermostForRealm, and replace that
5262       // if it's better.
5263       RealmToScriptMap::AddPtr p = innermostForRealm.lookupForAdd(realm);
5264       if (p) {
5265         // Is our newly found script deeper than the last one we found?
5266         JSScript* incumbent = p->value();
5267         if (script->innermostScope()->chainLength() >
5268             incumbent->innermostScope()->chainLength()) {
5269           p->value() = script;
5270         }
5271       } else {
5272         // This is the first matching script we've encountered for this
5273         // realm, so it is thus the innermost such script.
5274         if (!innermostForRealm.add(p, realm, script)) {
5275           oom = true;
5276           return;
5277         }
5278       }
5279     } else {
5280       // Record this matching script in the results scriptVector.
5281       if (!scriptVector.append(script)) {
5282         oom = true;
5283         return;
5284       }
5285     }
5286   }
5287 
considerLazy(BaseScript * lazyScript,const JS::AutoRequireNoGC & nogc)5288   void considerLazy(BaseScript* lazyScript, const JS::AutoRequireNoGC& nogc) {
5289     MOZ_ASSERT(!needsDelazifyBeforeQuery());
5290 
5291     if (oom) {
5292       return;
5293     }
5294     Realm* realm = lazyScript->realm();
5295     if (!realms.has(realm)) {
5296       return;
5297     }
5298 
5299     // If the script is already delazified, it should be in scriptVector.
5300     if (lazyScript->hasBytecode()) {
5301       return;
5302     }
5303 
5304     if (!commonFilter(lazyScript, nogc)) {
5305       return;
5306     }
5307 
5308     /* Record this matching script in the results scriptVector. */
5309     if (!scriptVector.append(lazyScript)) {
5310       oom = true;
5311     }
5312   }
5313 
5314   /*
5315    * If |instanceObject| matches this query, append it to |wasmInstanceVector|.
5316    * Set |oom| if an out of memory condition occurred.
5317    */
consider(WasmInstanceObject * instanceObject)5318   void consider(WasmInstanceObject* instanceObject) {
5319     if (oom) {
5320       return;
5321     }
5322 
5323     if (hasSource && source != AsVariant(instanceObject)) {
5324       return;
5325     }
5326 
5327     if (!wasmInstanceVector.append(instanceObject)) {
5328       oom = true;
5329     }
5330   }
5331 };
5332 
findScripts()5333 bool Debugger::CallData::findScripts() {
5334   ScriptQuery query(cx, dbg);
5335 
5336   if (args.length() >= 1) {
5337     RootedObject queryObject(cx, RequireObject(cx, args[0]));
5338     if (!queryObject || !query.parseQuery(queryObject)) {
5339       return false;
5340     }
5341   } else {
5342     if (!query.omittedQuery()) {
5343       return false;
5344     }
5345   }
5346 
5347   if (!query.findScripts()) {
5348     return false;
5349   }
5350 
5351   Handle<BaseScriptVector> scripts(query.foundScripts());
5352   Handle<WasmInstanceObjectVector> wasmInstances(query.foundWasmInstances());
5353 
5354   size_t resultLength = scripts.length() + wasmInstances.length();
5355   RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, resultLength));
5356   if (!result) {
5357     return false;
5358   }
5359 
5360   result->ensureDenseInitializedLength(cx, 0, resultLength);
5361 
5362   for (size_t i = 0; i < scripts.length(); i++) {
5363     JSObject* scriptObject = dbg->wrapScript(cx, scripts[i]);
5364     if (!scriptObject) {
5365       return false;
5366     }
5367     result->setDenseElement(i, ObjectValue(*scriptObject));
5368   }
5369 
5370   size_t wasmStart = scripts.length();
5371   for (size_t i = 0; i < wasmInstances.length(); i++) {
5372     JSObject* scriptObject = dbg->wrapWasmScript(cx, wasmInstances[i]);
5373     if (!scriptObject) {
5374       return false;
5375     }
5376     result->setDenseElement(wasmStart + i, ObjectValue(*scriptObject));
5377   }
5378 
5379   args.rval().setObject(*result);
5380   return true;
5381 }
5382 
5383 /*
5384  * A class for searching sources for 'findSources'.
5385  */
5386 class MOZ_STACK_CLASS Debugger::SourceQuery : public Debugger::QueryBase {
5387  public:
5388   using SourceSet = JS::GCHashSet<JSObject*, js::MovableCellHasher<JSObject*>,
5389                                   ZoneAllocPolicy>;
5390 
SourceQuery(JSContext * cx,Debugger * dbg)5391   SourceQuery(JSContext* cx, Debugger* dbg)
5392       : QueryBase(cx, dbg), sources(cx, SourceSet(cx->zone())) {}
5393 
findSources()5394   bool findSources() {
5395     if (!matchAllDebuggeeGlobals()) {
5396       return false;
5397     }
5398 
5399     Realm* singletonRealm = nullptr;
5400     if (realms.count() == 1) {
5401       singletonRealm = realms.all().front();
5402     }
5403 
5404     // Search each realm for debuggee scripts.
5405     MOZ_ASSERT(sources.empty());
5406     oom = false;
5407     IterateScripts(cx, singletonRealm, this, considerScript);
5408     IterateLazyScripts(cx, singletonRealm, this, considerLazyScript);
5409     if (oom) {
5410       ReportOutOfMemory(cx);
5411       return false;
5412     }
5413 
5414     // TODO: Until such time that wasm modules are real ES6 modules,
5415     // unconditionally consider all wasm toplevel instance scripts.
5416     for (WeakGlobalObjectSet::Range r = debugger->allDebuggees(); !r.empty();
5417          r.popFront()) {
5418       for (wasm::Instance* instance : r.front()->realm()->wasm.instances()) {
5419         consider(instance->object());
5420         if (oom) {
5421           ReportOutOfMemory(cx);
5422           return false;
5423         }
5424       }
5425     }
5426 
5427     return true;
5428   }
5429 
foundSources() const5430   Handle<SourceSet> foundSources() const { return sources; }
5431 
5432  private:
5433   Rooted<SourceSet> sources;
5434 
considerScript(JSRuntime * rt,void * data,BaseScript * script,const JS::AutoRequireNoGC & nogc)5435   static void considerScript(JSRuntime* rt, void* data, BaseScript* script,
5436                              const JS::AutoRequireNoGC& nogc) {
5437     SourceQuery* self = static_cast<SourceQuery*>(data);
5438     self->consider(script->asJSScript(), nogc);
5439   }
5440 
considerLazyScript(JSRuntime * rt,void * data,BaseScript * script,const JS::AutoRequireNoGC & nogc)5441   static void considerLazyScript(JSRuntime* rt, void* data, BaseScript* script,
5442                                  const JS::AutoRequireNoGC& nogc) {
5443     SourceQuery* self = static_cast<SourceQuery*>(data);
5444     self->considerLazy(script, nogc);
5445   }
5446 
consider(JSScript * script,const JS::AutoRequireNoGC & nogc)5447   void consider(JSScript* script, const JS::AutoRequireNoGC& nogc) {
5448     if (oom || script->selfHosted()) {
5449       return;
5450     }
5451     Realm* realm = script->realm();
5452     if (!realms.has(realm)) {
5453       return;
5454     }
5455 
5456     if (!script->sourceObject()) {
5457       return;
5458     }
5459 
5460     ScriptSourceObject* source =
5461         &UncheckedUnwrap(script->sourceObject())->as<ScriptSourceObject>();
5462     if (!sources.put(source)) {
5463       oom = true;
5464     }
5465   }
5466 
considerLazy(BaseScript * lazyScript,const JS::AutoRequireNoGC & nogc)5467   void considerLazy(BaseScript* lazyScript, const JS::AutoRequireNoGC& nogc) {
5468     if (oom) {
5469       return;
5470     }
5471     Realm* realm = lazyScript->realm();
5472     if (!realms.has(realm)) {
5473       return;
5474     }
5475 
5476     // If the script is already delazified, it should already be handled.
5477     if (lazyScript->hasBytecode()) {
5478       return;
5479     }
5480 
5481     ScriptSourceObject* source = lazyScript->sourceObject();
5482     if (!sources.put(source)) {
5483       oom = true;
5484     }
5485   }
5486 
consider(WasmInstanceObject * instanceObject)5487   void consider(WasmInstanceObject* instanceObject) {
5488     if (oom) {
5489       return;
5490     }
5491 
5492     if (!sources.put(instanceObject)) {
5493       oom = true;
5494     }
5495   }
5496 };
5497 
AsSourceReferent(JSObject * obj)5498 static inline DebuggerSourceReferent AsSourceReferent(JSObject* obj) {
5499   if (obj->is<ScriptSourceObject>()) {
5500     return AsVariant(&obj->as<ScriptSourceObject>());
5501   }
5502   return AsVariant(&obj->as<WasmInstanceObject>());
5503 }
5504 
findSources()5505 bool Debugger::CallData::findSources() {
5506   SourceQuery query(cx, dbg);
5507   if (!query.findSources()) {
5508     return false;
5509   }
5510 
5511   Handle<SourceQuery::SourceSet> sources(query.foundSources());
5512 
5513   size_t resultLength = sources.count();
5514   RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, resultLength));
5515   if (!result) {
5516     return false;
5517   }
5518 
5519   result->ensureDenseInitializedLength(cx, 0, resultLength);
5520 
5521   size_t i = 0;
5522   for (auto iter = sources.get().iter(); !iter.done(); iter.next()) {
5523     Rooted<DebuggerSourceReferent> sourceReferent(cx,
5524                                                   AsSourceReferent(iter.get()));
5525     RootedObject sourceObject(cx, dbg->wrapVariantReferent(cx, sourceReferent));
5526     if (!sourceObject) {
5527       return false;
5528     }
5529     result->setDenseElement(i, ObjectValue(*sourceObject));
5530     i++;
5531   }
5532 
5533   args.rval().setObject(*result);
5534   return true;
5535 }
5536 
5537 /*
5538  * A class for parsing 'findObjects' query arguments and searching for objects
5539  * that match the criteria they represent.
5540  */
5541 class MOZ_STACK_CLASS Debugger::ObjectQuery {
5542  public:
5543   /* Construct an ObjectQuery to use matching scripts for |dbg|. */
ObjectQuery(JSContext * cx,Debugger * dbg)5544   ObjectQuery(JSContext* cx, Debugger* dbg)
5545       : objects(cx), cx(cx), dbg(dbg), className(cx) {}
5546 
5547   /* The vector that we are accumulating results in. */
5548   RootedObjectVector objects;
5549 
5550   /* The set of debuggee compartments. */
5551   JS::CompartmentSet debuggeeCompartments;
5552 
5553   /*
5554    * Parse the query object |query|, and prepare to match only the objects it
5555    * specifies.
5556    */
parseQuery(HandleObject query)5557   bool parseQuery(HandleObject query) {
5558     // Check for the 'class' property
5559     RootedValue cls(cx);
5560     if (!GetProperty(cx, query, query, cx->names().class_, &cls)) {
5561       return false;
5562     }
5563     if (!cls.isUndefined()) {
5564       if (!cls.isString()) {
5565         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5566                                   JSMSG_UNEXPECTED_TYPE,
5567                                   "query object's 'class' property",
5568                                   "neither undefined nor a string");
5569         return false;
5570       }
5571       JSLinearString* str = cls.toString()->ensureLinear(cx);
5572       if (!str) {
5573         return false;
5574       }
5575       if (!StringIsAscii(str)) {
5576         JS_ReportErrorNumberASCII(
5577             cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
5578             "query object's 'class' property",
5579             "not a string containing only ASCII characters");
5580         return false;
5581       }
5582       className = cls;
5583     }
5584     return true;
5585   }
5586 
5587   /* Set up this ObjectQuery appropriately for a missing query argument. */
omittedQuery()5588   void omittedQuery() { className.setUndefined(); }
5589 
5590   /*
5591    * Traverse the heap to find all relevant objects and add them to the
5592    * provided vector.
5593    */
findObjects()5594   bool findObjects() {
5595     if (!prepareQuery()) {
5596       return false;
5597     }
5598 
5599     for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
5600          r.popFront()) {
5601       if (!debuggeeCompartments.put(r.front()->compartment())) {
5602         ReportOutOfMemory(cx);
5603         return false;
5604       }
5605     }
5606 
5607     {
5608       // We can't tolerate the GC moving things around while we're
5609       // searching the heap. Check that nothing we do causes a GC.
5610       Maybe<JS::AutoCheckCannotGC> maybeNoGC;
5611       RootedObject dbgObj(cx, dbg->object);
5612       JS::ubi::RootList rootList(cx, maybeNoGC);
5613       if (!rootList.init(dbgObj)) {
5614         ReportOutOfMemory(cx);
5615         return false;
5616       }
5617 
5618       Traversal traversal(cx, *this, maybeNoGC.ref());
5619       traversal.wantNames = false;
5620 
5621       return traversal.addStart(JS::ubi::Node(&rootList)) &&
5622              traversal.traverse();
5623     }
5624   }
5625 
5626   /*
5627    * |ubi::Node::BreadthFirst| interface.
5628    */
5629   class NodeData {};
5630   using Traversal = JS::ubi::BreadthFirst<ObjectQuery>;
operator ()(Traversal & traversal,JS::ubi::Node origin,const JS::ubi::Edge & edge,NodeData *,bool first)5631   bool operator()(Traversal& traversal, JS::ubi::Node origin,
5632                   const JS::ubi::Edge& edge, NodeData*, bool first) {
5633     if (!first) {
5634       return true;
5635     }
5636 
5637     JS::ubi::Node referent = edge.referent;
5638 
5639     // Only follow edges within our set of debuggee compartments; we don't
5640     // care about the heap's subgraphs outside of our debuggee compartments,
5641     // so we abandon the referent. Either (1) there is not a path from this
5642     // non-debuggee node back to a node in our debuggee compartments, and we
5643     // don't need to follow edges to or from this node, or (2) there does
5644     // exist some path from this non-debuggee node back to a node in our
5645     // debuggee compartments. However, if that were true, then the incoming
5646     // cross compartment edge back into a debuggee compartment is already
5647     // listed as an edge in the RootList we started traversal with, and
5648     // therefore we don't need to follow edges to or from this non-debuggee
5649     // node.
5650     JS::Compartment* comp = referent.compartment();
5651     if (comp && !debuggeeCompartments.has(comp)) {
5652       traversal.abandonReferent();
5653       return true;
5654     }
5655 
5656     // If the referent has an associated realm and it's not a debuggee
5657     // realm, skip it. Don't abandonReferent() here like above: realms
5658     // within a compartment can reference each other without going through
5659     // cross-compartment wrappers.
5660     Realm* realm = referent.realm();
5661     if (realm && !dbg->isDebuggeeUnbarriered(realm)) {
5662       return true;
5663     }
5664 
5665     // If the referent is an object and matches our query's restrictions,
5666     // add it to the vector accumulating results. Skip objects that should
5667     // never be exposed to JS, like EnvironmentObjects and internal
5668     // functions.
5669 
5670     if (!referent.is<JSObject>() || referent.exposeToJS().isUndefined()) {
5671       return true;
5672     }
5673 
5674     JSObject* obj = referent.as<JSObject>();
5675 
5676     if (!className.isUndefined()) {
5677       const char* objClassName = obj->getClass()->name;
5678       if (strcmp(objClassName, classNameCString.get()) != 0) {
5679         return true;
5680       }
5681     }
5682 
5683     return objects.append(obj);
5684   }
5685 
5686  private:
5687   /* The context in which we should do our work. */
5688   JSContext* cx;
5689 
5690   /* The debugger for which we conduct queries. */
5691   Debugger* dbg;
5692 
5693   /*
5694    * If this is non-null, matching objects will have a class whose name is
5695    * this property.
5696    */
5697   RootedValue className;
5698 
5699   /* The className member, as a C string. */
5700   UniqueChars classNameCString;
5701 
5702   /*
5703    * Given that either omittedQuery or parseQuery has been called, prepare the
5704    * query for matching objects.
5705    */
prepareQuery()5706   bool prepareQuery() {
5707     if (className.isString()) {
5708       classNameCString = JS_EncodeStringToASCII(cx, className.toString());
5709       if (!classNameCString) {
5710         return false;
5711       }
5712     }
5713 
5714     return true;
5715   }
5716 };
5717 
findObjects()5718 bool Debugger::CallData::findObjects() {
5719   ObjectQuery query(cx, dbg);
5720 
5721   if (args.length() >= 1) {
5722     RootedObject queryObject(cx, RequireObject(cx, args[0]));
5723     if (!queryObject || !query.parseQuery(queryObject)) {
5724       return false;
5725     }
5726   } else {
5727     query.omittedQuery();
5728   }
5729 
5730   if (!query.findObjects()) {
5731     return false;
5732   }
5733 
5734   size_t length = query.objects.length();
5735   RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
5736   if (!result) {
5737     return false;
5738   }
5739 
5740   result->ensureDenseInitializedLength(cx, 0, length);
5741 
5742   for (size_t i = 0; i < length; i++) {
5743     RootedValue debuggeeVal(cx, ObjectValue(*query.objects[i]));
5744     if (!dbg->wrapDebuggeeValue(cx, &debuggeeVal)) {
5745       return false;
5746     }
5747     result->setDenseElement(i, debuggeeVal);
5748   }
5749 
5750   args.rval().setObject(*result);
5751   return true;
5752 }
5753 
findAllGlobals()5754 bool Debugger::CallData::findAllGlobals() {
5755   RootedObjectVector globals(cx);
5756 
5757   {
5758     // Accumulate the list of globals before wrapping them, because
5759     // wrapping can GC and collect realms from under us, while iterating.
5760     JS::AutoCheckCannotGC nogc;
5761 
5762     for (RealmsIter r(cx->runtime()); !r.done(); r.next()) {
5763       if (r->creationOptions().invisibleToDebugger()) {
5764         continue;
5765       }
5766 
5767       if (!r->hasLiveGlobal()) {
5768         continue;
5769       }
5770 
5771       if (JS::RealmBehaviorsRef(r).isNonLive()) {
5772         continue;
5773       }
5774 
5775       r->compartment()->gcState.scheduledForDestruction = false;
5776 
5777       GlobalObject* global = r->maybeGlobal();
5778 
5779       if (cx->runtime()->isSelfHostingGlobal(global)) {
5780         continue;
5781       }
5782 
5783       // We pulled |global| out of nowhere, so it's possible that it was
5784       // marked gray by XPConnect. Since we're now exposing it to JS code,
5785       // we need to mark it black.
5786       JS::ExposeObjectToActiveJS(global);
5787       if (!globals.append(global)) {
5788         return false;
5789       }
5790     }
5791   }
5792 
5793   RootedObject result(cx, NewDenseEmptyArray(cx));
5794   if (!result) {
5795     return false;
5796   }
5797 
5798   for (size_t i = 0; i < globals.length(); i++) {
5799     RootedValue globalValue(cx, ObjectValue(*globals[i]));
5800     if (!dbg->wrapDebuggeeValue(cx, &globalValue)) {
5801       return false;
5802     }
5803     if (!NewbornArrayPush(cx, result, globalValue)) {
5804       return false;
5805     }
5806   }
5807 
5808   args.rval().setObject(*result);
5809   return true;
5810 }
5811 
findSourceURLs()5812 bool Debugger::CallData::findSourceURLs() {
5813   RootedObject result(cx, NewDenseEmptyArray(cx));
5814   if (!result) {
5815     return false;
5816   }
5817 
5818   for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
5819        r.popFront()) {
5820     RootedObject holder(cx, r.front()->getSourceURLsHolder());
5821     if (holder) {
5822       for (size_t i = 0; i < holder->as<ArrayObject>().length(); i++) {
5823         Value v = holder->as<ArrayObject>().getDenseElement(i);
5824 
5825         // The value is an atom and doesn't need wrapping, but the holder may be
5826         // in another zone and the atom must be marked when we create a
5827         // reference in this zone.
5828         MOZ_ASSERT(v.isString() && v.toString()->isAtom());
5829         cx->markAtomValue(v);
5830 
5831         if (!NewbornArrayPush(cx, result, v)) {
5832           return false;
5833         }
5834       }
5835     }
5836   }
5837 
5838   args.rval().setObject(*result);
5839   return true;
5840 }
5841 
makeGlobalObjectReference()5842 bool Debugger::CallData::makeGlobalObjectReference() {
5843   if (!args.requireAtLeast(cx, "Debugger.makeGlobalObjectReference", 1)) {
5844     return false;
5845   }
5846 
5847   Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
5848   if (!global) {
5849     return false;
5850   }
5851 
5852   // If we create a D.O referring to a global in an invisible realm,
5853   // then from it we can reach function objects, scripts, environments, etc.,
5854   // none of which we're ever supposed to see.
5855   if (global->realm()->creationOptions().invisibleToDebugger()) {
5856     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5857                               JSMSG_DEBUG_INVISIBLE_COMPARTMENT);
5858     return false;
5859   }
5860 
5861   args.rval().setObject(*global);
5862   return dbg->wrapDebuggeeValue(cx, args.rval());
5863 }
5864 
isCompilableUnit(JSContext * cx,unsigned argc,Value * vp)5865 bool Debugger::isCompilableUnit(JSContext* cx, unsigned argc, Value* vp) {
5866   CallArgs args = CallArgsFromVp(argc, vp);
5867 
5868   if (!args.requireAtLeast(cx, "Debugger.isCompilableUnit", 1)) {
5869     return false;
5870   }
5871 
5872   if (!args[0].isString()) {
5873     JS_ReportErrorNumberASCII(
5874         cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
5875         "Debugger.isCompilableUnit", "string", InformalValueTypeName(args[0]));
5876     return false;
5877   }
5878 
5879   JSString* str = args[0].toString();
5880   size_t length = str->length();
5881 
5882   AutoStableStringChars chars(cx);
5883   if (!chars.initTwoByte(cx, str)) {
5884     return false;
5885   }
5886 
5887   bool result = true;
5888 
5889   CompileOptions options(cx);
5890   LifoAllocScope allocScope(&cx->tempLifoAlloc());
5891   frontend::CompilationInfo compilationInfo(cx, allocScope, options);
5892   if (!compilationInfo.init(cx)) {
5893     return false;
5894   }
5895 
5896   JS::AutoSuppressWarningReporter suppressWarnings(cx);
5897   frontend::Parser<frontend::FullParseHandler, char16_t> parser(
5898       cx, options, chars.twoByteChars(), length,
5899       /* foldConstants = */ true, compilationInfo, nullptr, nullptr);
5900   if (!parser.checkOptions() || !parser.parse()) {
5901     // We ran into an error. If it was because we ran out of memory we report
5902     // it in the usual way.
5903     if (cx->isThrowingOutOfMemory()) {
5904       return false;
5905     }
5906 
5907     // If it was because we ran out of source, we return false so our caller
5908     // knows to try to collect more [source].
5909     if (parser.isUnexpectedEOF()) {
5910       result = false;
5911     }
5912 
5913     cx->clearPendingException();
5914   }
5915 
5916   args.rval().setBoolean(result);
5917   return true;
5918 }
5919 
adoptDebuggeeValue()5920 bool Debugger::CallData::adoptDebuggeeValue() {
5921   if (!args.requireAtLeast(cx, "Debugger.adoptDebuggeeValue", 1)) {
5922     return false;
5923   }
5924 
5925   RootedValue v(cx, args[0]);
5926   if (v.isObject()) {
5927     RootedObject obj(cx, &v.toObject());
5928     NativeObject* ndobj = ToNativeDebuggerObject(cx, &obj);
5929     if (!ndobj) {
5930       return false;
5931     }
5932 
5933     obj.set(static_cast<JSObject*>(ndobj->getPrivate()));
5934     v = ObjectValue(*obj);
5935 
5936     if (!dbg->wrapDebuggeeValue(cx, &v)) {
5937       return false;
5938     }
5939   }
5940 
5941   args.rval().set(v);
5942   return true;
5943 }
5944 
5945 class DebuggerAdoptSourceMatcher {
5946   JSContext* cx_;
5947   Debugger* dbg_;
5948 
5949  public:
DebuggerAdoptSourceMatcher(JSContext * cx,Debugger * dbg)5950   explicit DebuggerAdoptSourceMatcher(JSContext* cx, Debugger* dbg)
5951       : cx_(cx), dbg_(dbg) {}
5952 
5953   using ReturnType = DebuggerSource*;
5954 
match(HandleScriptSourceObject source)5955   ReturnType match(HandleScriptSourceObject source) {
5956     if (source->compartment() == cx_->compartment()) {
5957       JS_ReportErrorASCII(cx_,
5958                           "Source is in the same compartment as this debugger");
5959       return nullptr;
5960     }
5961     return dbg_->wrapSource(cx_, source);
5962   }
match(Handle<WasmInstanceObject * > wasmInstance)5963   ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
5964     if (wasmInstance->compartment() == cx_->compartment()) {
5965       JS_ReportErrorASCII(
5966           cx_, "WasmInstance is in the same compartment as this debugger");
5967       return nullptr;
5968     }
5969     return dbg_->wrapWasmSource(cx_, wasmInstance);
5970   }
5971 };
5972 
adoptFrame()5973 bool Debugger::CallData::adoptFrame() {
5974   if (!args.requireAtLeast(cx, "Debugger.adoptFrame", 1)) {
5975     return false;
5976   }
5977 
5978   RootedObject obj(cx, RequireObject(cx, args[0]));
5979   if (!obj) {
5980     return false;
5981   }
5982 
5983   obj = UncheckedUnwrap(obj);
5984   if (!obj->is<DebuggerFrame>()) {
5985     JS_ReportErrorASCII(cx, "Argument is not a Debugger.Frame");
5986     return false;
5987   }
5988 
5989   RootedValue objVal(cx, ObjectValue(*obj));
5990   RootedDebuggerFrame frameObj(cx, DebuggerFrame::check(cx, objVal));
5991   if (!frameObj) {
5992     return false;
5993   }
5994 
5995   RootedDebuggerFrame adoptedFrame(cx);
5996   if (frameObj->isOnStack()) {
5997     FrameIter iter(*frameObj->frameIterData());
5998     if (!dbg->observesFrame(iter)) {
5999       JS_ReportErrorASCII(cx, "Debugger.Frame's global is not a debuggee");
6000       return false;
6001     }
6002     if (!dbg->getFrame(cx, iter, &adoptedFrame)) {
6003       return false;
6004     }
6005   } else if (frameObj->hasGenerator()) {
6006     Rooted<AbstractGeneratorObject*> gen(cx, &frameObj->unwrappedGenerator());
6007     if (!dbg->observesGlobal(&gen->global())) {
6008       JS_ReportErrorASCII(cx, "Debugger.Frame's global is not a debuggee");
6009       return false;
6010     }
6011 
6012     if (!dbg->getFrame(cx, gen, &adoptedFrame)) {
6013       return false;
6014     }
6015   } else {
6016     if (!dbg->getFrame(cx, &adoptedFrame)) {
6017       return false;
6018     }
6019   }
6020 
6021   args.rval().setObject(*adoptedFrame);
6022   return true;
6023 }
6024 
adoptSource()6025 bool Debugger::CallData::adoptSource() {
6026   if (!args.requireAtLeast(cx, "Debugger.adoptSource", 1)) {
6027     return false;
6028   }
6029 
6030   RootedObject obj(cx, RequireObject(cx, args[0]));
6031   if (!obj) {
6032     return false;
6033   }
6034 
6035   obj = UncheckedUnwrap(obj);
6036   if (!obj->is<DebuggerSource>()) {
6037     JS_ReportErrorASCII(cx, "Argument is not a Debugger.Source");
6038     return false;
6039   }
6040 
6041   RootedDebuggerSource sourceObj(cx, &obj->as<DebuggerSource>());
6042   if (!sourceObj->getReferentRawObject()) {
6043     JS_ReportErrorASCII(cx, "Argument is Debugger.Source.prototype");
6044     return false;
6045   }
6046 
6047   Rooted<DebuggerSourceReferent> referent(cx, sourceObj->getReferent());
6048 
6049   DebuggerAdoptSourceMatcher matcher(cx, dbg);
6050   DebuggerSource* res = referent.match(matcher);
6051   if (!res) {
6052     return false;
6053   }
6054 
6055   args.rval().setObject(*res);
6056   return true;
6057 }
6058 
6059 const JSPropertySpec Debugger::properties[] = {
6060     JS_DEBUG_PSGS("onDebuggerStatement", getOnDebuggerStatement,
6061                   setOnDebuggerStatement),
6062     JS_DEBUG_PSGS("onExceptionUnwind", getOnExceptionUnwind,
6063                   setOnExceptionUnwind),
6064     JS_DEBUG_PSGS("onNewScript", getOnNewScript, setOnNewScript),
6065     JS_DEBUG_PSGS("onNewPromise", getOnNewPromise, setOnNewPromise),
6066     JS_DEBUG_PSGS("onPromiseSettled", getOnPromiseSettled, setOnPromiseSettled),
6067     JS_DEBUG_PSGS("onEnterFrame", getOnEnterFrame, setOnEnterFrame),
6068     JS_DEBUG_PSGS("onNativeCall", getOnNativeCall, setOnNativeCall),
6069     JS_DEBUG_PSGS("onNewGlobalObject", getOnNewGlobalObject,
6070                   setOnNewGlobalObject),
6071     JS_DEBUG_PSGS("uncaughtExceptionHook", getUncaughtExceptionHook,
6072                   setUncaughtExceptionHook),
6073     JS_DEBUG_PSGS("allowUnobservedAsmJS", getAllowUnobservedAsmJS,
6074                   setAllowUnobservedAsmJS),
6075     JS_DEBUG_PSGS("collectCoverageInfo", getCollectCoverageInfo,
6076                   setCollectCoverageInfo),
6077     JS_DEBUG_PSG("memory", getMemory),
6078     JS_STRING_SYM_PS(toStringTag, "Debugger", JSPROP_READONLY),
6079     JS_PS_END};
6080 
6081 const JSFunctionSpec Debugger::methods[] = {
6082     JS_DEBUG_FN("addDebuggee", addDebuggee, 1),
6083     JS_DEBUG_FN("addAllGlobalsAsDebuggees", addAllGlobalsAsDebuggees, 0),
6084     JS_DEBUG_FN("removeDebuggee", removeDebuggee, 1),
6085     JS_DEBUG_FN("removeAllDebuggees", removeAllDebuggees, 0),
6086     JS_DEBUG_FN("hasDebuggee", hasDebuggee, 1),
6087     JS_DEBUG_FN("getDebuggees", getDebuggees, 0),
6088     JS_DEBUG_FN("getNewestFrame", getNewestFrame, 0),
6089     JS_DEBUG_FN("clearAllBreakpoints", clearAllBreakpoints, 0),
6090     JS_DEBUG_FN("findScripts", findScripts, 1),
6091     JS_DEBUG_FN("findSources", findSources, 1),
6092     JS_DEBUG_FN("findObjects", findObjects, 1),
6093     JS_DEBUG_FN("findAllGlobals", findAllGlobals, 0),
6094     JS_DEBUG_FN("findSourceURLs", findSourceURLs, 0),
6095     JS_DEBUG_FN("makeGlobalObjectReference", makeGlobalObjectReference, 1),
6096     JS_DEBUG_FN("adoptDebuggeeValue", adoptDebuggeeValue, 1),
6097     JS_DEBUG_FN("adoptFrame", adoptFrame, 1),
6098     JS_DEBUG_FN("adoptSource", adoptSource, 1),
6099     JS_FS_END};
6100 
6101 const JSFunctionSpec Debugger::static_methods[]{
6102     JS_FN("isCompilableUnit", Debugger::isCompilableUnit, 1, 0), JS_FS_END};
6103 
newDebuggerScript(JSContext * cx,Handle<DebuggerScriptReferent> referent)6104 DebuggerScript* Debugger::newDebuggerScript(
6105     JSContext* cx, Handle<DebuggerScriptReferent> referent) {
6106   cx->check(object.get());
6107 
6108   RootedObject proto(
6109       cx, &object->getReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO).toObject());
6110   MOZ_ASSERT(proto);
6111   RootedNativeObject debugger(cx, object);
6112 
6113   return DebuggerScript::create(cx, proto, referent, debugger);
6114 }
6115 
6116 template <typename ReferentType, typename Map>
wrapVariantReferent(JSContext * cx,Map & map,Handle<typename Map::WrapperType::ReferentVariant> referent)6117 typename Map::WrapperType* Debugger::wrapVariantReferent(
6118     JSContext* cx, Map& map,
6119     Handle<typename Map::WrapperType::ReferentVariant> referent) {
6120   cx->check(object);
6121 
6122   Handle<ReferentType*> untaggedReferent =
6123       referent.template as<ReferentType*>();
6124   MOZ_ASSERT(cx->compartment() != untaggedReferent->compartment());
6125 
6126   DependentAddPtr<Map> p(cx, map, untaggedReferent);
6127   if (!p) {
6128     typename Map::WrapperType* wrapper = newVariantWrapper(cx, referent);
6129     if (!wrapper) {
6130       return nullptr;
6131     }
6132 
6133     if (!p.add(cx, map, untaggedReferent, wrapper)) {
6134       NukeDebuggerWrapper(wrapper);
6135       return nullptr;
6136     }
6137   }
6138 
6139   return &p->value()->template as<typename Map::WrapperType>();
6140 }
6141 
wrapVariantReferent(JSContext * cx,Handle<DebuggerScriptReferent> referent)6142 DebuggerScript* Debugger::wrapVariantReferent(
6143     JSContext* cx, Handle<DebuggerScriptReferent> referent) {
6144   if (referent.is<BaseScript*>()) {
6145     return wrapVariantReferent<BaseScript>(cx, scripts, referent);
6146   }
6147 
6148   return wrapVariantReferent<WasmInstanceObject>(cx, wasmInstanceScripts,
6149                                                  referent);
6150 }
6151 
wrapScript(JSContext * cx,Handle<BaseScript * > script)6152 DebuggerScript* Debugger::wrapScript(JSContext* cx,
6153                                      Handle<BaseScript*> script) {
6154   Rooted<DebuggerScriptReferent> referent(cx,
6155                                           DebuggerScriptReferent(script.get()));
6156   return wrapVariantReferent(cx, referent);
6157 }
6158 
wrapWasmScript(JSContext * cx,Handle<WasmInstanceObject * > wasmInstance)6159 DebuggerScript* Debugger::wrapWasmScript(
6160     JSContext* cx, Handle<WasmInstanceObject*> wasmInstance) {
6161   Rooted<DebuggerScriptReferent> referent(cx, wasmInstance.get());
6162   return wrapVariantReferent(cx, referent);
6163 }
6164 
newDebuggerSource(JSContext * cx,Handle<DebuggerSourceReferent> referent)6165 DebuggerSource* Debugger::newDebuggerSource(
6166     JSContext* cx, Handle<DebuggerSourceReferent> referent) {
6167   cx->check(object.get());
6168 
6169   RootedObject proto(
6170       cx, &object->getReservedSlot(JSSLOT_DEBUG_SOURCE_PROTO).toObject());
6171   MOZ_ASSERT(proto);
6172   RootedNativeObject debugger(cx, object);
6173   return DebuggerSource::create(cx, proto, referent, debugger);
6174 }
6175 
wrapVariantReferent(JSContext * cx,Handle<DebuggerSourceReferent> referent)6176 DebuggerSource* Debugger::wrapVariantReferent(
6177     JSContext* cx, Handle<DebuggerSourceReferent> referent) {
6178   DebuggerSource* obj;
6179   if (referent.is<ScriptSourceObject*>()) {
6180     obj = wrapVariantReferent<ScriptSourceObject>(cx, sources, referent);
6181   } else {
6182     obj = wrapVariantReferent<WasmInstanceObject>(cx, wasmInstanceSources,
6183                                                   referent);
6184   }
6185   MOZ_ASSERT_IF(obj, obj->getReferent() == referent);
6186   return obj;
6187 }
6188 
wrapSource(JSContext * cx,HandleScriptSourceObject source)6189 DebuggerSource* Debugger::wrapSource(JSContext* cx,
6190                                      HandleScriptSourceObject source) {
6191   Rooted<DebuggerSourceReferent> referent(cx, source.get());
6192   return wrapVariantReferent(cx, referent);
6193 }
6194 
wrapWasmSource(JSContext * cx,Handle<WasmInstanceObject * > wasmInstance)6195 DebuggerSource* Debugger::wrapWasmSource(
6196     JSContext* cx, Handle<WasmInstanceObject*> wasmInstance) {
6197   Rooted<DebuggerSourceReferent> referent(cx, wasmInstance.get());
6198   return wrapVariantReferent(cx, referent);
6199 }
6200 
getScriptInstrumentationId(JSContext * cx,HandleObject dbgObject,HandleScript script,MutableHandleValue rval)6201 bool DebugAPI::getScriptInstrumentationId(JSContext* cx, HandleObject dbgObject,
6202                                           HandleScript script,
6203                                           MutableHandleValue rval) {
6204   Debugger* dbg = Debugger::fromJSObject(dbgObject);
6205   DebuggerScript* dbgScript = dbg->wrapScript(cx, script);
6206   if (!dbgScript) {
6207     return false;
6208   }
6209   rval.set(dbgScript->getInstrumentationId());
6210   return true;
6211 }
6212 
observesFrame(AbstractFramePtr frame) const6213 bool Debugger::observesFrame(AbstractFramePtr frame) const {
6214   if (frame.isWasmDebugFrame()) {
6215     return observesWasm(frame.wasmInstance());
6216   }
6217 
6218   return observesScript(frame.script());
6219 }
6220 
observesFrame(const FrameIter & iter) const6221 bool Debugger::observesFrame(const FrameIter& iter) const {
6222   // Skip frames not yet fully initialized during their prologue.
6223   if (iter.isInterp() && iter.isFunctionFrame()) {
6224     const Value& thisVal = iter.interpFrame()->thisArgument();
6225     if (thisVal.isMagic() && thisVal.whyMagic() == JS_IS_CONSTRUCTING) {
6226       return false;
6227     }
6228   }
6229   if (iter.isWasm()) {
6230     // Skip frame of wasm instances we cannot observe.
6231     if (!iter.wasmDebugEnabled()) {
6232       return false;
6233     }
6234     return observesWasm(iter.wasmInstance());
6235   }
6236   return observesScript(iter.script());
6237 }
6238 
observesScript(JSScript * script) const6239 bool Debugger::observesScript(JSScript* script) const {
6240   // Don't ever observe self-hosted scripts: the Debugger API can break
6241   // self-hosted invariants.
6242   return observesGlobal(&script->global()) && !script->selfHosted();
6243 }
6244 
observesWasm(wasm::Instance * instance) const6245 bool Debugger::observesWasm(wasm::Instance* instance) const {
6246   if (!instance->debugEnabled()) {
6247     return false;
6248   }
6249   return observesGlobal(&instance->object()->global());
6250 }
6251 
6252 /* static */
replaceFrameGuts(JSContext * cx,AbstractFramePtr from,AbstractFramePtr to,ScriptFrameIter & iter)6253 bool Debugger::replaceFrameGuts(JSContext* cx, AbstractFramePtr from,
6254                                 AbstractFramePtr to, ScriptFrameIter& iter) {
6255   auto removeFromDebuggerFramesOnExit = MakeScopeExit([&] {
6256     // Remove any remaining old entries on exit, as the 'from' frame will
6257     // be gone. This is only done in the failure case. On failure, the
6258     // removeToDebuggerFramesOnExit lambda below will rollback any frames
6259     // that were replaced, resulting in !frameMaps(to). On success, the
6260     // range will be empty, as all from Frame.Debugger instances will have
6261     // been removed.
6262     MOZ_ASSERT_IF(DebugAPI::inFrameMaps(to), !DebugAPI::inFrameMaps(from));
6263     removeFromFrameMapsAndClearBreakpointsIn(cx, from);
6264 
6265     // Rekey missingScopes to maintain Debugger.Environment identity and
6266     // forward liveScopes to point to the new frame.
6267     DebugEnvironments::forwardLiveFrame(cx, from, to);
6268   });
6269 
6270   // Forward live Debugger.Frame objects.
6271   Rooted<DebuggerFrameVector> frames(cx, DebuggerFrameVector(cx));
6272   if (!getDebuggerFrames(from, &frames)) {
6273     // An OOM here means that all Debuggers' frame maps still contain
6274     // entries for 'from' and no entries for 'to'. Since the 'from' frame
6275     // will be gone, they are removed by removeFromDebuggerFramesOnExit
6276     // above.
6277     return false;
6278   }
6279 
6280   // If during the loop below we hit an OOM, we must also rollback any of
6281   // the frames that were successfully replaced. For OSR frames, OOM here
6282   // means those frames will pop from the OSR trampoline, which does not
6283   // call Debugger::onLeaveFrame.
6284   auto removeToDebuggerFramesOnExit =
6285       MakeScopeExit([&] { removeFromFrameMapsAndClearBreakpointsIn(cx, to); });
6286 
6287   for (size_t i = 0; i < frames.length(); i++) {
6288     HandleDebuggerFrame frameobj = frames[i];
6289     Debugger* dbg = Debugger::fromChildJSObject(frameobj);
6290 
6291     // Update frame object's ScriptFrameIter::data pointer.
6292     frameobj->freeFrameIterData(cx->runtime()->defaultFreeOp());
6293     ScriptFrameIter::Data* data = iter.copyData();
6294     if (!data) {
6295       // An OOM here means that some Debuggers' frame maps may still
6296       // contain entries for 'from' and some Debuggers' frame maps may
6297       // also contain entries for 'to'. Thus both
6298       // removeFromDebuggerFramesOnExit and
6299       // removeToDebuggerFramesOnExit must both run.
6300       //
6301       // The current frameobj in question is still in its Debugger's
6302       // frame map keyed by 'from', so it will be covered by
6303       // removeFromDebuggerFramesOnExit.
6304       return false;
6305     }
6306     frameobj->setFrameIterData(data);
6307 
6308     // Remove old frame.
6309     dbg->frames.remove(from);
6310 
6311     // Add the frame object with |to| as key.
6312     if (!dbg->frames.putNew(to, frameobj)) {
6313       // This OOM is subtle. At this point, both
6314       // removeFromDebuggerFramesOnExit and removeToDebuggerFramesOnExit
6315       // must both run for the same reason given above.
6316       //
6317       // The difference is that the current frameobj is no longer in its
6318       // Debugger's frame map, so it will not be cleaned up by neither
6319       // lambda. Manually clean it up here.
6320       JSFreeOp* fop = cx->runtime()->defaultFreeOp();
6321       frameobj->freeFrameIterData(fop);
6322       frameobj->maybeDecrementStepperCounter(fop, to);
6323 
6324       ReportOutOfMemory(cx);
6325       return false;
6326     }
6327   }
6328 
6329   // All frames successfuly replaced, cancel the rollback.
6330   removeToDebuggerFramesOnExit.release();
6331 
6332   return true;
6333 }
6334 
6335 /* static */
inFrameMaps(AbstractFramePtr frame)6336 bool DebugAPI::inFrameMaps(AbstractFramePtr frame) {
6337   bool foundAny = false;
6338   Debugger::forEachDebuggerFrame(
6339       frame, [&](DebuggerFrame* frameobj) { foundAny = true; });
6340   return foundAny;
6341 }
6342 
6343 /* static */
removeFromFrameMapsAndClearBreakpointsIn(JSContext * cx,AbstractFramePtr frame,bool suspending)6344 void Debugger::removeFromFrameMapsAndClearBreakpointsIn(JSContext* cx,
6345                                                         AbstractFramePtr frame,
6346                                                         bool suspending) {
6347   forEachDebuggerFrame(frame, [&](DebuggerFrame* frameobj) {
6348     JSFreeOp* fop = cx->runtime()->defaultFreeOp();
6349     frameobj->freeFrameIterData(fop);
6350 
6351     Debugger* dbg = Debugger::fromChildJSObject(frameobj);
6352     dbg->frames.remove(frame);
6353 
6354     if (frameobj->hasGenerator()) {
6355       // If this is a generator's final pop, remove its entry from
6356       // generatorFrames. Such an entry exists if and only if the
6357       // Debugger.Frame's generator has been set.
6358       if (!suspending) {
6359         // Terminally exiting a generator.
6360 #if DEBUG
6361         AbstractGeneratorObject& genObj = frameobj->unwrappedGenerator();
6362         GeneratorWeakMap::Ptr p = dbg->generatorFrames.lookup(&genObj);
6363         MOZ_ASSERT(p);
6364         MOZ_ASSERT(p->value() == frameobj);
6365 #endif
6366         frameobj->clearGenerator(fop, dbg);
6367       }
6368     } else {
6369       frameobj->maybeDecrementStepperCounter(fop, frame);
6370     }
6371   });
6372 
6373   // If this is an eval frame, then from the debugger's perspective the
6374   // script is about to be destroyed. Remove any breakpoints in it.
6375   if (frame.isEvalFrame()) {
6376     RootedScript script(cx, frame.script());
6377     DebugScript::clearBreakpointsIn(cx->runtime()->defaultFreeOp(), script,
6378                                     nullptr, nullptr);
6379   }
6380 }
6381 
getDebuggeeLink()6382 DebuggerDebuggeeLink* Debugger::getDebuggeeLink() {
6383   return &object->getReservedSlot(JSSLOT_DEBUG_DEBUGGEE_LINK)
6384               .toObject()
6385               .as<DebuggerDebuggeeLink>();
6386 }
6387 
setLinkSlot(Debugger & dbg)6388 void DebuggerDebuggeeLink::setLinkSlot(Debugger& dbg) {
6389   setReservedSlot(DEBUGGER_LINK_SLOT, ObjectValue(*dbg.toJSObject()));
6390 }
6391 
clearLinkSlot()6392 void DebuggerDebuggeeLink::clearLinkSlot() {
6393   setReservedSlot(DEBUGGER_LINK_SLOT, UndefinedValue());
6394 }
6395 
6396 const JSClass DebuggerDebuggeeLink::class_ = {
6397     "DebuggerDebuggeeLink", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS)};
6398 
6399 /* static */
handleBaselineOsr(JSContext * cx,InterpreterFrame * from,jit::BaselineFrame * to)6400 bool DebugAPI::handleBaselineOsr(JSContext* cx, InterpreterFrame* from,
6401                                  jit::BaselineFrame* to) {
6402   ScriptFrameIter iter(cx);
6403   MOZ_ASSERT(iter.abstractFramePtr() == to);
6404   return Debugger::replaceFrameGuts(cx, from, to, iter);
6405 }
6406 
6407 /* static */
handleIonBailout(JSContext * cx,jit::RematerializedFrame * from,jit::BaselineFrame * to)6408 bool DebugAPI::handleIonBailout(JSContext* cx, jit::RematerializedFrame* from,
6409                                 jit::BaselineFrame* to) {
6410   // When we return to a bailed-out Ion real frame, we must update all
6411   // Debugger.Frames that refer to its inline frames. However, since we
6412   // can't pop individual inline frames off the stack (we can only pop the
6413   // real frame that contains them all, as a unit), we cannot assume that
6414   // the frame we're dealing with is the top frame. Advance the iterator
6415   // across any inlined frames younger than |to|, the baseline frame
6416   // reconstructed during bailout from the Ion frame corresponding to
6417   // |from|.
6418   ScriptFrameIter iter(cx);
6419   while (iter.abstractFramePtr() != to) {
6420     ++iter;
6421   }
6422   return Debugger::replaceFrameGuts(cx, from, to, iter);
6423 }
6424 
6425 /* static */
handleUnrecoverableIonBailoutError(JSContext * cx,jit::RematerializedFrame * frame)6426 void DebugAPI::handleUnrecoverableIonBailoutError(
6427     JSContext* cx, jit::RematerializedFrame* frame) {
6428   // Ion bailout can fail due to overrecursion. In such cases we cannot
6429   // honor any further Debugger hooks on the frame, and need to ensure that
6430   // its Debugger.Frame entry is cleaned up.
6431   Debugger::removeFromFrameMapsAndClearBreakpointsIn(cx, frame);
6432 }
6433 
6434 /*** JS::dbg::Builder *******************************************************/
6435 
Builder(JSContext * cx,js::Debugger * debugger)6436 Builder::Builder(JSContext* cx, js::Debugger* debugger)
6437     : debuggerObject(cx, debugger->toJSObject().get()), debugger(debugger) {}
6438 
6439 #if DEBUG
assertBuilt(JSObject * obj)6440 void Builder::assertBuilt(JSObject* obj) {
6441   // We can't use assertSameCompartment here, because that is always keyed to
6442   // some JSContext's current compartment, whereas BuiltThings can be
6443   // constructed and assigned to without respect to any particular context;
6444   // the only constraint is that they should be in their debugger's compartment.
6445   MOZ_ASSERT_IF(obj, debuggerObject->compartment() == obj->compartment());
6446 }
6447 #endif
6448 
definePropertyToTrusted(JSContext * cx,const char * name,JS::MutableHandleValue trusted)6449 bool Builder::Object::definePropertyToTrusted(JSContext* cx, const char* name,
6450                                               JS::MutableHandleValue trusted) {
6451   // We should have checked for false Objects before calling this.
6452   MOZ_ASSERT(value);
6453 
6454   JSAtom* atom = Atomize(cx, name, strlen(name));
6455   if (!atom) {
6456     return false;
6457   }
6458   RootedId id(cx, AtomToId(atom));
6459 
6460   return DefineDataProperty(cx, value, id, trusted);
6461 }
6462 
defineProperty(JSContext * cx,const char * name,JS::HandleValue propval_)6463 bool Builder::Object::defineProperty(JSContext* cx, const char* name,
6464                                      JS::HandleValue propval_) {
6465   AutoRealm ar(cx, debuggerObject());
6466 
6467   RootedValue propval(cx, propval_);
6468   if (!debugger()->wrapDebuggeeValue(cx, &propval)) {
6469     return false;
6470   }
6471 
6472   return definePropertyToTrusted(cx, name, &propval);
6473 }
6474 
defineProperty(JSContext * cx,const char * name,JS::HandleObject propval_)6475 bool Builder::Object::defineProperty(JSContext* cx, const char* name,
6476                                      JS::HandleObject propval_) {
6477   RootedValue propval(cx, ObjectOrNullValue(propval_));
6478   return defineProperty(cx, name, propval);
6479 }
6480 
defineProperty(JSContext * cx,const char * name,Builder::Object & propval_)6481 bool Builder::Object::defineProperty(JSContext* cx, const char* name,
6482                                      Builder::Object& propval_) {
6483   AutoRealm ar(cx, debuggerObject());
6484 
6485   RootedValue propval(cx, ObjectOrNullValue(propval_.value));
6486   return definePropertyToTrusted(cx, name, &propval);
6487 }
6488 
newObject(JSContext * cx)6489 Builder::Object Builder::newObject(JSContext* cx) {
6490   AutoRealm ar(cx, debuggerObject);
6491 
6492   RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
6493 
6494   // If the allocation failed, this will return a false Object, as the spec
6495   // promises.
6496   return Object(cx, *this, obj);
6497 }
6498 
6499 /*** JS::dbg::AutoEntryMonitor **********************************************/
6500 
AutoEntryMonitor(JSContext * cx)6501 AutoEntryMonitor::AutoEntryMonitor(JSContext* cx)
6502     : cx_(cx), savedMonitor_(cx->entryMonitor) {
6503   cx->entryMonitor = this;
6504 }
6505 
~AutoEntryMonitor()6506 AutoEntryMonitor::~AutoEntryMonitor() { cx_->entryMonitor = savedMonitor_; }
6507 
6508 /*** Glue *******************************************************************/
6509 
JS_DefineDebuggerObject(JSContext * cx,HandleObject obj)6510 extern JS_PUBLIC_API bool JS_DefineDebuggerObject(JSContext* cx,
6511                                                   HandleObject obj) {
6512   RootedNativeObject debugCtor(cx), debugProto(cx), frameProto(cx),
6513       scriptProto(cx), sourceProto(cx), objectProto(cx), envProto(cx),
6514       memoryProto(cx);
6515   RootedObject debuggeeWouldRunProto(cx);
6516   RootedValue debuggeeWouldRunCtor(cx);
6517   Handle<GlobalObject*> global = obj.as<GlobalObject>();
6518 
6519   debugProto =
6520       InitClass(cx, global, nullptr, &DebuggerInstanceObject::class_,
6521                 Debugger::construct, 1, Debugger::properties, Debugger::methods,
6522                 nullptr, Debugger::static_methods, debugCtor.address());
6523   if (!debugProto) {
6524     return false;
6525   }
6526 
6527   frameProto = DebuggerFrame::initClass(cx, global, debugCtor);
6528   if (!frameProto) {
6529     return false;
6530   }
6531 
6532   scriptProto = DebuggerScript::initClass(cx, global, debugCtor);
6533   if (!scriptProto) {
6534     return false;
6535   }
6536 
6537   sourceProto = DebuggerSource::initClass(cx, global, debugCtor);
6538   if (!sourceProto) {
6539     return false;
6540   }
6541 
6542   objectProto = DebuggerObject::initClass(cx, global, debugCtor);
6543   if (!objectProto) {
6544     return false;
6545   }
6546 
6547   envProto = DebuggerEnvironment::initClass(cx, global, debugCtor);
6548   if (!envProto) {
6549     return false;
6550   }
6551 
6552   memoryProto =
6553       InitClass(cx, debugCtor, nullptr, &DebuggerMemory::class_,
6554                 DebuggerMemory::construct, 0, DebuggerMemory::properties,
6555                 DebuggerMemory::methods, nullptr, nullptr);
6556   if (!memoryProto) {
6557     return false;
6558   }
6559 
6560   debuggeeWouldRunProto = GlobalObject::getOrCreateCustomErrorPrototype(
6561       cx, global, JSEXN_DEBUGGEEWOULDRUN);
6562   if (!debuggeeWouldRunProto) {
6563     return false;
6564   }
6565   debuggeeWouldRunCtor = global->getConstructor(JSProto_DebuggeeWouldRun);
6566   RootedId debuggeeWouldRunId(
6567       cx, NameToId(ClassName(JSProto_DebuggeeWouldRun, cx)));
6568   if (!DefineDataProperty(cx, debugCtor, debuggeeWouldRunId,
6569                           debuggeeWouldRunCtor, 0)) {
6570     return false;
6571   }
6572 
6573   debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_FRAME_PROTO,
6574                               ObjectValue(*frameProto));
6575   debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_OBJECT_PROTO,
6576                               ObjectValue(*objectProto));
6577   debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SCRIPT_PROTO,
6578                               ObjectValue(*scriptProto));
6579   debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SOURCE_PROTO,
6580                               ObjectValue(*sourceProto));
6581   debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_ENV_PROTO,
6582                               ObjectValue(*envProto));
6583   debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO,
6584                               ObjectValue(*memoryProto));
6585   return true;
6586 }
6587 
IsDebugger(JSObject & obj)6588 JS_PUBLIC_API bool JS::dbg::IsDebugger(JSObject& obj) {
6589   /* We only care about debugger objects, so CheckedUnwrapStatic is OK. */
6590   JSObject* unwrapped = CheckedUnwrapStatic(&obj);
6591   return unwrapped && unwrapped->is<DebuggerInstanceObject>() &&
6592          js::Debugger::fromJSObject(unwrapped) != nullptr;
6593 }
6594 
GetDebuggeeGlobals(JSContext * cx,JSObject & dbgObj,MutableHandleObjectVector vector)6595 JS_PUBLIC_API bool JS::dbg::GetDebuggeeGlobals(
6596     JSContext* cx, JSObject& dbgObj, MutableHandleObjectVector vector) {
6597   MOZ_ASSERT(IsDebugger(dbgObj));
6598   /* Since we know we have a debugger object, CheckedUnwrapStatic is fine. */
6599   js::Debugger* dbg = js::Debugger::fromJSObject(CheckedUnwrapStatic(&dbgObj));
6600 
6601   if (!vector.reserve(vector.length() + dbg->debuggees.count())) {
6602     JS_ReportOutOfMemory(cx);
6603     return false;
6604   }
6605 
6606   for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
6607        r.popFront()) {
6608     vector.infallibleAppend(static_cast<JSObject*>(r.front()));
6609   }
6610 
6611   return true;
6612 }
6613 
6614 #ifdef DEBUG
6615 /* static */
isDebuggerCrossCompartmentEdge(JSObject * obj,const gc::Cell * target)6616 bool Debugger::isDebuggerCrossCompartmentEdge(JSObject* obj,
6617                                               const gc::Cell* target) {
6618   MOZ_ASSERT(target);
6619 
6620   const gc::Cell* referent = nullptr;
6621   if (obj->is<DebuggerScript>()) {
6622     referent = obj->as<DebuggerScript>().getReferentCell();
6623   } else if (obj->is<DebuggerSource>()) {
6624     referent = obj->as<DebuggerSource>().getReferentRawObject();
6625   } else if (obj->is<DebuggerObject>()) {
6626     referent = static_cast<gc::Cell*>(obj->as<DebuggerObject>().getPrivate());
6627   } else if (obj->is<DebuggerEnvironment>()) {
6628     referent =
6629         static_cast<gc::Cell*>(obj->as<DebuggerEnvironment>().getPrivate());
6630   }
6631 
6632   return referent == target;
6633 }
6634 
CheckDebuggeeThingRealm(Realm * realm,bool invisibleOk)6635 static void CheckDebuggeeThingRealm(Realm* realm, bool invisibleOk) {
6636   MOZ_ASSERT(!realm->creationOptions().mergeable());
6637   MOZ_ASSERT_IF(!invisibleOk, !realm->creationOptions().invisibleToDebugger());
6638 }
6639 
CheckDebuggeeThing(BaseScript * script,bool invisibleOk)6640 void js::CheckDebuggeeThing(BaseScript* script, bool invisibleOk) {
6641   CheckDebuggeeThingRealm(script->realm(), invisibleOk);
6642 }
6643 
CheckDebuggeeThing(JSObject * obj,bool invisibleOk)6644 void js::CheckDebuggeeThing(JSObject* obj, bool invisibleOk) {
6645   if (Realm* realm = JS::GetObjectRealmOrNull(obj)) {
6646     CheckDebuggeeThingRealm(realm, invisibleOk);
6647   }
6648 }
6649 #endif  // DEBUG
6650 
6651 /*** JS::dbg::GarbageCollectionEvent ****************************************/
6652 
6653 namespace JS {
6654 namespace dbg {
6655 
Create(JSRuntime * rt,::js::gcstats::Statistics & stats,uint64_t gcNumber)6656 /* static */ GarbageCollectionEvent::Ptr GarbageCollectionEvent::Create(
6657     JSRuntime* rt, ::js::gcstats::Statistics& stats, uint64_t gcNumber) {
6658   auto data = MakeUnique<GarbageCollectionEvent>(gcNumber);
6659   if (!data) {
6660     return nullptr;
6661   }
6662 
6663   data->nonincrementalReason = stats.nonincrementalReason();
6664 
6665   for (auto& slice : stats.slices()) {
6666     if (!data->reason) {
6667       // There is only one GC reason for the whole cycle, but for legacy
6668       // reasons this data is stored and replicated on each slice. Each
6669       // slice used to have its own GCReason, but now they are all the
6670       // same.
6671       data->reason = ExplainGCReason(slice.reason);
6672       MOZ_ASSERT(data->reason);
6673     }
6674 
6675     if (!data->collections.growBy(1)) {
6676       return nullptr;
6677     }
6678 
6679     data->collections.back().startTimestamp = slice.start;
6680     data->collections.back().endTimestamp = slice.end;
6681   }
6682 
6683   return data;
6684 }
6685 
DefineStringProperty(JSContext * cx,HandleObject obj,PropertyName * propName,const char * strVal)6686 static bool DefineStringProperty(JSContext* cx, HandleObject obj,
6687                                  PropertyName* propName, const char* strVal) {
6688   RootedValue val(cx, UndefinedValue());
6689   if (strVal) {
6690     JSAtom* atomized = Atomize(cx, strVal, strlen(strVal));
6691     if (!atomized) {
6692       return false;
6693     }
6694     val = StringValue(atomized);
6695   }
6696   return DefineDataProperty(cx, obj, propName, val);
6697 }
6698 
toJSObject(JSContext * cx) const6699 JSObject* GarbageCollectionEvent::toJSObject(JSContext* cx) const {
6700   RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
6701   RootedValue gcCycleNumberVal(cx, NumberValue(majorGCNumber_));
6702   if (!obj ||
6703       !DefineStringProperty(cx, obj, cx->names().nonincrementalReason,
6704                             nonincrementalReason) ||
6705       !DefineStringProperty(cx, obj, cx->names().reason, reason) ||
6706       !DefineDataProperty(cx, obj, cx->names().gcCycleNumber,
6707                           gcCycleNumberVal)) {
6708     return nullptr;
6709   }
6710 
6711   RootedArrayObject slicesArray(cx, NewDenseEmptyArray(cx));
6712   if (!slicesArray) {
6713     return nullptr;
6714   }
6715 
6716   TimeStamp originTime = TimeStamp::ProcessCreation();
6717 
6718   size_t idx = 0;
6719   for (auto range = collections.all(); !range.empty(); range.popFront()) {
6720     RootedPlainObject collectionObj(cx,
6721                                     NewBuiltinClassInstance<PlainObject>(cx));
6722     if (!collectionObj) {
6723       return nullptr;
6724     }
6725 
6726     RootedValue start(cx), end(cx);
6727     start = NumberValue(
6728         (range.front().startTimestamp - originTime).ToMilliseconds());
6729     end =
6730         NumberValue((range.front().endTimestamp - originTime).ToMilliseconds());
6731     if (!DefineDataProperty(cx, collectionObj, cx->names().startTimestamp,
6732                             start) ||
6733         !DefineDataProperty(cx, collectionObj, cx->names().endTimestamp, end)) {
6734       return nullptr;
6735     }
6736 
6737     RootedValue collectionVal(cx, ObjectValue(*collectionObj));
6738     if (!DefineDataElement(cx, slicesArray, idx++, collectionVal)) {
6739       return nullptr;
6740     }
6741   }
6742 
6743   RootedValue slicesValue(cx, ObjectValue(*slicesArray));
6744   if (!DefineDataProperty(cx, obj, cx->names().collections, slicesValue)) {
6745     return nullptr;
6746   }
6747 
6748   return obj;
6749 }
6750 
FireOnGarbageCollectionHookRequired(JSContext * cx)6751 JS_PUBLIC_API bool FireOnGarbageCollectionHookRequired(JSContext* cx) {
6752   AutoCheckCannotGC noGC;
6753 
6754   for (Debugger* dbg : cx->runtime()->debuggerList()) {
6755     if (dbg->observedGC(cx->runtime()->gc.majorGCCount()) &&
6756         dbg->getHook(Debugger::OnGarbageCollection)) {
6757       return true;
6758     }
6759   }
6760 
6761   return false;
6762 }
6763 
FireOnGarbageCollectionHook(JSContext * cx,JS::dbg::GarbageCollectionEvent::Ptr && data)6764 JS_PUBLIC_API bool FireOnGarbageCollectionHook(
6765     JSContext* cx, JS::dbg::GarbageCollectionEvent::Ptr&& data) {
6766   RootedObjectVector triggered(cx);
6767 
6768   {
6769     // We had better not GC (and potentially get a dangling Debugger
6770     // pointer) while finding all Debuggers observing a debuggee that
6771     // participated in this GC.
6772     AutoCheckCannotGC noGC;
6773 
6774     for (Debugger* dbg : cx->runtime()->debuggerList()) {
6775       if (dbg->observedGC(data->majorGCNumber()) &&
6776           dbg->getHook(Debugger::OnGarbageCollection)) {
6777         if (!triggered.append(dbg->object)) {
6778           JS_ReportOutOfMemory(cx);
6779           return false;
6780         }
6781       }
6782     }
6783   }
6784 
6785   for (; !triggered.empty(); triggered.popBack()) {
6786     Debugger* dbg = Debugger::fromJSObject(triggered.back());
6787 
6788     mozilla::Unused << dbg->enterDebuggerHook(cx, [&]() -> bool {
6789       return dbg->fireOnGarbageCollectionHook(cx, data);
6790     });
6791     MOZ_ASSERT(!cx->isExceptionPending());
6792   }
6793 
6794   return true;
6795 }
6796 
6797 }  // namespace dbg
6798 }  // namespace JS
6799