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