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