SavedStacks.cpp (74416B)
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 "vm/SavedStacks.h" 8 9 #include "mozilla/Attributes.h" 10 #include "mozilla/DebugOnly.h" 11 12 #include <algorithm> 13 #include <utility> 14 15 #include "jsapi.h" 16 #include "jsmath.h" 17 #include "jsnum.h" 18 19 #include "gc/GCContext.h" 20 #include "gc/HashUtil.h" 21 #include "js/CharacterEncoding.h" 22 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin 23 #include "js/ErrorReport.h" // JSErrorBase 24 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 25 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty 26 #include "js/PropertySpec.h" 27 #include "js/SavedFrameAPI.h" 28 #include "js/Stack.h" 29 #include "js/Vector.h" 30 #include "util/DifferentialTesting.h" 31 #include "util/StringBuilder.h" 32 #include "vm/Compartment.h" 33 #include "vm/FrameIter.h" 34 #include "vm/GeckoProfiler.h" 35 #include "vm/JSScript.h" 36 #include "vm/Realm.h" 37 #include "vm/SavedFrame.h" 38 #include "vm/WrapperObject.h" 39 40 #include "debugger/DebugAPI-inl.h" 41 #include "gc/StableCellHasher-inl.h" 42 #include "vm/GeckoProfiler-inl.h" 43 #include "vm/JSContext-inl.h" 44 45 using mozilla::AddToHash; 46 using mozilla::DebugOnly; 47 using mozilla::Maybe; 48 using mozilla::Nothing; 49 using mozilla::Some; 50 51 namespace js { 52 53 /** 54 * Maximum number of saved frames returned for an async stack. 55 */ 56 const uint32_t ASYNC_STACK_MAX_FRAME_COUNT = 60; 57 58 void LiveSavedFrameCache::trace(JSTracer* trc) { 59 if (!initialized()) { 60 return; 61 } 62 63 for (auto* entry = frames->begin(); entry < frames->end(); entry++) { 64 TraceEdge(trc, &entry->savedFrame, 65 "LiveSavedFrameCache::frames SavedFrame"); 66 } 67 } 68 69 bool LiveSavedFrameCache::insert(JSContext* cx, FramePtr&& framePtr, 70 const jsbytecode* pc, 71 Handle<SavedFrame*> savedFrame) { 72 MOZ_ASSERT(savedFrame); 73 MOZ_ASSERT(initialized()); 74 75 #ifdef DEBUG 76 // There should not already be an entry for this frame. Checking the full 77 // stack really slows down some tests, so just check the first and last five 78 // hundred. 79 size_t limit = std::min(frames->length() / 2, size_t(500)); 80 for (size_t i = 0; i < limit; i++) { 81 MOZ_ASSERT(Key(framePtr) != (*frames)[i].key); 82 MOZ_ASSERT(Key(framePtr) != (*frames)[frames->length() - 1 - i].key); 83 } 84 #endif 85 86 if (!frames->emplaceBack(framePtr, pc, savedFrame)) { 87 ReportOutOfMemory(cx); 88 return false; 89 } 90 91 framePtr.setHasCachedSavedFrame(); 92 93 return true; 94 } 95 96 void LiveSavedFrameCache::find(JSContext* cx, FramePtr& framePtr, 97 const jsbytecode* pc, 98 MutableHandle<SavedFrame*> frame) const { 99 MOZ_ASSERT(initialized()); 100 MOZ_ASSERT(framePtr.hasCachedSavedFrame()); 101 102 // The assertions here check that either 1) frames' hasCachedSavedFrame flags 103 // accurately indicate the presence of a cache entry for that frame (ignoring 104 // pc mismatches), or 2) the cache is completely empty, having been flushed 105 // for a realm mismatch. 106 107 // If we flushed the cache due to a realm mismatch, then we shouldn't 108 // expect to find any frames in the cache. 109 if (frames->empty()) { 110 frame.set(nullptr); 111 return; 112 } 113 114 // All our SavedFrames should be in the same realm. If the last 115 // entry's SavedFrame's realm doesn't match cx's, flush the cache. 116 if (frames->back().savedFrame->realm() != cx->realm()) { 117 #ifdef DEBUG 118 // Check that they are, indeed, all in the same realm. 119 auto realm = frames->back().savedFrame->realm(); 120 for (const auto& f : (*frames)) { 121 MOZ_ASSERT(realm == f.savedFrame->realm()); 122 } 123 #endif 124 frames->clear(); 125 frame.set(nullptr); 126 return; 127 } 128 129 Key key(framePtr); 130 while (key != frames->back().key) { 131 MOZ_ASSERT(frames->back().savedFrame->realm() == cx->realm()); 132 133 // framePtr must have an entry, but apparently it's below this one on the 134 // stack; frames->back() must correspond to a frame younger than framePtr's. 135 // SavedStacks::insertFrames is going to push new cache entries for 136 // everything younger than framePtr, so this entry should be popped. 137 frames->popBack(); 138 139 // If the frame's bit was set, the frame should always have an entry in 140 // the cache. (If we purged the entire cache because its SavedFrames had 141 // been captured for a different realm, then we would have 142 // returned early above.) 143 MOZ_RELEASE_ASSERT(!frames->empty()); 144 } 145 146 // The youngest valid frame may have run some code, so its current pc may 147 // not match its cache entry's pc. In this case, just treat it as a miss. No 148 // older frame has executed any code; it would have been necessary to pop 149 // this frame for that to happen, but this frame's bit is set. 150 if (pc != frames->back().pc) { 151 frames->popBack(); 152 frame.set(nullptr); 153 return; 154 } 155 156 frame.set(frames->back().savedFrame); 157 } 158 159 void LiveSavedFrameCache::findWithoutInvalidation( 160 const FramePtr& framePtr, MutableHandle<SavedFrame*> frame) const { 161 MOZ_ASSERT(initialized()); 162 MOZ_ASSERT(framePtr.hasCachedSavedFrame()); 163 164 Key key(framePtr); 165 for (auto& entry : (*frames)) { 166 if (entry.key == key) { 167 frame.set(entry.savedFrame); 168 return; 169 } 170 } 171 172 frame.set(nullptr); 173 } 174 175 struct MOZ_STACK_CLASS SavedFrame::Lookup { 176 Lookup(JSAtom* source, uint32_t sourceId, uint32_t line, 177 JS::TaggedColumnNumberOneOrigin column, JSAtom* functionDisplayName, 178 JSAtom* asyncCause, SavedFrame* parent, JSPrincipals* principals, 179 bool mutedErrors, 180 const Maybe<LiveSavedFrameCache::FramePtr>& framePtr = Nothing(), 181 jsbytecode* pc = nullptr, Activation* activation = nullptr) 182 : source(source), 183 sourceId(sourceId), 184 line(line), 185 column(column), 186 functionDisplayName(functionDisplayName), 187 asyncCause(asyncCause), 188 parent(parent), 189 principals(principals), 190 mutedErrors(mutedErrors), 191 framePtr(framePtr), 192 pc(pc), 193 activation(activation) { 194 MOZ_ASSERT(source); 195 MOZ_ASSERT_IF(framePtr.isSome(), activation); 196 if (js::SupportDifferentialTesting()) { 197 this->column = JS::TaggedColumnNumberOneOrigin::forDifferentialTesting(); 198 } 199 } 200 201 explicit Lookup(SavedFrame& savedFrame) 202 : source(savedFrame.getSource()), 203 sourceId(savedFrame.getSourceId()), 204 line(savedFrame.getLine()), 205 column(savedFrame.getColumn()), 206 functionDisplayName(savedFrame.getFunctionDisplayName()), 207 asyncCause(savedFrame.getAsyncCause()), 208 parent(savedFrame.getParent()), 209 principals(savedFrame.getPrincipals()), 210 mutedErrors(savedFrame.getMutedErrors()), 211 framePtr(Nothing()), 212 pc(nullptr), 213 activation(nullptr) { 214 MOZ_ASSERT(source); 215 } 216 217 JSAtom* source; 218 uint32_t sourceId; 219 220 // Line number (1-origin). 221 uint32_t line; 222 223 // Columm number in UTF-16 code units. 224 JS::TaggedColumnNumberOneOrigin column; 225 226 JSAtom* functionDisplayName; 227 JSAtom* asyncCause; 228 SavedFrame* parent; 229 JSPrincipals* principals; 230 bool mutedErrors; 231 232 // These are used only by the LiveSavedFrameCache and not used for identity or 233 // hashing. 234 Maybe<LiveSavedFrameCache::FramePtr> framePtr; 235 jsbytecode* pc; 236 Activation* activation; 237 238 void trace(JSTracer* trc) { 239 TraceRoot(trc, &source, "SavedFrame::Lookup::source"); 240 TraceNullableRoot(trc, &functionDisplayName, 241 "SavedFrame::Lookup::functionDisplayName"); 242 TraceNullableRoot(trc, &asyncCause, "SavedFrame::Lookup::asyncCause"); 243 TraceNullableRoot(trc, &parent, "SavedFrame::Lookup::parent"); 244 } 245 }; 246 247 using GCLookupVector = 248 GCVector<SavedFrame::Lookup, ASYNC_STACK_MAX_FRAME_COUNT>; 249 250 template <class Wrapper> 251 class WrappedPtrOperations<SavedFrame::Lookup, Wrapper> { 252 const SavedFrame::Lookup& value() const { 253 return static_cast<const Wrapper*>(this)->get(); 254 } 255 256 public: 257 JSAtom* source() { return value().source; } 258 uint32_t sourceId() { return value().sourceId; } 259 uint32_t line() { return value().line; } 260 JS::TaggedColumnNumberOneOrigin column() { return value().column; } 261 JSAtom* functionDisplayName() { return value().functionDisplayName; } 262 JSAtom* asyncCause() { return value().asyncCause; } 263 SavedFrame* parent() { return value().parent; } 264 JSPrincipals* principals() { return value().principals; } 265 bool mutedErrors() { return value().mutedErrors; } 266 Maybe<LiveSavedFrameCache::FramePtr> framePtr() { return value().framePtr; } 267 jsbytecode* pc() { return value().pc; } 268 Activation* activation() { return value().activation; } 269 }; 270 271 template <typename Wrapper> 272 class MutableWrappedPtrOperations<SavedFrame::Lookup, Wrapper> 273 : public WrappedPtrOperations<SavedFrame::Lookup, Wrapper> { 274 SavedFrame::Lookup& value() { return static_cast<Wrapper*>(this)->get(); } 275 276 public: 277 void setParent(SavedFrame* parent) { value().parent = parent; } 278 279 void setAsyncCause(Handle<JSAtom*> asyncCause) { 280 value().asyncCause = asyncCause; 281 } 282 }; 283 284 /* static */ 285 bool SavedFrame::HashPolicy::maybeGetHash(const Lookup& l, 286 HashNumber* hashOut) { 287 HashNumber parentHash; 288 if (!SavedFramePtrHasher::maybeGetHash(l.parent, &parentHash)) { 289 return false; 290 } 291 *hashOut = calculateHash(l, parentHash); 292 return true; 293 } 294 295 /* static */ 296 bool SavedFrame::HashPolicy::ensureHash(const Lookup& l, HashNumber* hashOut) { 297 HashNumber parentHash; 298 if (!SavedFramePtrHasher::ensureHash(l.parent, &parentHash)) { 299 return false; 300 } 301 *hashOut = calculateHash(l, parentHash); 302 return true; 303 } 304 305 /* static */ 306 HashNumber SavedFrame::HashPolicy::hash(const Lookup& lookup) { 307 return calculateHash(lookup, SavedFramePtrHasher::hash(lookup.parent)); 308 } 309 310 /* static */ 311 HashNumber SavedFrame::HashPolicy::calculateHash(const Lookup& lookup, 312 HashNumber parentHash) { 313 JS::AutoCheckCannotGC nogc; 314 // Assume that we can take line mod 2^32 without losing anything of 315 // interest. If that assumption changes, we'll just need to start with 0 316 // and add another overload of AddToHash with more arguments. 317 return AddToHash(lookup.line, lookup.column.rawValue(), lookup.source, 318 lookup.functionDisplayName, lookup.asyncCause, 319 lookup.mutedErrors, parentHash, 320 JSPrincipalsPtrHasher::hash(lookup.principals)); 321 } 322 323 /* static */ 324 bool SavedFrame::HashPolicy::match(SavedFrame* existing, const Lookup& lookup) { 325 MOZ_ASSERT(existing); 326 327 if (existing->getLine() != lookup.line) { 328 return false; 329 } 330 331 if (existing->getColumn() != lookup.column) { 332 return false; 333 } 334 335 if (existing->getParent() != lookup.parent) { 336 return false; 337 } 338 339 if (existing->getPrincipals() != lookup.principals) { 340 return false; 341 } 342 343 JSAtom* source = existing->getSource(); 344 if (source != lookup.source) { 345 return false; 346 } 347 348 JSAtom* functionDisplayName = existing->getFunctionDisplayName(); 349 if (functionDisplayName != lookup.functionDisplayName) { 350 return false; 351 } 352 353 JSAtom* asyncCause = existing->getAsyncCause(); 354 if (asyncCause != lookup.asyncCause) { 355 return false; 356 } 357 358 return true; 359 } 360 361 /* static */ 362 void SavedFrame::HashPolicy::rekey(Key& key, const Key& newKey) { 363 key = newKey; 364 } 365 366 /* static */ 367 bool SavedFrame::finishSavedFrameInit(JSContext* cx, HandleObject ctor, 368 HandleObject proto) { 369 return FreezeObject(cx, proto); 370 } 371 372 static const JSClassOps SavedFrameClassOps = { 373 nullptr, // addProperty 374 nullptr, // delProperty 375 nullptr, // enumerate 376 nullptr, // newEnumerate 377 nullptr, // resolve 378 nullptr, // mayResolve 379 SavedFrame::finalize, // finalize 380 nullptr, // call 381 nullptr, // construct 382 nullptr, // trace 383 }; 384 385 const ClassSpec SavedFrame::classSpec_ = { 386 GenericCreateConstructor<SavedFrame::construct, 0, gc::AllocKind::FUNCTION>, 387 GenericCreatePrototype<SavedFrame>, 388 SavedFrame::staticFunctions, 389 nullptr, 390 SavedFrame::protoFunctions, 391 SavedFrame::protoAccessors, 392 SavedFrame::finishSavedFrameInit, 393 ClassSpec::DontDefineConstructor, 394 }; 395 396 /* static */ const JSClass SavedFrame::class_ = { 397 "SavedFrame", 398 JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT) | 399 JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame) | 400 JSCLASS_FOREGROUND_FINALIZE, 401 &SavedFrameClassOps, 402 &SavedFrame::classSpec_, 403 }; 404 405 const JSClass SavedFrame::protoClass_ = { 406 "SavedFrame.prototype", 407 JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame), 408 JS_NULL_CLASS_OPS, 409 &SavedFrame::classSpec_, 410 }; 411 412 /* static */ const JSFunctionSpec SavedFrame::staticFunctions[] = { 413 JS_FS_END, 414 }; 415 416 /* static */ const JSFunctionSpec SavedFrame::protoFunctions[] = { 417 JS_FN("constructor", SavedFrame::construct, 0, 0), 418 JS_FN("toString", SavedFrame::toStringMethod, 0, 0), 419 JS_FS_END, 420 }; 421 422 /* static */ const JSPropertySpec SavedFrame::protoAccessors[] = { 423 JS_PSG("source", SavedFrame::sourceProperty, 0), 424 JS_PSG("sourceId", SavedFrame::sourceIdProperty, 0), 425 JS_PSG("line", SavedFrame::lineProperty, 0), 426 JS_PSG("column", SavedFrame::columnProperty, 0), 427 JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty, 0), 428 JS_PSG("asyncCause", SavedFrame::asyncCauseProperty, 0), 429 JS_PSG("asyncParent", SavedFrame::asyncParentProperty, 0), 430 JS_PSG("parent", SavedFrame::parentProperty, 0), 431 JS_STRING_SYM_PS(toStringTag, "SavedFrame", JSPROP_READONLY), 432 JS_PS_END, 433 }; 434 435 /* static */ 436 void SavedFrame::finalize(JS::GCContext* gcx, JSObject* obj) { 437 MOZ_ASSERT(gcx->onMainThread()); 438 JSPrincipals* p = obj->as<SavedFrame>().getPrincipals(); 439 if (p) { 440 JSRuntime* rt = obj->runtimeFromMainThread(); 441 JS_DropPrincipals(rt->mainContextFromOwnThread(), p); 442 } 443 } 444 445 JSAtom* SavedFrame::getSource() { 446 const Value& v = getReservedSlot(JSSLOT_SOURCE); 447 JSString* s = v.toString(); 448 return &s->asAtom(); 449 } 450 451 uint32_t SavedFrame::getSourceId() { 452 const Value& v = getReservedSlot(JSSLOT_SOURCEID); 453 return v.toPrivateUint32(); 454 } 455 456 uint32_t SavedFrame::getLine() { 457 const Value& v = getReservedSlot(JSSLOT_LINE); 458 return v.toPrivateUint32(); 459 } 460 461 JS::TaggedColumnNumberOneOrigin SavedFrame::getColumn() { 462 const Value& v = getReservedSlot(JSSLOT_COLUMN); 463 return JS::TaggedColumnNumberOneOrigin::fromRaw(v.toPrivateUint32()); 464 } 465 466 JSAtom* SavedFrame::getFunctionDisplayName() { 467 const Value& v = getReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME); 468 if (v.isNull()) { 469 return nullptr; 470 } 471 JSString* s = v.toString(); 472 return &s->asAtom(); 473 } 474 475 JSAtom* SavedFrame::getAsyncCause() { 476 const Value& v = getReservedSlot(JSSLOT_ASYNCCAUSE); 477 if (v.isNull()) { 478 return nullptr; 479 } 480 JSString* s = v.toString(); 481 return &s->asAtom(); 482 } 483 484 SavedFrame* SavedFrame::getParent() const { 485 const Value& v = getReservedSlot(JSSLOT_PARENT); 486 return v.isObject() ? &v.toObject().as<SavedFrame>() : nullptr; 487 } 488 489 JSPrincipals* SavedFrame::getPrincipals() { 490 const Value& v = getReservedSlot(JSSLOT_PRINCIPALS); 491 if (v.isUndefined()) { 492 return nullptr; 493 } 494 return reinterpret_cast<JSPrincipals*>(uintptr_t(v.toPrivate()) & ~0b1); 495 } 496 497 bool SavedFrame::getMutedErrors() { 498 const Value& v = getReservedSlot(JSSLOT_PRINCIPALS); 499 if (v.isUndefined()) { 500 return true; 501 } 502 return bool(uintptr_t(v.toPrivate()) & 0b1); 503 } 504 505 void SavedFrame::initSource(JSAtom* source) { 506 MOZ_ASSERT(source); 507 initReservedSlot(JSSLOT_SOURCE, StringValue(source)); 508 } 509 510 void SavedFrame::initSourceId(uint32_t sourceId) { 511 initReservedSlot(JSSLOT_SOURCEID, PrivateUint32Value(sourceId)); 512 } 513 514 void SavedFrame::initLine(uint32_t line) { 515 initReservedSlot(JSSLOT_LINE, PrivateUint32Value(line)); 516 } 517 518 void SavedFrame::initColumn(JS::TaggedColumnNumberOneOrigin column) { 519 if (js::SupportDifferentialTesting()) { 520 column = JS::TaggedColumnNumberOneOrigin::forDifferentialTesting(); 521 } 522 initReservedSlot(JSSLOT_COLUMN, PrivateUint32Value(column.rawValue())); 523 } 524 525 void SavedFrame::initPrincipalsAndMutedErrors(JSPrincipals* principals, 526 bool mutedErrors) { 527 if (principals) { 528 JS_HoldPrincipals(principals); 529 } 530 initPrincipalsAlreadyHeldAndMutedErrors(principals, mutedErrors); 531 } 532 533 void SavedFrame::initPrincipalsAlreadyHeldAndMutedErrors( 534 JSPrincipals* principals, bool mutedErrors) { 535 MOZ_ASSERT_IF(principals, principals->refcount > 0); 536 uintptr_t ptr = uintptr_t(principals) | mutedErrors; 537 initReservedSlot(JSSLOT_PRINCIPALS, 538 PrivateValue(reinterpret_cast<void*>(ptr))); 539 } 540 541 void SavedFrame::initFunctionDisplayName(JSAtom* maybeName) { 542 initReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME, 543 maybeName ? StringValue(maybeName) : NullValue()); 544 } 545 546 void SavedFrame::initAsyncCause(JSAtom* maybeCause) { 547 initReservedSlot(JSSLOT_ASYNCCAUSE, 548 maybeCause ? StringValue(maybeCause) : NullValue()); 549 } 550 551 void SavedFrame::initParent(SavedFrame* maybeParent) { 552 initReservedSlot(JSSLOT_PARENT, ObjectOrNullValue(maybeParent)); 553 } 554 555 void SavedFrame::initFromLookup(JSContext* cx, Handle<Lookup> lookup) { 556 // Make sure any atoms used in the lookup are marked in the current zone. 557 // Normally we would try to keep these mark bits up to date around the 558 // points where the context moves between compartments, but Lookups live on 559 // the stack (where the atoms are kept alive regardless) and this is a 560 // more convenient pinchpoint. 561 if (lookup.source()) { 562 cx->markAtom(lookup.source()); 563 } 564 if (lookup.functionDisplayName()) { 565 cx->markAtom(lookup.functionDisplayName()); 566 } 567 if (lookup.asyncCause()) { 568 cx->markAtom(lookup.asyncCause()); 569 } 570 571 initSource(lookup.source()); 572 initSourceId(lookup.sourceId()); 573 initLine(lookup.line()); 574 initColumn(lookup.column()); 575 initFunctionDisplayName(lookup.functionDisplayName()); 576 initAsyncCause(lookup.asyncCause()); 577 initParent(lookup.parent()); 578 initPrincipalsAndMutedErrors(lookup.principals(), lookup.mutedErrors()); 579 } 580 581 /* static */ 582 SavedFrame* SavedFrame::create(JSContext* cx) { 583 Rooted<GlobalObject*> global(cx, cx->global()); 584 cx->check(global); 585 586 // Ensure that we don't try to capture the stack again in the 587 // `SavedStacksMetadataBuilder` for this new SavedFrame object, and 588 // accidentally cause O(n^2) behavior. 589 SavedStacks::AutoReentrancyGuard guard(cx->realm()->savedStacks()); 590 591 RootedObject proto(cx, 592 GlobalObject::getOrCreateSavedFramePrototype(cx, global)); 593 if (!proto) { 594 return nullptr; 595 } 596 cx->check(proto); 597 598 return NewTenuredObjectWithGivenProto<SavedFrame>(cx, proto); 599 } 600 601 bool SavedFrame::isSelfHosted(JSContext* cx) { 602 JSAtom* source = getSource(); 603 return source == cx->names().self_hosted_; 604 } 605 606 bool SavedFrame::isWasm() { return getColumn().isWasmFunctionIndex(); } 607 608 uint32_t SavedFrame::wasmFuncIndex() { 609 return getColumn().toWasmFunctionIndex().value(); 610 } 611 612 uint32_t SavedFrame::wasmBytecodeOffset() { 613 MOZ_ASSERT(isWasm()); 614 return getLine(); 615 } 616 617 /* static */ 618 bool SavedFrame::construct(JSContext* cx, unsigned argc, Value* vp) { 619 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, 620 "SavedFrame"); 621 return false; 622 } 623 624 static bool SavedFrameSubsumedByPrincipals(JSContext* cx, 625 JSPrincipals* principals, 626 Handle<SavedFrame*> frame) { 627 auto subsumes = cx->runtime()->securityCallbacks->subsumes; 628 if (!subsumes) { 629 return true; 630 } 631 632 MOZ_ASSERT(!ReconstructedSavedFramePrincipals::is(principals)); 633 634 auto framePrincipals = frame->getPrincipals(); 635 636 // Handle SavedFrames that have been reconstructed from stacks in a heap 637 // snapshot. 638 if (framePrincipals == &ReconstructedSavedFramePrincipals::IsSystem) { 639 return cx->runningWithTrustedPrincipals(); 640 } 641 if (framePrincipals == &ReconstructedSavedFramePrincipals::IsNotSystem) { 642 return true; 643 } 644 645 return subsumes(principals, framePrincipals); 646 } 647 648 // Return the first SavedFrame in the chain that starts with |frame| whose 649 // for which the given match function returns true. If there is no such frame, 650 // return nullptr. |skippedAsync| is set to true if any of the skipped frames 651 // had the |asyncCause| property set, otherwise it is explicitly set to false. 652 template <typename Matcher> 653 static SavedFrame* GetFirstMatchedFrame(JSContext* cx, JSPrincipals* principals, 654 Matcher& matches, 655 Handle<SavedFrame*> frame, 656 JS::SavedFrameSelfHosted selfHosted, 657 bool& skippedAsync) { 658 skippedAsync = false; 659 660 Rooted<SavedFrame*> rootedFrame(cx, frame); 661 while (rootedFrame) { 662 if ((selfHosted == JS::SavedFrameSelfHosted::Include || 663 !rootedFrame->isSelfHosted(cx)) && 664 matches(cx, principals, rootedFrame)) { 665 return rootedFrame; 666 } 667 668 if (rootedFrame->getAsyncCause()) { 669 skippedAsync = true; 670 } 671 672 rootedFrame = rootedFrame->getParent(); 673 } 674 675 return nullptr; 676 } 677 678 // Return the first SavedFrame in the chain that starts with |frame| whose 679 // principals are subsumed by |principals|, according to |subsumes|. If there is 680 // no such frame, return nullptr. |skippedAsync| is set to true if any of the 681 // skipped frames had the |asyncCause| property set, otherwise it is explicitly 682 // set to false. 683 static SavedFrame* GetFirstSubsumedFrame(JSContext* cx, 684 JSPrincipals* principals, 685 Handle<SavedFrame*> frame, 686 JS::SavedFrameSelfHosted selfHosted, 687 bool& skippedAsync) { 688 return GetFirstMatchedFrame(cx, principals, SavedFrameSubsumedByPrincipals, 689 frame, selfHosted, skippedAsync); 690 } 691 692 JS_PUBLIC_API JSObject* GetFirstSubsumedSavedFrame( 693 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame, 694 JS::SavedFrameSelfHosted selfHosted) { 695 if (!savedFrame) { 696 return nullptr; 697 } 698 699 auto subsumes = cx->runtime()->securityCallbacks->subsumes; 700 if (!subsumes) { 701 return nullptr; 702 } 703 704 auto matcher = [subsumes](JSContext* cx, JSPrincipals* principals, 705 Handle<SavedFrame*> frame) -> bool { 706 return subsumes(principals, frame->getPrincipals()); 707 }; 708 709 bool skippedAsync; 710 Rooted<SavedFrame*> frame(cx, &savedFrame->as<SavedFrame>()); 711 return GetFirstMatchedFrame(cx, principals, matcher, frame, selfHosted, 712 skippedAsync); 713 } 714 715 [[nodiscard]] static bool SavedFrame_checkThis(JSContext* cx, CallArgs& args, 716 const char* fnName, 717 MutableHandleObject frame) { 718 const Value& thisValue = args.thisv(); 719 720 if (!thisValue.isObject()) { 721 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 722 JSMSG_OBJECT_REQUIRED, 723 InformalValueTypeName(thisValue)); 724 return false; 725 } 726 727 if (!thisValue.toObject().canUnwrapAs<SavedFrame>()) { 728 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 729 JSMSG_INCOMPATIBLE_PROTO, SavedFrame::class_.name, 730 fnName, "object"); 731 return false; 732 } 733 734 // Now set "frame" to the actual object we were invoked in (which may be a 735 // wrapper), not the unwrapped version. Consumers will need to know what 736 // that original object was, and will do principal checks as needed. 737 frame.set(&thisValue.toObject()); 738 return true; 739 } 740 741 // Get the SavedFrame * from the current this value and handle any errors that 742 // might occur therein. 743 // 744 // These parameters must already exist when calling this macro: 745 // - JSContext* cx 746 // - unsigned argc 747 // - Value* vp 748 // - const char* fnName 749 // These parameters will be defined after calling this macro: 750 // - CallArgs args 751 // - Rooted<SavedFrame*> frame (will be non-null) 752 #define THIS_SAVEDFRAME(cx, argc, vp, fnName, args, frame) \ 753 CallArgs args = CallArgsFromVp(argc, vp); \ 754 RootedObject frame(cx); \ 755 if (!SavedFrame_checkThis(cx, args, fnName, &frame)) return false; 756 757 } /* namespace js */ 758 759 js::SavedFrame* js::UnwrapSavedFrame(JSContext* cx, JSPrincipals* principals, 760 HandleObject obj, 761 JS::SavedFrameSelfHosted selfHosted, 762 bool& skippedAsync) { 763 if (!obj) { 764 return nullptr; 765 } 766 767 Rooted<SavedFrame*> frame(cx, obj->maybeUnwrapAs<SavedFrame>()); 768 if (!frame) { 769 return nullptr; 770 } 771 772 return GetFirstSubsumedFrame(cx, principals, frame, selfHosted, skippedAsync); 773 } 774 775 namespace JS { 776 777 JS_PUBLIC_API SavedFrameResult GetSavedFrameSource( 778 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame, 779 MutableHandleString sourcep, 780 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) { 781 js::AssertHeapIsIdle(); 782 CHECK_THREAD(cx); 783 MOZ_RELEASE_ASSERT(cx->realm()); 784 785 { 786 bool skippedAsync; 787 Rooted<js::SavedFrame*> frame( 788 cx, 789 UnwrapSavedFrame(cx, principals, savedFrame, selfHosted, skippedAsync)); 790 if (!frame) { 791 sourcep.set(cx->runtime()->emptyString); 792 return SavedFrameResult::AccessDenied; 793 } 794 sourcep.set(frame->getSource()); 795 } 796 if (sourcep->isAtom()) { 797 cx->markAtom(&sourcep->asAtom()); 798 } 799 return SavedFrameResult::Ok; 800 } 801 802 JS_PUBLIC_API SavedFrameResult GetSavedFrameSourceId( 803 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame, 804 uint32_t* sourceIdp, 805 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) { 806 js::AssertHeapIsIdle(); 807 CHECK_THREAD(cx); 808 MOZ_RELEASE_ASSERT(cx->realm()); 809 810 bool skippedAsync; 811 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame, 812 selfHosted, skippedAsync)); 813 if (!frame) { 814 *sourceIdp = 0; 815 return SavedFrameResult::AccessDenied; 816 } 817 *sourceIdp = frame->getSourceId(); 818 return SavedFrameResult::Ok; 819 } 820 821 JS_PUBLIC_API SavedFrameResult GetSavedFrameLine( 822 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame, 823 uint32_t* linep, 824 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) { 825 js::AssertHeapIsIdle(); 826 CHECK_THREAD(cx); 827 MOZ_RELEASE_ASSERT(cx->realm()); 828 MOZ_ASSERT(linep); 829 830 bool skippedAsync; 831 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame, 832 selfHosted, skippedAsync)); 833 if (!frame) { 834 *linep = 0; 835 return SavedFrameResult::AccessDenied; 836 } 837 *linep = frame->getLine(); 838 return SavedFrameResult::Ok; 839 } 840 841 JS_PUBLIC_API SavedFrameResult GetSavedFrameColumn( 842 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame, 843 JS::TaggedColumnNumberOneOrigin* columnp, 844 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) { 845 js::AssertHeapIsIdle(); 846 CHECK_THREAD(cx); 847 MOZ_RELEASE_ASSERT(cx->realm()); 848 MOZ_ASSERT(columnp); 849 850 bool skippedAsync; 851 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame, 852 selfHosted, skippedAsync)); 853 if (!frame) { 854 *columnp = JS::TaggedColumnNumberOneOrigin(); 855 return SavedFrameResult::AccessDenied; 856 } 857 *columnp = frame->getColumn(); 858 return SavedFrameResult::Ok; 859 } 860 861 JS_PUBLIC_API SavedFrameResult GetSavedFrameFunctionDisplayName( 862 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame, 863 MutableHandleString namep, 864 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) { 865 js::AssertHeapIsIdle(); 866 CHECK_THREAD(cx); 867 MOZ_RELEASE_ASSERT(cx->realm()); 868 869 { 870 bool skippedAsync; 871 Rooted<js::SavedFrame*> frame( 872 cx, 873 UnwrapSavedFrame(cx, principals, savedFrame, selfHosted, skippedAsync)); 874 if (!frame) { 875 namep.set(nullptr); 876 return SavedFrameResult::AccessDenied; 877 } 878 namep.set(frame->getFunctionDisplayName()); 879 } 880 if (namep && namep->isAtom()) { 881 cx->markAtom(&namep->asAtom()); 882 } 883 return SavedFrameResult::Ok; 884 } 885 886 JS_PUBLIC_API SavedFrameResult GetSavedFrameAsyncCause( 887 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame, 888 MutableHandleString asyncCausep, 889 SavedFrameSelfHosted unused_ /* = SavedFrameSelfHosted::Include */) { 890 js::AssertHeapIsIdle(); 891 CHECK_THREAD(cx); 892 MOZ_RELEASE_ASSERT(cx->realm()); 893 894 { 895 bool skippedAsync; 896 // This function is always called with self-hosted frames excluded by 897 // GetValueIfNotCached in dom/bindings/Exceptions.cpp. However, we want 898 // to include them because our Promise implementation causes us to have 899 // the async cause on a self-hosted frame. So we just ignore the 900 // parameter and always include self-hosted frames. 901 Rooted<js::SavedFrame*> frame( 902 cx, UnwrapSavedFrame(cx, principals, savedFrame, 903 SavedFrameSelfHosted::Include, skippedAsync)); 904 if (!frame) { 905 asyncCausep.set(nullptr); 906 return SavedFrameResult::AccessDenied; 907 } 908 asyncCausep.set(frame->getAsyncCause()); 909 if (!asyncCausep && skippedAsync) { 910 asyncCausep.set(cx->names().Async); 911 } 912 } 913 if (asyncCausep && asyncCausep->isAtom()) { 914 cx->markAtom(&asyncCausep->asAtom()); 915 } 916 return SavedFrameResult::Ok; 917 } 918 919 JS_PUBLIC_API SavedFrameResult GetSavedFrameAsyncParent( 920 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame, 921 MutableHandleObject asyncParentp, 922 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) { 923 js::AssertHeapIsIdle(); 924 CHECK_THREAD(cx); 925 MOZ_RELEASE_ASSERT(cx->realm()); 926 927 bool skippedAsync; 928 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame, 929 selfHosted, skippedAsync)); 930 if (!frame) { 931 asyncParentp.set(nullptr); 932 return SavedFrameResult::AccessDenied; 933 } 934 Rooted<js::SavedFrame*> parent(cx, frame->getParent()); 935 936 // The current value of |skippedAsync| is not interesting, because we are 937 // interested in whether we would cross any async parents to get from here 938 // to the first subsumed parent frame instead. 939 Rooted<js::SavedFrame*> subsumedParent( 940 cx, 941 GetFirstSubsumedFrame(cx, principals, parent, selfHosted, skippedAsync)); 942 943 // Even if |parent| is not subsumed, we still want to return a pointer to it 944 // rather than |subsumedParent| so it can pick up any |asyncCause| from the 945 // inaccessible part of the chain. 946 if (subsumedParent && (subsumedParent->getAsyncCause() || skippedAsync)) { 947 asyncParentp.set(parent); 948 } else { 949 asyncParentp.set(nullptr); 950 } 951 return SavedFrameResult::Ok; 952 } 953 954 JS_PUBLIC_API SavedFrameResult GetSavedFrameParent( 955 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame, 956 MutableHandleObject parentp, 957 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) { 958 js::AssertHeapIsIdle(); 959 CHECK_THREAD(cx); 960 MOZ_RELEASE_ASSERT(cx->realm()); 961 962 bool skippedAsync; 963 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame, 964 selfHosted, skippedAsync)); 965 if (!frame) { 966 parentp.set(nullptr); 967 return SavedFrameResult::AccessDenied; 968 } 969 Rooted<js::SavedFrame*> parent(cx, frame->getParent()); 970 971 // The current value of |skippedAsync| is not interesting, because we are 972 // interested in whether we would cross any async parents to get from here 973 // to the first subsumed parent frame instead. 974 Rooted<js::SavedFrame*> subsumedParent( 975 cx, 976 GetFirstSubsumedFrame(cx, principals, parent, selfHosted, skippedAsync)); 977 978 // Even if |parent| is not subsumed, we still want to return a pointer to it 979 // rather than |subsumedParent| so it can pick up any |asyncCause| from the 980 // inaccessible part of the chain. 981 if (subsumedParent && !(subsumedParent->getAsyncCause() || skippedAsync)) { 982 parentp.set(parent); 983 } else { 984 parentp.set(nullptr); 985 } 986 return SavedFrameResult::Ok; 987 } 988 989 static bool FormatStackFrameLine(js::StringBuilder& sb, 990 JS::Handle<js::SavedFrame*> frame) { 991 if (frame->isWasm()) { 992 // See comment in WasmFrameIter::computeLine(). 993 return sb.append("wasm-function[") && 994 NumberValueToStringBuilder(NumberValue(frame->wasmFuncIndex()), 995 sb) && 996 sb.append(']'); 997 } 998 999 return NumberValueToStringBuilder(NumberValue(frame->getLine()), sb); 1000 } 1001 1002 static bool FormatStackFrameColumn(js::StringBuilder& sb, 1003 JS::Handle<js::SavedFrame*> frame) { 1004 if (frame->isWasm()) { 1005 // See comment in WasmFrameIter::computeLine(). 1006 js::Int32ToCStringBuf cbuf; 1007 size_t cstrlen; 1008 const char* cstr = 1009 Uint32ToHexCString(&cbuf, frame->wasmBytecodeOffset(), &cstrlen); 1010 MOZ_ASSERT(cstr); 1011 1012 return sb.append("0x") && sb.append(cstr, cstrlen); 1013 } 1014 1015 return NumberValueToStringBuilder( 1016 NumberValue(frame->getColumn().oneOriginValue()), sb); 1017 } 1018 1019 static bool FormatSpiderMonkeyStackFrame(JSContext* cx, js::StringBuilder& sb, 1020 JS::Handle<js::SavedFrame*> frame, 1021 size_t indent, bool skippedAsync) { 1022 RootedString asyncCause(cx, frame->getAsyncCause()); 1023 if (!asyncCause && skippedAsync) { 1024 asyncCause.set(cx->names().Async); 1025 } 1026 1027 Rooted<JSAtom*> name(cx, frame->getFunctionDisplayName()); 1028 return (!indent || sb.appendN(' ', indent)) && 1029 (!asyncCause || (sb.append(asyncCause) && sb.append('*'))) && 1030 (!name || sb.append(name)) && sb.append('@') && 1031 sb.append(frame->getSource()) && sb.append(':') && 1032 FormatStackFrameLine(sb, frame) && sb.append(':') && 1033 FormatStackFrameColumn(sb, frame) && sb.append('\n'); 1034 } 1035 1036 static bool FormatV8StackFrame(JSContext* cx, js::StringBuilder& sb, 1037 JS::Handle<js::SavedFrame*> frame, size_t indent, 1038 bool lastFrame) { 1039 Rooted<JSAtom*> name(cx, frame->getFunctionDisplayName()); 1040 return sb.appendN(' ', indent + 4) && sb.append('a') && sb.append('t') && 1041 sb.append(' ') && 1042 (!name || (sb.append(name) && sb.append(' ') && sb.append('('))) && 1043 sb.append(frame->getSource()) && sb.append(':') && 1044 FormatStackFrameLine(sb, frame) && sb.append(':') && 1045 FormatStackFrameColumn(sb, frame) && (!name || sb.append(')')) && 1046 (lastFrame || sb.append('\n')); 1047 } 1048 1049 JS_PUBLIC_API bool BuildStackString(JSContext* cx, JSPrincipals* principals, 1050 HandleObject stack, 1051 MutableHandleString stringp, size_t indent, 1052 js::StackFormat format) { 1053 js::AssertHeapIsIdle(); 1054 CHECK_THREAD(cx); 1055 MOZ_RELEASE_ASSERT(cx->realm()); 1056 1057 js::JSStringBuilder sb(cx); 1058 1059 if (format == js::StackFormat::Default) { 1060 format = cx->runtime()->stackFormat(); 1061 } 1062 MOZ_ASSERT(format != js::StackFormat::Default); 1063 1064 // Enter a new block to constrain the scope of possibly entering the stack's 1065 // realm. This ensures that when we finish the StringBuilder, we are back in 1066 // the cx's original compartment, and fulfill our contract with callers to 1067 // place the output string in the cx's current realm. 1068 { 1069 bool skippedAsync; 1070 Rooted<js::SavedFrame*> frame( 1071 cx, UnwrapSavedFrame(cx, principals, stack, 1072 SavedFrameSelfHosted::Exclude, skippedAsync)); 1073 if (!frame) { 1074 stringp.set(cx->runtime()->emptyString); 1075 return true; 1076 } 1077 1078 Rooted<js::SavedFrame*> parent(cx); 1079 do { 1080 MOZ_ASSERT(SavedFrameSubsumedByPrincipals(cx, principals, frame)); 1081 MOZ_ASSERT(!frame->isSelfHosted(cx)); 1082 1083 parent = frame->getParent(); 1084 bool skippedNextAsync; 1085 Rooted<js::SavedFrame*> nextFrame( 1086 cx, js::GetFirstSubsumedFrame(cx, principals, parent, 1087 SavedFrameSelfHosted::Exclude, 1088 skippedNextAsync)); 1089 1090 switch (format) { 1091 case js::StackFormat::SpiderMonkey: 1092 if (!FormatSpiderMonkeyStackFrame(cx, sb, frame, indent, 1093 skippedAsync)) { 1094 return false; 1095 } 1096 break; 1097 case js::StackFormat::V8: 1098 if (!FormatV8StackFrame(cx, sb, frame, indent, !nextFrame)) { 1099 return false; 1100 } 1101 break; 1102 case js::StackFormat::Default: 1103 MOZ_CRASH("Unexpected value"); 1104 break; 1105 } 1106 1107 frame = nextFrame; 1108 skippedAsync = skippedNextAsync; 1109 } while (frame); 1110 } 1111 1112 JSString* str = sb.finishString(); 1113 if (!str) { 1114 return false; 1115 } 1116 cx->check(str); 1117 stringp.set(str); 1118 return true; 1119 } 1120 1121 JS_PUBLIC_API bool IsMaybeWrappedSavedFrame(JSObject* obj) { 1122 MOZ_ASSERT(obj); 1123 return obj->canUnwrapAs<js::SavedFrame>(); 1124 } 1125 1126 JS_PUBLIC_API bool IsUnwrappedSavedFrame(JSObject* obj) { 1127 MOZ_ASSERT(obj); 1128 return obj->is<js::SavedFrame>(); 1129 } 1130 1131 static bool AssignProperty(JSContext* cx, HandleObject dst, HandleObject src, 1132 const char* property) { 1133 RootedValue v(cx); 1134 return JS_GetProperty(cx, src, property, &v) && 1135 JS_DefineProperty(cx, dst, property, v, JSPROP_ENUMERATE); 1136 } 1137 1138 JS_PUBLIC_API JSObject* ConvertSavedFrameToPlainObject( 1139 JSContext* cx, HandleObject savedFrameArg, 1140 SavedFrameSelfHosted selfHosted) { 1141 MOZ_ASSERT(savedFrameArg); 1142 1143 RootedObject savedFrame(cx, savedFrameArg); 1144 RootedObject baseConverted(cx), lastConverted(cx); 1145 RootedValue v(cx); 1146 1147 baseConverted = lastConverted = JS_NewObject(cx, nullptr); 1148 if (!baseConverted) { 1149 return nullptr; 1150 } 1151 1152 bool foundParent; 1153 do { 1154 if (!AssignProperty(cx, lastConverted, savedFrame, "source") || 1155 !AssignProperty(cx, lastConverted, savedFrame, "sourceId") || 1156 !AssignProperty(cx, lastConverted, savedFrame, "line") || 1157 !AssignProperty(cx, lastConverted, savedFrame, "column") || 1158 !AssignProperty(cx, lastConverted, savedFrame, "functionDisplayName") || 1159 !AssignProperty(cx, lastConverted, savedFrame, "asyncCause")) { 1160 return nullptr; 1161 } 1162 1163 const char* parentProperties[] = {"parent", "asyncParent"}; 1164 foundParent = false; 1165 for (const char* prop : parentProperties) { 1166 if (!JS_GetProperty(cx, savedFrame, prop, &v)) { 1167 return nullptr; 1168 } 1169 if (v.isObject()) { 1170 RootedObject nextConverted(cx, JS_NewObject(cx, nullptr)); 1171 if (!nextConverted || 1172 !JS_DefineProperty(cx, lastConverted, prop, nextConverted, 1173 JSPROP_ENUMERATE)) { 1174 return nullptr; 1175 } 1176 lastConverted = nextConverted; 1177 savedFrame = &v.toObject(); 1178 foundParent = true; 1179 break; 1180 } 1181 } 1182 } while (foundParent); 1183 1184 return baseConverted; 1185 } 1186 1187 } /* namespace JS */ 1188 1189 namespace js { 1190 1191 /* static */ 1192 bool SavedFrame::sourceProperty(JSContext* cx, unsigned argc, Value* vp) { 1193 THIS_SAVEDFRAME(cx, argc, vp, "(get source)", args, frame); 1194 JSPrincipals* principals = cx->realm()->principals(); 1195 RootedString source(cx); 1196 if (JS::GetSavedFrameSource(cx, principals, frame, &source) == 1197 JS::SavedFrameResult::Ok) { 1198 if (!cx->compartment()->wrap(cx, &source)) { 1199 return false; 1200 } 1201 args.rval().setString(source); 1202 } else { 1203 args.rval().setNull(); 1204 } 1205 return true; 1206 } 1207 1208 /* static */ 1209 bool SavedFrame::sourceIdProperty(JSContext* cx, unsigned argc, Value* vp) { 1210 THIS_SAVEDFRAME(cx, argc, vp, "(get sourceId)", args, frame); 1211 JSPrincipals* principals = cx->realm()->principals(); 1212 uint32_t sourceId; 1213 if (JS::GetSavedFrameSourceId(cx, principals, frame, &sourceId) == 1214 JS::SavedFrameResult::Ok) { 1215 args.rval().setNumber(sourceId); 1216 } else { 1217 args.rval().setNull(); 1218 } 1219 return true; 1220 } 1221 1222 /* static */ 1223 bool SavedFrame::lineProperty(JSContext* cx, unsigned argc, Value* vp) { 1224 THIS_SAVEDFRAME(cx, argc, vp, "(get line)", args, frame); 1225 JSPrincipals* principals = cx->realm()->principals(); 1226 uint32_t line; 1227 if (JS::GetSavedFrameLine(cx, principals, frame, &line) == 1228 JS::SavedFrameResult::Ok) { 1229 args.rval().setNumber(line); 1230 } else { 1231 args.rval().setNull(); 1232 } 1233 return true; 1234 } 1235 1236 /* static */ 1237 bool SavedFrame::columnProperty(JSContext* cx, unsigned argc, Value* vp) { 1238 THIS_SAVEDFRAME(cx, argc, vp, "(get column)", args, frame); 1239 JSPrincipals* principals = cx->realm()->principals(); 1240 JS::TaggedColumnNumberOneOrigin column; 1241 if (JS::GetSavedFrameColumn(cx, principals, frame, &column) == 1242 JS::SavedFrameResult::Ok) { 1243 args.rval().setNumber(column.oneOriginValue()); 1244 } else { 1245 args.rval().setNull(); 1246 } 1247 return true; 1248 } 1249 1250 /* static */ 1251 bool SavedFrame::functionDisplayNameProperty(JSContext* cx, unsigned argc, 1252 Value* vp) { 1253 THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", args, frame); 1254 JSPrincipals* principals = cx->realm()->principals(); 1255 RootedString name(cx); 1256 JS::SavedFrameResult result = 1257 JS::GetSavedFrameFunctionDisplayName(cx, principals, frame, &name); 1258 if (result == JS::SavedFrameResult::Ok && name) { 1259 if (!cx->compartment()->wrap(cx, &name)) { 1260 return false; 1261 } 1262 args.rval().setString(name); 1263 } else { 1264 args.rval().setNull(); 1265 } 1266 return true; 1267 } 1268 1269 /* static */ 1270 bool SavedFrame::asyncCauseProperty(JSContext* cx, unsigned argc, Value* vp) { 1271 THIS_SAVEDFRAME(cx, argc, vp, "(get asyncCause)", args, frame); 1272 JSPrincipals* principals = cx->realm()->principals(); 1273 RootedString asyncCause(cx); 1274 JS::SavedFrameResult result = 1275 JS::GetSavedFrameAsyncCause(cx, principals, frame, &asyncCause); 1276 if (result == JS::SavedFrameResult::Ok && asyncCause) { 1277 if (!cx->compartment()->wrap(cx, &asyncCause)) { 1278 return false; 1279 } 1280 args.rval().setString(asyncCause); 1281 } else { 1282 args.rval().setNull(); 1283 } 1284 return true; 1285 } 1286 1287 /* static */ 1288 bool SavedFrame::asyncParentProperty(JSContext* cx, unsigned argc, Value* vp) { 1289 THIS_SAVEDFRAME(cx, argc, vp, "(get asyncParent)", args, frame); 1290 JSPrincipals* principals = cx->realm()->principals(); 1291 RootedObject asyncParent(cx); 1292 (void)JS::GetSavedFrameAsyncParent(cx, principals, frame, &asyncParent); 1293 if (!cx->compartment()->wrap(cx, &asyncParent)) { 1294 return false; 1295 } 1296 args.rval().setObjectOrNull(asyncParent); 1297 return true; 1298 } 1299 1300 /* static */ 1301 bool SavedFrame::parentProperty(JSContext* cx, unsigned argc, Value* vp) { 1302 THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", args, frame); 1303 JSPrincipals* principals = cx->realm()->principals(); 1304 RootedObject parent(cx); 1305 (void)JS::GetSavedFrameParent(cx, principals, frame, &parent); 1306 if (!cx->compartment()->wrap(cx, &parent)) { 1307 return false; 1308 } 1309 args.rval().setObjectOrNull(parent); 1310 return true; 1311 } 1312 1313 /* static */ 1314 bool SavedFrame::toStringMethod(JSContext* cx, unsigned argc, Value* vp) { 1315 THIS_SAVEDFRAME(cx, argc, vp, "toString", args, frame); 1316 JSPrincipals* principals = cx->realm()->principals(); 1317 RootedString string(cx); 1318 if (!JS::BuildStackString(cx, principals, frame, &string)) { 1319 return false; 1320 } 1321 args.rval().setString(string); 1322 return true; 1323 } 1324 1325 bool SavedStacks::saveCurrentStack( 1326 JSContext* cx, MutableHandle<SavedFrame*> frame, 1327 JS::StackCapture&& capture /* = JS::StackCapture(JS::AllFrames()) */, 1328 HandleObject startAt /* nullptr */) { 1329 MOZ_RELEASE_ASSERT(cx->realm()); 1330 MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this); 1331 1332 if (creatingSavedFrame || cx->isExceptionPending() || !cx->global() || 1333 !cx->global()->isStandardClassResolved(JSProto_Object)) { 1334 frame.set(nullptr); 1335 return true; 1336 } 1337 1338 AutoGeckoProfilerEntry labelFrame(cx, "js::SavedStacks::saveCurrentStack"); 1339 return insertFrames(cx, frame, std::move(capture), startAt); 1340 } 1341 1342 bool SavedStacks::copyAsyncStack(JSContext* cx, HandleObject asyncStack, 1343 HandleString asyncCause, 1344 MutableHandle<SavedFrame*> adoptedStack, 1345 const Maybe<size_t>& maxFrameCount) { 1346 MOZ_RELEASE_ASSERT(cx->realm()); 1347 MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this); 1348 1349 Rooted<JSAtom*> asyncCauseAtom(cx, AtomizeString(cx, asyncCause)); 1350 if (!asyncCauseAtom) { 1351 return false; 1352 } 1353 1354 Rooted<SavedFrame*> asyncStackObj( 1355 cx, asyncStack->maybeUnwrapAs<js::SavedFrame>()); 1356 MOZ_RELEASE_ASSERT(asyncStackObj); 1357 adoptedStack.set(asyncStackObj); 1358 1359 if (!adoptAsyncStack(cx, adoptedStack, asyncCauseAtom, maxFrameCount)) { 1360 return false; 1361 } 1362 1363 return true; 1364 } 1365 1366 void SavedStacks::traceWeak(JSTracer* trc) { 1367 frames.traceWeak(trc); 1368 pcLocationMap.traceWeak(trc); 1369 } 1370 1371 void SavedStacks::trace(JSTracer* trc) { pcLocationMap.trace(trc); } 1372 1373 uint32_t SavedStacks::count() { return frames.count(); } 1374 1375 void SavedStacks::clear() { frames.clear(); } 1376 1377 size_t SavedStacks::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { 1378 return frames.shallowSizeOfExcludingThis(mallocSizeOf) + 1379 pcLocationMap.shallowSizeOfExcludingThis(mallocSizeOf); 1380 } 1381 1382 // Given that we have captured a stack frame with the given principals and 1383 // source, return true if the requested `StackCapture` has been satisfied and 1384 // stack walking can halt. Return false otherwise (and stack walking and frame 1385 // capturing should continue). 1386 static inline bool captureIsSatisfied(JSContext* cx, JSPrincipals* principals, 1387 const JSAtom* source, 1388 JS::StackCapture& capture) { 1389 class Matcher { 1390 JSContext* cx_; 1391 JSPrincipals* framePrincipals_; 1392 const JSAtom* frameSource_; 1393 1394 public: 1395 Matcher(JSContext* cx, JSPrincipals* principals, const JSAtom* source) 1396 : cx_(cx), framePrincipals_(principals), frameSource_(source) {} 1397 1398 bool operator()(JS::FirstSubsumedFrame& target) { 1399 auto subsumes = cx_->runtime()->securityCallbacks->subsumes; 1400 return (!subsumes || subsumes(target.principals, framePrincipals_)) && 1401 (!target.ignoreSelfHosted || 1402 frameSource_ != cx_->names().self_hosted_); 1403 } 1404 1405 bool operator()(JS::MaxFrames& target) { return target.maxFrames == 1; } 1406 1407 bool operator()(JS::AllFrames&) { return false; } 1408 }; 1409 1410 Matcher m(cx, principals, source); 1411 return capture.match(m); 1412 } 1413 1414 bool SavedStacks::insertFrames(JSContext* cx, MutableHandle<SavedFrame*> frame, 1415 JS::StackCapture&& capture, 1416 HandleObject startAtObj) { 1417 MOZ_ASSERT_IF(startAtObj, startAtObj->isCallable()); 1418 1419 // In order to look up a cached SavedFrame object, we need to have its parent 1420 // SavedFrame, which means we need to walk the stack from oldest frame to 1421 // youngest. However, FrameIter walks the stack from youngest frame to 1422 // oldest. The solution is to append stack frames to a vector as we walk the 1423 // stack with FrameIter, and then do a second pass through that vector in 1424 // reverse order after the traversal has completed and get or create the 1425 // SavedFrame objects at that time. 1426 // 1427 // To avoid making many copies of FrameIter (whose copy constructor is 1428 // relatively slow), we use a vector of `SavedFrame::Lookup` objects, which 1429 // only contain the FrameIter data we need. The `SavedFrame::Lookup` 1430 // objects are partially initialized with everything except their parent 1431 // pointers on the first pass, and then we fill in the parent pointers as we 1432 // return in the second pass. 1433 1434 // Accumulate the vector of Lookup objects here, youngest to oldest. 1435 Rooted<js::GCLookupVector> stackChain(cx, js::GCLookupVector(cx)); 1436 1437 // If we find a cached saved frame, then that supplies the parent of the 1438 // frames we have placed in stackChain. If we walk the stack all the way 1439 // to the end, this remains null. 1440 Rooted<SavedFrame*> cachedParentFrame(cx, nullptr); 1441 1442 // Choose the right frame iteration strategy to accomodate both 1443 // evalInFramePrev links and the LiveSavedFrameCache. For background, see 1444 // the LiveSavedFrameCache comments in Stack.h. 1445 // 1446 // If we're using the LiveSavedFrameCache, then don't handle evalInFramePrev 1447 // links by skipping over the frames altogether; that violates the cache's 1448 // assumptions. Instead, traverse the entire stack, but choose each 1449 // SavedFrame's parent as directed by the evalInFramePrev link, if any. 1450 // 1451 // If we're not using the LiveSavedFrameCache, it's hard to recover the 1452 // frame to which the evalInFramePrev link refers, so we just let FrameIter 1453 // skip those frames. Then each SavedFrame's parent is simply the frame that 1454 // follows it in the stackChain vector, even when it has an evalInFramePrev 1455 // link. 1456 FrameIter iter(cx, capture.is<JS::AllFrames>() 1457 ? FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK 1458 : FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK); 1459 1460 // Once we've seen one frame with its hasCachedSavedFrame bit set, all its 1461 // parents (that can be cached) ought to have it set too. 1462 DebugOnly<bool> seenCached = false; 1463 1464 // If we are using evalInFramePrev links to adjust the parents of debugger 1465 // eval frames, we have to ensure the target frame is cached in the current 1466 // realm. (This might not happen by default if the target frame is 1467 // rematerialized, or if there is an async parent between the debugger eval 1468 // frame and the target frame.) To accomplish this, we keep track of eval 1469 // targets and ensure that we don't stop before they have all been reached. 1470 Vector<AbstractFramePtr, 4, TempAllocPolicy> unreachedEvalTargets(cx); 1471 1472 Rooted<JSFunction*> startAt(cx, startAtObj && startAtObj->is<JSFunction>() 1473 ? &startAtObj->as<JSFunction>() 1474 : nullptr); 1475 bool seenStartAt = !startAt; 1476 1477 while (!iter.done()) { 1478 Activation& activation = *iter.activation(); 1479 Maybe<LiveSavedFrameCache::FramePtr> framePtr = 1480 LiveSavedFrameCache::FramePtr::create(iter); 1481 1482 if (capture.is<JS::AllFrames>() && iter.hasUsableAbstractFramePtr()) { 1483 unreachedEvalTargets.eraseIfEqual(iter.abstractFramePtr()); 1484 } 1485 1486 if (framePtr) { 1487 // In general, when we reach a frame with its hasCachedSavedFrame bit set, 1488 // all its parents will have the bit set as well. See the 1489 // LiveSavedFrameCache comment in Activation.h for more details. There are 1490 // a few exceptions: 1491 // - Rematerialized frames are always created with the bit clear. 1492 // - Captures using FirstSubsumedFrame ignore async parents and walk the 1493 // real stack. Because we're using different rules for walking the 1494 // stack, we can reach frames that weren't cached in a previous 1495 // AllFrames traversal. 1496 DebugOnly<bool> hasGoodExcuse = framePtr->isRematerializedFrame() || 1497 capture.is<JS::FirstSubsumedFrame>(); 1498 MOZ_ASSERT_IF(seenCached, 1499 framePtr->hasCachedSavedFrame() || hasGoodExcuse); 1500 seenCached |= framePtr->hasCachedSavedFrame(); 1501 1502 if (capture.is<JS::AllFrames>() && framePtr->isInterpreterFrame() && 1503 framePtr->asInterpreterFrame().isDebuggerEvalFrame()) { 1504 AbstractFramePtr target = 1505 framePtr->asInterpreterFrame().evalInFramePrev(); 1506 if (!unreachedEvalTargets.append(target)) { 1507 return false; 1508 } 1509 } 1510 } 1511 1512 if (capture.is<JS::AllFrames>() && framePtr && 1513 framePtr->hasCachedSavedFrame()) { 1514 auto* cache = activation.getLiveSavedFrameCache(cx); 1515 if (!cache) { 1516 return false; 1517 } 1518 cache->find(cx, *framePtr, iter.pc(), &cachedParentFrame); 1519 1520 // Even though iter.hasCachedSavedFrame() was true, we may still get a 1521 // cache miss, if the frame's pc doesn't match the cache entry's, or if 1522 // the cache was emptied due to a realm mismatch. If we got a cache hit, 1523 // and we do not have to keep looking for unreached eval target frames, 1524 // we can stop traversing the stack and start building the chain. 1525 if (cachedParentFrame && unreachedEvalTargets.empty()) { 1526 break; 1527 } 1528 1529 // This frame doesn't have a cache entry, despite its hasCachedSavedFrame 1530 // flag being set. If this was due to a pc mismatch, we can clear the flag 1531 // here and set things right. If the cache was emptied due to a realm 1532 // mismatch, we should clear all the frames' flags as we walk to the 1533 // bottom of the stack, so that they are all clear before we start pushing 1534 // any new entries. 1535 framePtr->clearHasCachedSavedFrame(); 1536 } 1537 1538 // We'll be pushing this frame onto stackChain. Gather the information 1539 // needed to construct the SavedFrame::Lookup. 1540 Rooted<LocationValue> location(cx); 1541 { 1542 AutoRealmUnchecked ar(cx, iter.realm()); 1543 if (!cx->realm()->savedStacks().getLocation(cx, iter, &location)) { 1544 return false; 1545 } 1546 } 1547 1548 Rooted<JSAtom*> displayAtom(cx, iter.maybeFunctionDisplayAtom()); 1549 1550 auto principals = iter.realm()->principals(); 1551 MOZ_ASSERT_IF(framePtr && !iter.isWasm(), iter.pc()); 1552 1553 // If we haven't yet seen the start, then don't add anything to the stack 1554 // chain. 1555 if (seenStartAt) { 1556 if (!stackChain.emplaceBack(location.source(), location.sourceId(), 1557 location.line(), location.column(), 1558 displayAtom, 1559 nullptr, // asyncCause 1560 nullptr, // parent (not known yet) 1561 principals, iter.mutedErrors(), framePtr, 1562 iter.pc(), &activation)) { 1563 return false; 1564 } 1565 } 1566 1567 if (captureIsSatisfied(cx, principals, location.source(), capture)) { 1568 break; 1569 } 1570 1571 if (!seenStartAt && iter.isFunctionFrame() && 1572 iter.matchCallee(cx, startAt)) { 1573 seenStartAt = true; 1574 } 1575 1576 ++iter; 1577 framePtr = LiveSavedFrameCache::FramePtr::create(iter); 1578 1579 if (iter.activation() != &activation && capture.is<JS::AllFrames>()) { 1580 // If there were no cache hits in the entire activation, clear its 1581 // cache so we'll be able to push new ones when we build the 1582 // SavedFrame chain. 1583 activation.clearLiveSavedFrameCache(); 1584 } 1585 1586 // If we have crossed into a new activation, check whether the prior 1587 // activation had an async parent set. 1588 // 1589 // If the async call was explicit (async function resumptions, most 1590 // testing facilities), then the async parent stack has priority over 1591 // any actual frames still on the JavaScript stack. If the async call 1592 // was implicit (DOM CallbackObject::CallSetup calls), then the async 1593 // parent stack is used only if there were no other frames on the 1594 // stack. 1595 // 1596 // Captures using FirstSubsumedFrame expect us to ignore async parents. 1597 bool hasAsyncStackToAdopt = 1598 iter.activation() != &activation && activation.asyncStack() && 1599 (activation.asyncCallIsExplicit() || iter.done()) && 1600 !capture.is<JS::FirstSubsumedFrame>(); 1601 1602 // If we're censoring the stack for Error.captureStackTrace we also 1603 // don't want to re-parent an empty stack trace, so make sure 1604 // we actually saw a frame; stop walking the trace if we haven't 1605 // seen anything. 1606 if (hasAsyncStackToAdopt && stackChain.length() == 0) { 1607 break; 1608 } 1609 1610 if (hasAsyncStackToAdopt) { 1611 // Atomize the async cause string. There should only be a few 1612 // different strings used. 1613 const char* cause = activation.asyncCause(); 1614 Rooted<JSAtom*> causeAtom(cx, AtomizeUTF8Chars(cx, cause, strlen(cause))); 1615 if (!causeAtom) { 1616 return false; 1617 } 1618 1619 // Translate our capture into a frame count limit for 1620 // adoptAsyncStack, which will impose further limits. 1621 Maybe<size_t> maxFrames = 1622 !capture.is<JS::MaxFrames>() ? Nothing() 1623 : capture.as<JS::MaxFrames>().maxFrames == 0 1624 ? Nothing() 1625 : Some(capture.as<JS::MaxFrames>().maxFrames); 1626 1627 // Clip the stack if needed, attach the async cause string to the 1628 // top frame, and copy it into our compartment if necessary. 1629 Rooted<SavedFrame*> asyncParent(cx, activation.asyncStack()); 1630 if (!adoptAsyncStack(cx, &asyncParent, causeAtom, maxFrames)) { 1631 return false; 1632 } 1633 stackChain[stackChain.length() - 1].setParent(asyncParent); 1634 if (!capture.is<JS::AllFrames>() || unreachedEvalTargets.empty()) { 1635 // In the case of a JS::AllFrames capture, we will be populating the 1636 // LiveSavedFrameCache in the second loop. In the case where there is 1637 // a debugger eval frame on the stack, the second loop will use 1638 // checkForEvalInFramePrev to skip from the eval frame to the "prev" 1639 // frame and assert that when this happens, the "prev" 1640 // frame is in the cache. In cases where there is an async stack 1641 // activation between the debugger eval frame and the "prev" frame, 1642 // breaking here would not populate the "prev" cache entry, causing 1643 // checkForEvalInFramePrev to fail. 1644 break; 1645 } 1646 1647 // At this point, we would normally stop walking the stack, but 1648 // we're continuing because of an unreached eval target. If a 1649 // previous capture stopped here, it's possible that this frame was 1650 // already cached, but its non-async parent wasn't, which violates 1651 // our `seenCached` invariant. By clearing `seenCached` here, we 1652 // avoid spurious assertions. We continue to enforce the invariant 1653 // for subsequent frames: if any frame above this is cached, then 1654 // all of that frame's parents should also be cached. 1655 seenCached = false; 1656 } 1657 1658 if (capture.is<JS::MaxFrames>()) { 1659 capture.as<JS::MaxFrames>().maxFrames--; 1660 } 1661 } 1662 1663 // Iterate through |stackChain| in reverse order and get or create the 1664 // actual SavedFrame instances. 1665 frame.set(cachedParentFrame); 1666 for (size_t i = stackChain.length(); i != 0; i--) { 1667 MutableHandle<SavedFrame::Lookup> lookup = stackChain[i - 1]; 1668 if (!lookup.parent()) { 1669 // The frame may already have an async parent frame set explicitly 1670 // on its activation. 1671 lookup.setParent(frame); 1672 } 1673 1674 // If necessary, adjust the parent of a debugger eval frame to point to 1675 // the frame in whose scope the eval occurs - if we're using 1676 // LiveSavedFrameCache. Otherwise, we simply ask the FrameIter to follow 1677 // evalInFramePrev links, so that the parent is always the last frame we 1678 // created. 1679 if (capture.is<JS::AllFrames>() && lookup.framePtr()) { 1680 if (!checkForEvalInFramePrev(cx, lookup)) { 1681 return false; 1682 } 1683 } 1684 1685 frame.set(getOrCreateSavedFrame(cx, lookup)); 1686 if (!frame) { 1687 return false; 1688 } 1689 1690 if (capture.is<JS::AllFrames>() && lookup.framePtr()) { 1691 auto* cache = lookup.activation()->getLiveSavedFrameCache(cx); 1692 if (!cache || 1693 !cache->insert(cx, *lookup.framePtr(), lookup.pc(), frame)) { 1694 return false; 1695 } 1696 } 1697 } 1698 1699 return true; 1700 } 1701 1702 bool SavedStacks::adoptAsyncStack(JSContext* cx, 1703 MutableHandle<SavedFrame*> asyncStack, 1704 Handle<JSAtom*> asyncCause, 1705 const Maybe<size_t>& maxFrameCount) { 1706 MOZ_ASSERT(asyncStack); 1707 MOZ_ASSERT(asyncCause); 1708 1709 // If maxFrameCount is Nothing, the caller asked for an unlimited number of 1710 // stack frames, but async stacks are not limited by the available stack 1711 // memory, so we need to set an arbitrary limit when collecting them. We 1712 // still don't enforce an upper limit if the caller requested more frames. 1713 size_t maxFrames = maxFrameCount.valueOr(ASYNC_STACK_MAX_FRAME_COUNT); 1714 1715 // Turn the chain of frames starting with asyncStack into a vector of Lookup 1716 // objects in |stackChain|, youngest to oldest. 1717 Rooted<js::GCLookupVector> stackChain(cx, js::GCLookupVector(cx)); 1718 SavedFrame* currentSavedFrame = asyncStack; 1719 while (currentSavedFrame && stackChain.length() < maxFrames) { 1720 if (!stackChain.emplaceBack(*currentSavedFrame)) { 1721 ReportOutOfMemory(cx); 1722 return false; 1723 } 1724 1725 currentSavedFrame = currentSavedFrame->getParent(); 1726 } 1727 1728 // Attach the asyncCause to the youngest frame. 1729 stackChain[0].setAsyncCause(asyncCause); 1730 1731 // If we walked the entire stack, and it's in cx's realm, we don't 1732 // need to rebuild the full chain again using the lookup objects - we can 1733 // just use the existing chain. Only the asyncCause on the youngest frame 1734 // needs to be changed. 1735 if (currentSavedFrame == nullptr && asyncStack->realm() == cx->realm()) { 1736 MutableHandle<SavedFrame::Lookup> lookup = stackChain[0]; 1737 lookup.setParent(asyncStack->getParent()); 1738 asyncStack.set(getOrCreateSavedFrame(cx, lookup)); 1739 return !!asyncStack; 1740 } 1741 1742 // If we captured the maximum number of frames and the caller requested no 1743 // specific limit, we only return half of them. This means that if we do 1744 // many subsequent captures with the same async stack, it's likely we can 1745 // use the optimization above. 1746 if (maxFrameCount.isNothing() && currentSavedFrame) { 1747 stackChain.shrinkBy(ASYNC_STACK_MAX_FRAME_COUNT / 2); 1748 } 1749 1750 // Iterate through |stackChain| in reverse order and get or create the 1751 // actual SavedFrame instances. 1752 asyncStack.set(nullptr); 1753 while (!stackChain.empty()) { 1754 Rooted<SavedFrame::Lookup> lookup(cx, stackChain.back()); 1755 lookup.setParent(asyncStack); 1756 asyncStack.set(getOrCreateSavedFrame(cx, lookup)); 1757 if (!asyncStack) { 1758 return false; 1759 } 1760 stackChain.popBack(); 1761 } 1762 1763 return true; 1764 } 1765 1766 // Given a |lookup| for which we're about to construct a SavedFrame, if it 1767 // refers to a Debugger eval frame, adjust |lookup|'s parent to be the frame's 1768 // evalInFramePrev target. 1769 // 1770 // Debugger eval frames run code in the scope of some random older frame on the 1771 // stack (the 'target' frame). It is our custom to report the target as the 1772 // immediate parent of the eval frame. The LiveSavedFrameCache requires us not 1773 // to skip frames, so instead we walk the entire stack, and just give Debugger 1774 // eval frames the right parents as we encounter them. 1775 // 1776 // Call this function only if we are using the LiveSavedFrameCache; otherwise, 1777 // FrameIter has already taken care of getting us the right parent. 1778 bool SavedStacks::checkForEvalInFramePrev( 1779 JSContext* cx, MutableHandle<SavedFrame::Lookup> lookup) { 1780 MOZ_ASSERT(lookup.framePtr()); 1781 if (!lookup.framePtr()->isInterpreterFrame()) { 1782 return true; 1783 } 1784 1785 InterpreterFrame& interpreterFrame = lookup.framePtr()->asInterpreterFrame(); 1786 if (!interpreterFrame.isDebuggerEvalFrame()) { 1787 return true; 1788 } 1789 1790 FrameIter iter(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); 1791 while (!iter.done() && 1792 (!iter.hasUsableAbstractFramePtr() || 1793 iter.abstractFramePtr() != interpreterFrame.evalInFramePrev())) { 1794 ++iter; 1795 } 1796 1797 Maybe<LiveSavedFrameCache::FramePtr> maybeTarget = 1798 LiveSavedFrameCache::FramePtr::create(iter); 1799 MOZ_ASSERT(maybeTarget); 1800 1801 LiveSavedFrameCache::FramePtr target = *maybeTarget; 1802 1803 // If we're caching the frame to which |lookup| refers, then we should 1804 // definitely have the target frame in the cache as well. 1805 MOZ_ASSERT(target.hasCachedSavedFrame()); 1806 1807 // Search the chain of activations for a LiveSavedFrameCache that has an 1808 // entry for target. 1809 Rooted<SavedFrame*> saved(cx, nullptr); 1810 for (Activation* act = lookup.activation(); act; act = act->prev()) { 1811 // It's okay to force allocation of a cache here; we're about to put 1812 // something in the top cache, and all the lower ones should exist 1813 // already. 1814 auto* cache = act->getLiveSavedFrameCache(cx); 1815 if (!cache) { 1816 return false; 1817 } 1818 1819 cache->findWithoutInvalidation(target, &saved); 1820 if (saved) { 1821 break; 1822 } 1823 } 1824 1825 // Since |target| has its cached bit set, we should have found it. 1826 MOZ_ALWAYS_TRUE(saved); 1827 1828 // Because we use findWithoutInvalidation here, we can technically get a 1829 // SavedFrame here for any realm. That shouldn't happen here because 1830 // checkForEvalInFramePrev is only called _after_ the parent frames have 1831 // been constructed, but if something prevents the chain from being properly 1832 // reconstructed, that invariant could be accidentally broken. 1833 MOZ_ASSERT(saved->realm() == cx->realm()); 1834 1835 lookup.setParent(saved); 1836 return true; 1837 } 1838 1839 SavedFrame* SavedStacks::getOrCreateSavedFrame( 1840 JSContext* cx, Handle<SavedFrame::Lookup> lookup) { 1841 const SavedFrame::Lookup& lookupInstance = lookup.get(); 1842 DependentAddPtr<SavedFrame::Set> p(cx, frames, lookupInstance); 1843 if (p) { 1844 MOZ_ASSERT(*p); 1845 return *p; 1846 } 1847 1848 SavedFrame* frame = createFrameFromLookup(cx, lookup); 1849 if (!frame) { 1850 return nullptr; 1851 } 1852 1853 if (!p.add(cx, frames, lookupInstance, frame)) { 1854 return nullptr; 1855 } 1856 1857 return frame; 1858 } 1859 1860 SavedFrame* SavedStacks::createFrameFromLookup( 1861 JSContext* cx, Handle<SavedFrame::Lookup> lookup) { 1862 Rooted<SavedFrame*> frame(cx, SavedFrame::create(cx)); 1863 if (!frame) { 1864 return nullptr; 1865 } 1866 frame->initFromLookup(cx, lookup); 1867 1868 if (!FreezeObject(cx, frame)) { 1869 return nullptr; 1870 } 1871 1872 return frame; 1873 } 1874 1875 bool SavedStacks::getLocation(JSContext* cx, const FrameIter& iter, 1876 MutableHandle<LocationValue> locationp) { 1877 // We should only ever be caching location values for scripts in this 1878 // compartment. Otherwise, we would get dead cross-compartment scripts in 1879 // the cache because our compartment's sweep method isn't called when their 1880 // compartment gets collected. 1881 MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this); 1882 cx->check(iter.compartment()); 1883 1884 // When we have a |JSScript| for this frame, use a potentially memoized 1885 // location from our PCLocationMap and copy it into |locationp|. When we do 1886 // not have a |JSScript| for this frame (wasm frames), we take a slow path 1887 // that doesn't employ memoization, and update |locationp|'s slots directly. 1888 1889 if (iter.isWasm()) { 1890 // Only asm.js has a displayURL. 1891 if (const char16_t* displayURL = iter.displayURL()) { 1892 locationp.setSource(AtomizeChars(cx, displayURL, js_strlen(displayURL))); 1893 } else { 1894 const char* filename = iter.filename() ? iter.filename() : ""; 1895 locationp.setSource(AtomizeUTF8Chars(cx, filename, strlen(filename))); 1896 } 1897 if (!locationp.source()) { 1898 return false; 1899 } 1900 1901 JS::TaggedColumnNumberOneOrigin column; 1902 locationp.setLine(iter.computeLine(&column)); 1903 locationp.setColumn(column); 1904 return true; 1905 } 1906 1907 RootedScript script(cx, iter.script()); 1908 jsbytecode* pc = iter.pc(); 1909 1910 PCLocationMap::AddPtr p = pcLocationMap.lookupForAdd(PCKey(script, pc)); 1911 1912 if (!p) { 1913 Rooted<JSAtom*> source(cx); 1914 if (const char16_t* displayURL = iter.displayURL()) { 1915 source = AtomizeChars(cx, displayURL, js_strlen(displayURL)); 1916 } else { 1917 const char* filename = script->filename() ? script->filename() : ""; 1918 source = AtomizeUTF8Chars(cx, filename, strlen(filename)); 1919 } 1920 if (!source) { 1921 return false; 1922 } 1923 1924 uint32_t sourceId = script->scriptSource()->id(); 1925 JS::LimitedColumnNumberOneOrigin column; 1926 uint32_t line = PCToLineNumber(script, pc, &column); 1927 1928 PCKey key(script, pc); 1929 LocationValue value(source, sourceId, line, 1930 JS::TaggedColumnNumberOneOrigin(column)); 1931 if (!pcLocationMap.add(p, key, value)) { 1932 ReportOutOfMemory(cx); 1933 return false; 1934 } 1935 } 1936 1937 locationp.set(p->value()); 1938 return true; 1939 } 1940 1941 void SavedStacks::chooseSamplingProbability(Realm* realm) { 1942 { 1943 JSRuntime* runtime = realm->runtimeFromMainThread(); 1944 if (runtime->recordAllocationCallback) { 1945 // The runtime is tracking allocations across all realms, in this case 1946 // ignore all of the debugger values, and use the runtime's probability. 1947 this->setSamplingProbability(runtime->allocationSamplingProbability); 1948 return; 1949 } 1950 } 1951 1952 // Use unbarriered version to prevent triggering read barrier while 1953 // collecting, this is safe as long as global does not escape. 1954 GlobalObject* global = realm->unsafeUnbarrieredMaybeGlobal(); 1955 if (!global) { 1956 return; 1957 } 1958 1959 Maybe<double> probability = DebugAPI::allocationSamplingProbability(global); 1960 if (probability.isNothing()) { 1961 return; 1962 } 1963 1964 this->setSamplingProbability(*probability); 1965 } 1966 1967 void SavedStacks::setSamplingProbability(double probability) { 1968 if (!bernoulliSeeded) { 1969 mozilla::Array<uint64_t, 2> seed; 1970 GenerateXorShift128PlusSeed(seed); 1971 bernoulli.setRandomState(seed[0], seed[1]); 1972 bernoulliSeeded = true; 1973 } 1974 1975 bernoulli.setProbability(probability); 1976 } 1977 1978 JSObject* SavedStacks::MetadataBuilder::build( 1979 JSContext* cx, HandleObject target, 1980 AutoEnterOOMUnsafeRegion& oomUnsafe) const { 1981 RootedObject obj(cx, target); 1982 1983 SavedStacks& stacks = cx->realm()->savedStacks(); 1984 if (!stacks.bernoulli.trial()) { 1985 return nullptr; 1986 } 1987 1988 Rooted<SavedFrame*> frame(cx); 1989 if (!stacks.saveCurrentStack(cx, &frame)) { 1990 oomUnsafe.crash("SavedStacksMetadataBuilder"); 1991 } 1992 1993 if (!DebugAPI::onLogAllocationSite(cx, obj, frame, 1994 mozilla::TimeStamp::Now())) { 1995 oomUnsafe.crash("SavedStacksMetadataBuilder"); 1996 } 1997 1998 auto recordAllocationCallback = 1999 cx->realm()->runtimeFromMainThread()->recordAllocationCallback; 2000 if (recordAllocationCallback) { 2001 // The following code translates the JS-specific information, into an 2002 // RecordAllocationInfo object that can be consumed outside of SpiderMonkey. 2003 2004 auto node = JS::ubi::Node(obj.get()); 2005 2006 // Pass the non-SpiderMonkey specific information back to the 2007 // callback to get it out of the JS engine. 2008 recordAllocationCallback(JS::RecordAllocationInfo{ 2009 node.typeName(), node.jsObjectClassName(), node.descriptiveTypeName(), 2010 JS::ubi::CoarseTypeToString(node.coarseType()), 2011 node.size(cx->runtime()->debuggerMallocSizeOf), 2012 gc::IsInsideNursery(obj)}); 2013 } 2014 2015 MOZ_ASSERT_IF(frame, !frame->is<WrapperObject>()); 2016 return frame; 2017 } 2018 2019 const SavedStacks::MetadataBuilder SavedStacks::metadataBuilder; 2020 2021 /* static */ 2022 constinit ReconstructedSavedFramePrincipals 2023 ReconstructedSavedFramePrincipals::IsSystem; 2024 /* static */ 2025 constinit ReconstructedSavedFramePrincipals 2026 ReconstructedSavedFramePrincipals::IsNotSystem; 2027 2028 UniqueChars BuildUTF8StackString(JSContext* cx, JSPrincipals* principals, 2029 HandleObject stack) { 2030 RootedString stackStr(cx); 2031 if (!JS::BuildStackString(cx, principals, stack, &stackStr)) { 2032 return nullptr; 2033 } 2034 2035 return JS_EncodeStringToUTF8(cx, stackStr); 2036 } 2037 2038 } /* namespace js */ 2039 2040 namespace JS { 2041 namespace ubi { 2042 2043 bool ConcreteStackFrame<SavedFrame>::isSystem() const { 2044 auto trustedPrincipals = get().runtimeFromAnyThread()->trustedPrincipals(); 2045 return get().getPrincipals() == trustedPrincipals || 2046 get().getPrincipals() == 2047 &js::ReconstructedSavedFramePrincipals::IsSystem; 2048 } 2049 2050 bool ConcreteStackFrame<SavedFrame>::constructSavedFrameStack( 2051 JSContext* cx, MutableHandleObject outSavedFrameStack) const { 2052 outSavedFrameStack.set(&get()); 2053 if (!cx->compartment()->wrap(cx, outSavedFrameStack)) { 2054 outSavedFrameStack.set(nullptr); 2055 return false; 2056 } 2057 return true; 2058 } 2059 2060 // A `mozilla::Variant` matcher that converts the inner value of a 2061 // `JS::ubi::AtomOrTwoByteChars` string to a `JSAtom*`. 2062 struct MOZ_STACK_CLASS AtomizingMatcher { 2063 JSContext* cx; 2064 size_t length; 2065 2066 explicit AtomizingMatcher(JSContext* cx, size_t length) 2067 : cx(cx), length(length) {} 2068 2069 JSAtom* operator()(JSAtom* atom) { 2070 MOZ_ASSERT(atom); 2071 return atom; 2072 } 2073 2074 JSAtom* operator()(const char16_t* chars) { 2075 MOZ_ASSERT(chars); 2076 return AtomizeChars(cx, chars, length); 2077 } 2078 }; 2079 2080 JS_PUBLIC_API bool ConstructSavedFrameStackSlow( 2081 JSContext* cx, JS::ubi::StackFrame& frame, 2082 MutableHandleObject outSavedFrameStack) { 2083 Rooted<js::GCLookupVector> stackChain(cx, js::GCLookupVector(cx)); 2084 Rooted<JS::ubi::StackFrame> ubiFrame(cx, frame); 2085 2086 while (ubiFrame.get()) { 2087 // Convert the source and functionDisplayName strings to atoms. 2088 2089 Rooted<JSAtom*> source(cx); 2090 AtomizingMatcher atomizer(cx, ubiFrame.get().sourceLength()); 2091 source = ubiFrame.get().source().match(atomizer); 2092 if (!source) { 2093 return false; 2094 } 2095 2096 Rooted<JSAtom*> functionDisplayName(cx); 2097 auto nameLength = ubiFrame.get().functionDisplayNameLength(); 2098 if (nameLength > 0) { 2099 AtomizingMatcher atomizer(cx, nameLength); 2100 functionDisplayName = 2101 ubiFrame.get().functionDisplayName().match(atomizer); 2102 if (!functionDisplayName) { 2103 return false; 2104 } 2105 } 2106 2107 auto principals = 2108 js::ReconstructedSavedFramePrincipals::getSingleton(ubiFrame.get()); 2109 2110 if (!stackChain.emplaceBack(source, ubiFrame.get().sourceId(), 2111 ubiFrame.get().line(), ubiFrame.get().column(), 2112 functionDisplayName, 2113 /* asyncCause */ nullptr, 2114 /* parent */ nullptr, principals, 2115 /* mutedErrors */ true)) { 2116 ReportOutOfMemory(cx); 2117 return false; 2118 } 2119 2120 ubiFrame = ubiFrame.get().parent(); 2121 } 2122 2123 Rooted<js::SavedFrame*> parentFrame(cx); 2124 for (size_t i = stackChain.length(); i != 0; i--) { 2125 MutableHandle<SavedFrame::Lookup> lookup = stackChain[i - 1]; 2126 lookup.setParent(parentFrame); 2127 parentFrame = cx->realm()->savedStacks().getOrCreateSavedFrame(cx, lookup); 2128 if (!parentFrame) { 2129 return false; 2130 } 2131 } 2132 2133 outSavedFrameStack.set(parentFrame); 2134 return true; 2135 } 2136 2137 } // namespace ubi 2138 } // namespace JS