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