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