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