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