Bug 1925181 - Properly set small alloc randomization on Android content processes...
[gecko.git] / js / src / vm / SavedStacks.cpp
blobc419e3c5f05eaf39f7950bcbfdb9cba3eee47a94
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/. */
7 #include "vm/SavedStacks.h"
9 #include "mozilla/Attributes.h"
10 #include "mozilla/DebugOnly.h"
12 #include <algorithm>
13 #include <utility>
15 #include "jsapi.h"
16 #include "jsmath.h"
17 #include "jsnum.h"
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"
40 #include "debugger/DebugAPI-inl.h"
41 #include "gc/StableCellHasher-inl.h"
42 #include "vm/GeckoProfiler-inl.h"
43 #include "vm/JSContext-inl.h"
45 using mozilla::AddToHash;
46 using mozilla::DebugOnly;
47 using mozilla::Maybe;
48 using mozilla::Nothing;
49 using mozilla::Some;
51 namespace js {
53 /**
54 * Maximum number of saved frames returned for an async stack.
56 const uint32_t ASYNC_STACK_MAX_FRAME_COUNT = 60;
58 void LiveSavedFrameCache::trace(JSTracer* trc) {
59 if (!initialized()) {
60 return;
63 for (auto* entry = frames->begin(); entry < frames->end(); entry++) {
64 TraceEdge(trc, &entry->savedFrame,
65 "LiveSavedFrameCache::frames SavedFrame");
69 bool LiveSavedFrameCache::insert(JSContext* cx, FramePtr&& framePtr,
70 const jsbytecode* pc,
71 Handle<SavedFrame*> savedFrame) {
72 MOZ_ASSERT(savedFrame);
73 MOZ_ASSERT(initialized());
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);
84 #endif
86 if (!frames->emplaceBack(framePtr, pc, savedFrame)) {
87 ReportOutOfMemory(cx);
88 return false;
91 framePtr.setHasCachedSavedFrame();
93 return true;
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());
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.
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;
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());
123 #endif
124 frames->clear();
125 frame.set(nullptr);
126 return;
129 Key key(framePtr);
130 while (key != frames->back().key) {
131 MOZ_ASSERT(frames->back().savedFrame->realm() == cx->realm());
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();
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());
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;
156 frame.set(frames->back().savedFrame);
159 void LiveSavedFrameCache::findWithoutInvalidation(
160 const FramePtr& framePtr, MutableHandle<SavedFrame*> frame) const {
161 MOZ_ASSERT(initialized());
162 MOZ_ASSERT(framePtr.hasCachedSavedFrame());
164 Key key(framePtr);
165 for (auto& entry : (*frames)) {
166 if (entry.key == key) {
167 frame.set(entry.savedFrame);
168 return;
172 frame.set(nullptr);
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();
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);
217 JSAtom* source;
218 uint32_t sourceId;
220 // Line number (1-origin).
221 uint32_t line;
223 // Columm number in UTF-16 code units.
224 JS::TaggedColumnNumberOneOrigin column;
226 JSAtom* functionDisplayName;
227 JSAtom* asyncCause;
228 SavedFrame* parent;
229 JSPrincipals* principals;
230 bool mutedErrors;
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;
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");
247 using GCLookupVector =
248 GCVector<SavedFrame::Lookup, ASYNC_STACK_MAX_FRAME_COUNT>;
250 template <class Wrapper>
251 class WrappedPtrOperations<SavedFrame::Lookup, Wrapper> {
252 const SavedFrame::Lookup& value() const {
253 return static_cast<const Wrapper*>(this)->get();
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; }
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(); }
276 public:
277 void setParent(SavedFrame* parent) { value().parent = parent; }
279 void setAsyncCause(Handle<JSAtom*> asyncCause) {
280 value().asyncCause = asyncCause;
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;
291 *hashOut = calculateHash(l, parentHash);
292 return true;
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;
301 *hashOut = calculateHash(l, parentHash);
302 return true;
305 /* static */
306 HashNumber SavedFrame::HashPolicy::hash(const Lookup& lookup) {
307 return calculateHash(lookup, SavedFramePtrHasher::hash(lookup.parent));
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));
323 /* static */
324 bool SavedFrame::HashPolicy::match(SavedFrame* existing, const Lookup& lookup) {
325 MOZ_ASSERT(existing);
327 if (existing->getLine() != lookup.line) {
328 return false;
331 if (existing->getColumn() != lookup.column) {
332 return false;
335 if (existing->getParent() != lookup.parent) {
336 return false;
339 if (existing->getPrincipals() != lookup.principals) {
340 return false;
343 JSAtom* source = existing->getSource();
344 if (source != lookup.source) {
345 return false;
348 JSAtom* functionDisplayName = existing->getFunctionDisplayName();
349 if (functionDisplayName != lookup.functionDisplayName) {
350 return false;
353 JSAtom* asyncCause = existing->getAsyncCause();
354 if (asyncCause != lookup.asyncCause) {
355 return false;
358 return true;
361 /* static */
362 void SavedFrame::HashPolicy::rekey(Key& key, const Key& newKey) {
363 key = newKey;
366 /* static */
367 bool SavedFrame::finishSavedFrameInit(JSContext* cx, HandleObject ctor,
368 HandleObject proto) {
369 return FreezeObject(cx, proto);
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
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,
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_,
405 const JSClass SavedFrame::protoClass_ = {
406 "SavedFrame.prototype",
407 JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame),
408 JS_NULL_CLASS_OPS,
409 &SavedFrame::classSpec_,
412 /* static */ const JSFunctionSpec SavedFrame::staticFunctions[] = {
413 JS_FS_END,
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,
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,
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);
445 JSAtom* SavedFrame::getSource() {
446 const Value& v = getReservedSlot(JSSLOT_SOURCE);
447 JSString* s = v.toString();
448 return &s->asAtom();
451 uint32_t SavedFrame::getSourceId() {
452 const Value& v = getReservedSlot(JSSLOT_SOURCEID);
453 return v.toPrivateUint32();
456 uint32_t SavedFrame::getLine() {
457 const Value& v = getReservedSlot(JSSLOT_LINE);
458 return v.toPrivateUint32();
461 JS::TaggedColumnNumberOneOrigin SavedFrame::getColumn() {
462 const Value& v = getReservedSlot(JSSLOT_COLUMN);
463 return JS::TaggedColumnNumberOneOrigin::fromRaw(v.toPrivateUint32());
466 JSAtom* SavedFrame::getFunctionDisplayName() {
467 const Value& v = getReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME);
468 if (v.isNull()) {
469 return nullptr;
471 JSString* s = v.toString();
472 return &s->asAtom();
475 JSAtom* SavedFrame::getAsyncCause() {
476 const Value& v = getReservedSlot(JSSLOT_ASYNCCAUSE);
477 if (v.isNull()) {
478 return nullptr;
480 JSString* s = v.toString();
481 return &s->asAtom();
484 SavedFrame* SavedFrame::getParent() const {
485 const Value& v = getReservedSlot(JSSLOT_PARENT);
486 return v.isObject() ? &v.toObject().as<SavedFrame>() : nullptr;
489 JSPrincipals* SavedFrame::getPrincipals() {
490 const Value& v = getReservedSlot(JSSLOT_PRINCIPALS);
491 if (v.isUndefined()) {
492 return nullptr;
494 return reinterpret_cast<JSPrincipals*>(uintptr_t(v.toPrivate()) & ~0b1);
497 bool SavedFrame::getMutedErrors() {
498 const Value& v = getReservedSlot(JSSLOT_PRINCIPALS);
499 if (v.isUndefined()) {
500 return true;
502 return bool(uintptr_t(v.toPrivate()) & 0b1);
505 void SavedFrame::initSource(JSAtom* source) {
506 MOZ_ASSERT(source);
507 initReservedSlot(JSSLOT_SOURCE, StringValue(source));
510 void SavedFrame::initSourceId(uint32_t sourceId) {
511 initReservedSlot(JSSLOT_SOURCEID, PrivateUint32Value(sourceId));
514 void SavedFrame::initLine(uint32_t line) {
515 initReservedSlot(JSSLOT_LINE, PrivateUint32Value(line));
518 void SavedFrame::initColumn(JS::TaggedColumnNumberOneOrigin column) {
519 if (js::SupportDifferentialTesting()) {
520 column = JS::TaggedColumnNumberOneOrigin::forDifferentialTesting();
522 initReservedSlot(JSSLOT_COLUMN, PrivateUint32Value(column.rawValue()));
525 void SavedFrame::initPrincipalsAndMutedErrors(JSPrincipals* principals,
526 bool mutedErrors) {
527 if (principals) {
528 JS_HoldPrincipals(principals);
530 initPrincipalsAlreadyHeldAndMutedErrors(principals, mutedErrors);
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)));
541 void SavedFrame::initFunctionDisplayName(JSAtom* maybeName) {
542 initReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME,
543 maybeName ? StringValue(maybeName) : NullValue());
546 void SavedFrame::initAsyncCause(JSAtom* maybeCause) {
547 initReservedSlot(JSSLOT_ASYNCCAUSE,
548 maybeCause ? StringValue(maybeCause) : NullValue());
551 void SavedFrame::initParent(SavedFrame* maybeParent) {
552 initReservedSlot(JSSLOT_PARENT, ObjectOrNullValue(maybeParent));
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());
564 if (lookup.functionDisplayName()) {
565 cx->markAtom(lookup.functionDisplayName());
567 if (lookup.asyncCause()) {
568 cx->markAtom(lookup.asyncCause());
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());
581 /* static */
582 SavedFrame* SavedFrame::create(JSContext* cx) {
583 Rooted<GlobalObject*> global(cx, cx->global());
584 cx->check(global);
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());
591 RootedObject proto(cx,
592 GlobalObject::getOrCreateSavedFramePrototype(cx, global));
593 if (!proto) {
594 return nullptr;
596 cx->check(proto);
598 return NewTenuredObjectWithGivenProto<SavedFrame>(cx, proto);
601 bool SavedFrame::isSelfHosted(JSContext* cx) {
602 JSAtom* source = getSource();
603 return source == cx->names().self_hosted_;
606 bool SavedFrame::isWasm() { return getColumn().isWasmFunctionIndex(); }
608 uint32_t SavedFrame::wasmFuncIndex() {
609 return getColumn().toWasmFunctionIndex().value();
612 uint32_t SavedFrame::wasmBytecodeOffset() {
613 MOZ_ASSERT(isWasm());
614 return getLine();
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;
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;
632 MOZ_ASSERT(!ReconstructedSavedFramePrincipals::is(principals));
634 auto framePrincipals = frame->getPrincipals();
636 // Handle SavedFrames that have been reconstructed from stacks in a heap
637 // snapshot.
638 if (framePrincipals == &ReconstructedSavedFramePrincipals::IsSystem) {
639 return cx->runningWithTrustedPrincipals();
641 if (framePrincipals == &ReconstructedSavedFramePrincipals::IsNotSystem) {
642 return true;
645 return subsumes(principals, framePrincipals);
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;
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;
668 if (rootedFrame->getAsyncCause()) {
669 skippedAsync = true;
672 rootedFrame = rootedFrame->getParent();
675 return nullptr;
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);
692 JS_PUBLIC_API JSObject* GetFirstSubsumedSavedFrame(
693 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame,
694 JS::SavedFrameSelfHosted selfHosted) {
695 if (!savedFrame) {
696 return nullptr;
699 auto subsumes = cx->runtime()->securityCallbacks->subsumes;
700 if (!subsumes) {
701 return nullptr;
704 auto matcher = [subsumes](JSContext* cx, JSPrincipals* principals,
705 Handle<SavedFrame*> frame) -> bool {
706 return subsumes(principals, frame->getPrincipals());
709 bool skippedAsync;
710 Rooted<SavedFrame*> frame(cx, &savedFrame->as<SavedFrame>());
711 return GetFirstMatchedFrame(cx, principals, matcher, frame, selfHosted,
712 skippedAsync);
715 [[nodiscard]] static bool SavedFrame_checkThis(JSContext* cx, CallArgs& args,
716 const char* fnName,
717 MutableHandleObject frame) {
718 const Value& thisValue = args.thisv();
720 if (!thisValue.isObject()) {
721 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
722 JSMSG_OBJECT_REQUIRED,
723 InformalValueTypeName(thisValue));
724 return false;
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;
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;
741 // Get the SavedFrame * from the current this value and handle any errors that
742 // might occur therein.
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;
757 } /* namespace js */
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;
767 Rooted<SavedFrame*> frame(cx, obj->maybeUnwrapAs<SavedFrame>());
768 if (!frame) {
769 return nullptr;
772 return GetFirstSubsumedFrame(cx, principals, frame, selfHosted, skippedAsync);
775 namespace JS {
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());
786 bool skippedAsync;
787 Rooted<js::SavedFrame*> frame(
789 UnwrapSavedFrame(cx, principals, savedFrame, selfHosted, skippedAsync));
790 if (!frame) {
791 sourcep.set(cx->runtime()->emptyString);
792 return SavedFrameResult::AccessDenied;
794 sourcep.set(frame->getSource());
796 if (sourcep->isAtom()) {
797 cx->markAtom(&sourcep->asAtom());
799 return SavedFrameResult::Ok;
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());
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;
817 *sourceIdp = frame->getSourceId();
818 return SavedFrameResult::Ok;
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);
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;
837 *linep = frame->getLine();
838 return SavedFrameResult::Ok;
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);
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;
857 *columnp = frame->getColumn();
858 return SavedFrameResult::Ok;
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());
870 bool skippedAsync;
871 Rooted<js::SavedFrame*> frame(
873 UnwrapSavedFrame(cx, principals, savedFrame, selfHosted, skippedAsync));
874 if (!frame) {
875 namep.set(nullptr);
876 return SavedFrameResult::AccessDenied;
878 namep.set(frame->getFunctionDisplayName());
880 if (namep && namep->isAtom()) {
881 cx->markAtom(&namep->asAtom());
883 return SavedFrameResult::Ok;
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());
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;
908 asyncCausep.set(frame->getAsyncCause());
909 if (!asyncCausep && skippedAsync) {
910 asyncCausep.set(cx->names().Async);
913 if (asyncCausep && asyncCausep->isAtom()) {
914 cx->markAtom(&asyncCausep->asAtom());
916 return SavedFrameResult::Ok;
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());
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;
934 Rooted<js::SavedFrame*> parent(cx, frame->getParent());
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(
941 GetFirstSubsumedFrame(cx, principals, parent, selfHosted, skippedAsync));
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);
951 return SavedFrameResult::Ok;
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());
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;
969 Rooted<js::SavedFrame*> parent(cx, frame->getParent());
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(
976 GetFirstSubsumedFrame(cx, principals, parent, selfHosted, skippedAsync));
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);
986 return SavedFrameResult::Ok;
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(']');
999 return NumberValueToStringBuilder(NumberValue(frame->getLine()), sb);
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);
1012 return sb.append("0x") && sb.append(cstr, cstrlen);
1015 return NumberValueToStringBuilder(
1016 NumberValue(frame->getColumn().oneOriginValue()), sb);
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);
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');
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'));
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());
1057 js::JSStringBuilder sb(cx);
1059 if (format == js::StackFormat::Default) {
1060 format = cx->runtime()->stackFormat();
1062 MOZ_ASSERT(format != js::StackFormat::Default);
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.
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;
1078 Rooted<js::SavedFrame*> parent(cx);
1079 do {
1080 MOZ_ASSERT(SavedFrameSubsumedByPrincipals(cx, principals, frame));
1081 MOZ_ASSERT(!frame->isSelfHosted(cx));
1083 parent = frame->getParent();
1084 bool skippedNextAsync;
1085 Rooted<js::SavedFrame*> nextFrame(
1086 cx, js::GetFirstSubsumedFrame(cx, principals, parent,
1087 SavedFrameSelfHosted::Exclude,
1088 skippedNextAsync));
1090 switch (format) {
1091 case js::StackFormat::SpiderMonkey:
1092 if (!FormatSpiderMonkeyStackFrame(cx, sb, frame, indent,
1093 skippedAsync)) {
1094 return false;
1096 break;
1097 case js::StackFormat::V8:
1098 if (!FormatV8StackFrame(cx, sb, frame, indent, !nextFrame)) {
1099 return false;
1101 break;
1102 case js::StackFormat::Default:
1103 MOZ_CRASH("Unexpected value");
1104 break;
1107 frame = nextFrame;
1108 skippedAsync = skippedNextAsync;
1109 } while (frame);
1112 JSString* str = sb.finishString();
1113 if (!str) {
1114 return false;
1116 cx->check(str);
1117 stringp.set(str);
1118 return true;
1121 JS_PUBLIC_API bool IsMaybeWrappedSavedFrame(JSObject* obj) {
1122 MOZ_ASSERT(obj);
1123 return obj->canUnwrapAs<js::SavedFrame>();
1126 JS_PUBLIC_API bool IsUnwrappedSavedFrame(JSObject* obj) {
1127 MOZ_ASSERT(obj);
1128 return obj->is<js::SavedFrame>();
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);
1138 JS_PUBLIC_API JSObject* ConvertSavedFrameToPlainObject(
1139 JSContext* cx, HandleObject savedFrameArg,
1140 SavedFrameSelfHosted selfHosted) {
1141 MOZ_ASSERT(savedFrameArg);
1143 RootedObject savedFrame(cx, savedFrameArg);
1144 RootedObject baseConverted(cx), lastConverted(cx);
1145 RootedValue v(cx);
1147 baseConverted = lastConverted = JS_NewObject(cx, nullptr);
1148 if (!baseConverted) {
1149 return nullptr;
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;
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;
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;
1176 lastConverted = nextConverted;
1177 savedFrame = &v.toObject();
1178 foundParent = true;
1179 break;
1182 } while (foundParent);
1184 return baseConverted;
1187 } /* namespace JS */
1189 namespace js {
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;
1201 args.rval().setString(source);
1202 } else {
1203 args.rval().setNull();
1205 return true;
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();
1219 return true;
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();
1233 return true;
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();
1247 return true;
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;
1262 args.rval().setString(name);
1263 } else {
1264 args.rval().setNull();
1266 return true;
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;
1280 args.rval().setString(asyncCause);
1281 } else {
1282 args.rval().setNull();
1284 return true;
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;
1296 args.rval().setObjectOrNull(asyncParent);
1297 return true;
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;
1309 args.rval().setObjectOrNull(parent);
1310 return true;
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;
1321 args.rval().setString(string);
1322 return true;
1325 bool SavedStacks::saveCurrentStack(
1326 JSContext* cx, MutableHandle<SavedFrame*> frame,
1327 JS::StackCapture&& capture /* = JS::StackCapture(JS::AllFrames()) */) {
1328 MOZ_RELEASE_ASSERT(cx->realm());
1329 MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this);
1331 if (creatingSavedFrame || cx->isExceptionPending() || !cx->global() ||
1332 !cx->global()->isStandardClassResolved(JSProto_Object)) {
1333 frame.set(nullptr);
1334 return true;
1337 AutoGeckoProfilerEntry labelFrame(cx, "js::SavedStacks::saveCurrentStack");
1338 return insertFrames(cx, frame, std::move(capture));
1341 bool SavedStacks::copyAsyncStack(JSContext* cx, HandleObject asyncStack,
1342 HandleString asyncCause,
1343 MutableHandle<SavedFrame*> adoptedStack,
1344 const Maybe<size_t>& maxFrameCount) {
1345 MOZ_RELEASE_ASSERT(cx->realm());
1346 MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this);
1348 Rooted<JSAtom*> asyncCauseAtom(cx, AtomizeString(cx, asyncCause));
1349 if (!asyncCauseAtom) {
1350 return false;
1353 Rooted<SavedFrame*> asyncStackObj(
1354 cx, asyncStack->maybeUnwrapAs<js::SavedFrame>());
1355 MOZ_RELEASE_ASSERT(asyncStackObj);
1356 adoptedStack.set(asyncStackObj);
1358 if (!adoptAsyncStack(cx, adoptedStack, asyncCauseAtom, maxFrameCount)) {
1359 return false;
1362 return true;
1365 void SavedStacks::traceWeak(JSTracer* trc) {
1366 frames.traceWeak(trc);
1367 pcLocationMap.traceWeak(trc);
1370 void SavedStacks::trace(JSTracer* trc) { pcLocationMap.trace(trc); }
1372 uint32_t SavedStacks::count() { return frames.count(); }
1374 void SavedStacks::clear() { frames.clear(); }
1376 size_t SavedStacks::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
1377 return frames.shallowSizeOfExcludingThis(mallocSizeOf) +
1378 pcLocationMap.shallowSizeOfExcludingThis(mallocSizeOf);
1381 // Given that we have captured a stack frame with the given principals and
1382 // source, return true if the requested `StackCapture` has been satisfied and
1383 // stack walking can halt. Return false otherwise (and stack walking and frame
1384 // capturing should continue).
1385 static inline bool captureIsSatisfied(JSContext* cx, JSPrincipals* principals,
1386 const JSAtom* source,
1387 JS::StackCapture& capture) {
1388 class Matcher {
1389 JSContext* cx_;
1390 JSPrincipals* framePrincipals_;
1391 const JSAtom* frameSource_;
1393 public:
1394 Matcher(JSContext* cx, JSPrincipals* principals, const JSAtom* source)
1395 : cx_(cx), framePrincipals_(principals), frameSource_(source) {}
1397 bool operator()(JS::FirstSubsumedFrame& target) {
1398 auto subsumes = cx_->runtime()->securityCallbacks->subsumes;
1399 return (!subsumes || subsumes(target.principals, framePrincipals_)) &&
1400 (!target.ignoreSelfHosted ||
1401 frameSource_ != cx_->names().self_hosted_);
1404 bool operator()(JS::MaxFrames& target) { return target.maxFrames == 1; }
1406 bool operator()(JS::AllFrames&) { return false; }
1409 Matcher m(cx, principals, source);
1410 return capture.match(m);
1413 bool SavedStacks::insertFrames(JSContext* cx, MutableHandle<SavedFrame*> frame,
1414 JS::StackCapture&& capture) {
1415 // In order to look up a cached SavedFrame object, we need to have its parent
1416 // SavedFrame, which means we need to walk the stack from oldest frame to
1417 // youngest. However, FrameIter walks the stack from youngest frame to
1418 // oldest. The solution is to append stack frames to a vector as we walk the
1419 // stack with FrameIter, and then do a second pass through that vector in
1420 // reverse order after the traversal has completed and get or create the
1421 // SavedFrame objects at that time.
1423 // To avoid making many copies of FrameIter (whose copy constructor is
1424 // relatively slow), we use a vector of `SavedFrame::Lookup` objects, which
1425 // only contain the FrameIter data we need. The `SavedFrame::Lookup`
1426 // objects are partially initialized with everything except their parent
1427 // pointers on the first pass, and then we fill in the parent pointers as we
1428 // return in the second pass.
1430 // Accumulate the vector of Lookup objects here, youngest to oldest.
1431 Rooted<js::GCLookupVector> stackChain(cx, js::GCLookupVector(cx));
1433 // If we find a cached saved frame, then that supplies the parent of the
1434 // frames we have placed in stackChain. If we walk the stack all the way
1435 // to the end, this remains null.
1436 Rooted<SavedFrame*> cachedParentFrame(cx, nullptr);
1438 // Choose the right frame iteration strategy to accomodate both
1439 // evalInFramePrev links and the LiveSavedFrameCache. For background, see
1440 // the LiveSavedFrameCache comments in Stack.h.
1442 // If we're using the LiveSavedFrameCache, then don't handle evalInFramePrev
1443 // links by skipping over the frames altogether; that violates the cache's
1444 // assumptions. Instead, traverse the entire stack, but choose each
1445 // SavedFrame's parent as directed by the evalInFramePrev link, if any.
1447 // If we're not using the LiveSavedFrameCache, it's hard to recover the
1448 // frame to which the evalInFramePrev link refers, so we just let FrameIter
1449 // skip those frames. Then each SavedFrame's parent is simply the frame that
1450 // follows it in the stackChain vector, even when it has an evalInFramePrev
1451 // link.
1452 FrameIter iter(cx, capture.is<JS::AllFrames>()
1453 ? FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK
1454 : FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK);
1456 // Once we've seen one frame with its hasCachedSavedFrame bit set, all its
1457 // parents (that can be cached) ought to have it set too.
1458 DebugOnly<bool> seenCached = false;
1460 // If we are using evalInFramePrev links to adjust the parents of debugger
1461 // eval frames, we have to ensure the target frame is cached in the current
1462 // realm. (This might not happen by default if the target frame is
1463 // rematerialized, or if there is an async parent between the debugger eval
1464 // frame and the target frame.) To accomplish this, we keep track of eval
1465 // targets and ensure that we don't stop before they have all been reached.
1466 Vector<AbstractFramePtr, 4, TempAllocPolicy> unreachedEvalTargets(cx);
1468 while (!iter.done()) {
1469 Activation& activation = *iter.activation();
1470 Maybe<LiveSavedFrameCache::FramePtr> framePtr =
1471 LiveSavedFrameCache::FramePtr::create(iter);
1473 if (capture.is<JS::AllFrames>() && iter.hasUsableAbstractFramePtr()) {
1474 unreachedEvalTargets.eraseIfEqual(iter.abstractFramePtr());
1477 if (framePtr) {
1478 // In general, when we reach a frame with its hasCachedSavedFrame bit set,
1479 // all its parents will have the bit set as well. See the
1480 // LiveSavedFrameCache comment in Activation.h for more details. There are
1481 // a few exceptions:
1482 // - Rematerialized frames are always created with the bit clear.
1483 // - Captures using FirstSubsumedFrame ignore async parents and walk the
1484 // real stack. Because we're using different rules for walking the
1485 // stack, we can reach frames that weren't cached in a previous
1486 // AllFrames traversal.
1487 DebugOnly<bool> hasGoodExcuse = framePtr->isRematerializedFrame() ||
1488 capture.is<JS::FirstSubsumedFrame>();
1489 MOZ_ASSERT_IF(seenCached,
1490 framePtr->hasCachedSavedFrame() || hasGoodExcuse);
1491 seenCached |= framePtr->hasCachedSavedFrame();
1493 if (capture.is<JS::AllFrames>() && framePtr->isInterpreterFrame() &&
1494 framePtr->asInterpreterFrame().isDebuggerEvalFrame()) {
1495 AbstractFramePtr target =
1496 framePtr->asInterpreterFrame().evalInFramePrev();
1497 if (!unreachedEvalTargets.append(target)) {
1498 return false;
1503 if (capture.is<JS::AllFrames>() && framePtr &&
1504 framePtr->hasCachedSavedFrame()) {
1505 auto* cache = activation.getLiveSavedFrameCache(cx);
1506 if (!cache) {
1507 return false;
1509 cache->find(cx, *framePtr, iter.pc(), &cachedParentFrame);
1511 // Even though iter.hasCachedSavedFrame() was true, we may still get a
1512 // cache miss, if the frame's pc doesn't match the cache entry's, or if
1513 // the cache was emptied due to a realm mismatch. If we got a cache hit,
1514 // and we do not have to keep looking for unreached eval target frames,
1515 // we can stop traversing the stack and start building the chain.
1516 if (cachedParentFrame && unreachedEvalTargets.empty()) {
1517 break;
1520 // This frame doesn't have a cache entry, despite its hasCachedSavedFrame
1521 // flag being set. If this was due to a pc mismatch, we can clear the flag
1522 // here and set things right. If the cache was emptied due to a realm
1523 // mismatch, we should clear all the frames' flags as we walk to the
1524 // bottom of the stack, so that they are all clear before we start pushing
1525 // any new entries.
1526 framePtr->clearHasCachedSavedFrame();
1529 // We'll be pushing this frame onto stackChain. Gather the information
1530 // needed to construct the SavedFrame::Lookup.
1531 Rooted<LocationValue> location(cx);
1533 AutoRealmUnchecked ar(cx, iter.realm());
1534 if (!cx->realm()->savedStacks().getLocation(cx, iter, &location)) {
1535 return false;
1539 Rooted<JSAtom*> displayAtom(cx, iter.maybeFunctionDisplayAtom());
1541 auto principals = iter.realm()->principals();
1542 MOZ_ASSERT_IF(framePtr && !iter.isWasm(), iter.pc());
1544 if (!stackChain.emplaceBack(location.source(), location.sourceId(),
1545 location.line(), location.column(), displayAtom,
1546 nullptr, // asyncCause
1547 nullptr, // parent (not known yet)
1548 principals, iter.mutedErrors(), framePtr,
1549 iter.pc(), &activation)) {
1550 return false;
1553 if (captureIsSatisfied(cx, principals, location.source(), capture)) {
1554 break;
1557 ++iter;
1558 framePtr = LiveSavedFrameCache::FramePtr::create(iter);
1560 if (iter.activation() != &activation && capture.is<JS::AllFrames>()) {
1561 // If there were no cache hits in the entire activation, clear its
1562 // cache so we'll be able to push new ones when we build the
1563 // SavedFrame chain.
1564 activation.clearLiveSavedFrameCache();
1567 // If we have crossed into a new activation, check whether the prior
1568 // activation had an async parent set.
1570 // If the async call was explicit (async function resumptions, most
1571 // testing facilities), then the async parent stack has priority over
1572 // any actual frames still on the JavaScript stack. If the async call
1573 // was implicit (DOM CallbackObject::CallSetup calls), then the async
1574 // parent stack is used only if there were no other frames on the
1575 // stack.
1577 // Captures using FirstSubsumedFrame expect us to ignore async parents.
1578 if (iter.activation() != &activation && activation.asyncStack() &&
1579 (activation.asyncCallIsExplicit() || iter.done()) &&
1580 !capture.is<JS::FirstSubsumedFrame>()) {
1581 // Atomize the async cause string. There should only be a few
1582 // different strings used.
1583 const char* cause = activation.asyncCause();
1584 Rooted<JSAtom*> causeAtom(cx, AtomizeUTF8Chars(cx, cause, strlen(cause)));
1585 if (!causeAtom) {
1586 return false;
1589 // Translate our capture into a frame count limit for
1590 // adoptAsyncStack, which will impose further limits.
1591 Maybe<size_t> maxFrames =
1592 !capture.is<JS::MaxFrames>() ? Nothing()
1593 : capture.as<JS::MaxFrames>().maxFrames == 0
1594 ? Nothing()
1595 : Some(capture.as<JS::MaxFrames>().maxFrames);
1597 // Clip the stack if needed, attach the async cause string to the
1598 // top frame, and copy it into our compartment if necessary.
1599 Rooted<SavedFrame*> asyncParent(cx, activation.asyncStack());
1600 if (!adoptAsyncStack(cx, &asyncParent, causeAtom, maxFrames)) {
1601 return false;
1603 stackChain[stackChain.length() - 1].setParent(asyncParent);
1604 if (!capture.is<JS::AllFrames>() || unreachedEvalTargets.empty()) {
1605 // In the case of a JS::AllFrames capture, we will be populating the
1606 // LiveSavedFrameCache in the second loop. In the case where there is
1607 // a debugger eval frame on the stack, the second loop will use
1608 // checkForEvalInFramePrev to skip from the eval frame to the "prev"
1609 // frame and assert that when this happens, the "prev"
1610 // frame is in the cache. In cases where there is an async stack
1611 // activation between the debugger eval frame and the "prev" frame,
1612 // breaking here would not populate the "prev" cache entry, causing
1613 // checkForEvalInFramePrev to fail.
1614 break;
1617 // At this point, we would normally stop walking the stack, but
1618 // we're continuing because of an unreached eval target. If a
1619 // previous capture stopped here, it's possible that this frame was
1620 // already cached, but its non-async parent wasn't, which violates
1621 // our `seenCached` invariant. By clearing `seenCached` here, we
1622 // avoid spurious assertions. We continue to enforce the invariant
1623 // for subsequent frames: if any frame above this is cached, then
1624 // all of that frame's parents should also be cached.
1625 seenCached = false;
1628 if (capture.is<JS::MaxFrames>()) {
1629 capture.as<JS::MaxFrames>().maxFrames--;
1633 // Iterate through |stackChain| in reverse order and get or create the
1634 // actual SavedFrame instances.
1635 frame.set(cachedParentFrame);
1636 for (size_t i = stackChain.length(); i != 0; i--) {
1637 MutableHandle<SavedFrame::Lookup> lookup = stackChain[i - 1];
1638 if (!lookup.parent()) {
1639 // The frame may already have an async parent frame set explicitly
1640 // on its activation.
1641 lookup.setParent(frame);
1644 // If necessary, adjust the parent of a debugger eval frame to point to
1645 // the frame in whose scope the eval occurs - if we're using
1646 // LiveSavedFrameCache. Otherwise, we simply ask the FrameIter to follow
1647 // evalInFramePrev links, so that the parent is always the last frame we
1648 // created.
1649 if (capture.is<JS::AllFrames>() && lookup.framePtr()) {
1650 if (!checkForEvalInFramePrev(cx, lookup)) {
1651 return false;
1655 frame.set(getOrCreateSavedFrame(cx, lookup));
1656 if (!frame) {
1657 return false;
1660 if (capture.is<JS::AllFrames>() && lookup.framePtr()) {
1661 auto* cache = lookup.activation()->getLiveSavedFrameCache(cx);
1662 if (!cache ||
1663 !cache->insert(cx, *lookup.framePtr(), lookup.pc(), frame)) {
1664 return false;
1669 return true;
1672 bool SavedStacks::adoptAsyncStack(JSContext* cx,
1673 MutableHandle<SavedFrame*> asyncStack,
1674 Handle<JSAtom*> asyncCause,
1675 const Maybe<size_t>& maxFrameCount) {
1676 MOZ_ASSERT(asyncStack);
1677 MOZ_ASSERT(asyncCause);
1679 // If maxFrameCount is Nothing, the caller asked for an unlimited number of
1680 // stack frames, but async stacks are not limited by the available stack
1681 // memory, so we need to set an arbitrary limit when collecting them. We
1682 // still don't enforce an upper limit if the caller requested more frames.
1683 size_t maxFrames = maxFrameCount.valueOr(ASYNC_STACK_MAX_FRAME_COUNT);
1685 // Turn the chain of frames starting with asyncStack into a vector of Lookup
1686 // objects in |stackChain|, youngest to oldest.
1687 Rooted<js::GCLookupVector> stackChain(cx, js::GCLookupVector(cx));
1688 SavedFrame* currentSavedFrame = asyncStack;
1689 while (currentSavedFrame && stackChain.length() < maxFrames) {
1690 if (!stackChain.emplaceBack(*currentSavedFrame)) {
1691 ReportOutOfMemory(cx);
1692 return false;
1695 currentSavedFrame = currentSavedFrame->getParent();
1698 // Attach the asyncCause to the youngest frame.
1699 stackChain[0].setAsyncCause(asyncCause);
1701 // If we walked the entire stack, and it's in cx's realm, we don't
1702 // need to rebuild the full chain again using the lookup objects - we can
1703 // just use the existing chain. Only the asyncCause on the youngest frame
1704 // needs to be changed.
1705 if (currentSavedFrame == nullptr && asyncStack->realm() == cx->realm()) {
1706 MutableHandle<SavedFrame::Lookup> lookup = stackChain[0];
1707 lookup.setParent(asyncStack->getParent());
1708 asyncStack.set(getOrCreateSavedFrame(cx, lookup));
1709 return !!asyncStack;
1712 // If we captured the maximum number of frames and the caller requested no
1713 // specific limit, we only return half of them. This means that if we do
1714 // many subsequent captures with the same async stack, it's likely we can
1715 // use the optimization above.
1716 if (maxFrameCount.isNothing() && currentSavedFrame) {
1717 stackChain.shrinkBy(ASYNC_STACK_MAX_FRAME_COUNT / 2);
1720 // Iterate through |stackChain| in reverse order and get or create the
1721 // actual SavedFrame instances.
1722 asyncStack.set(nullptr);
1723 while (!stackChain.empty()) {
1724 Rooted<SavedFrame::Lookup> lookup(cx, stackChain.back());
1725 lookup.setParent(asyncStack);
1726 asyncStack.set(getOrCreateSavedFrame(cx, lookup));
1727 if (!asyncStack) {
1728 return false;
1730 stackChain.popBack();
1733 return true;
1736 // Given a |lookup| for which we're about to construct a SavedFrame, if it
1737 // refers to a Debugger eval frame, adjust |lookup|'s parent to be the frame's
1738 // evalInFramePrev target.
1740 // Debugger eval frames run code in the scope of some random older frame on the
1741 // stack (the 'target' frame). It is our custom to report the target as the
1742 // immediate parent of the eval frame. The LiveSavedFrameCache requires us not
1743 // to skip frames, so instead we walk the entire stack, and just give Debugger
1744 // eval frames the right parents as we encounter them.
1746 // Call this function only if we are using the LiveSavedFrameCache; otherwise,
1747 // FrameIter has already taken care of getting us the right parent.
1748 bool SavedStacks::checkForEvalInFramePrev(
1749 JSContext* cx, MutableHandle<SavedFrame::Lookup> lookup) {
1750 MOZ_ASSERT(lookup.framePtr());
1751 if (!lookup.framePtr()->isInterpreterFrame()) {
1752 return true;
1755 InterpreterFrame& interpreterFrame = lookup.framePtr()->asInterpreterFrame();
1756 if (!interpreterFrame.isDebuggerEvalFrame()) {
1757 return true;
1760 FrameIter iter(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
1761 while (!iter.done() &&
1762 (!iter.hasUsableAbstractFramePtr() ||
1763 iter.abstractFramePtr() != interpreterFrame.evalInFramePrev())) {
1764 ++iter;
1767 Maybe<LiveSavedFrameCache::FramePtr> maybeTarget =
1768 LiveSavedFrameCache::FramePtr::create(iter);
1769 MOZ_ASSERT(maybeTarget);
1771 LiveSavedFrameCache::FramePtr target = *maybeTarget;
1773 // If we're caching the frame to which |lookup| refers, then we should
1774 // definitely have the target frame in the cache as well.
1775 MOZ_ASSERT(target.hasCachedSavedFrame());
1777 // Search the chain of activations for a LiveSavedFrameCache that has an
1778 // entry for target.
1779 Rooted<SavedFrame*> saved(cx, nullptr);
1780 for (Activation* act = lookup.activation(); act; act = act->prev()) {
1781 // It's okay to force allocation of a cache here; we're about to put
1782 // something in the top cache, and all the lower ones should exist
1783 // already.
1784 auto* cache = act->getLiveSavedFrameCache(cx);
1785 if (!cache) {
1786 return false;
1789 cache->findWithoutInvalidation(target, &saved);
1790 if (saved) {
1791 break;
1795 // Since |target| has its cached bit set, we should have found it.
1796 MOZ_ALWAYS_TRUE(saved);
1798 // Because we use findWithoutInvalidation here, we can technically get a
1799 // SavedFrame here for any realm. That shouldn't happen here because
1800 // checkForEvalInFramePrev is only called _after_ the parent frames have
1801 // been constructed, but if something prevents the chain from being properly
1802 // reconstructed, that invariant could be accidentally broken.
1803 MOZ_ASSERT(saved->realm() == cx->realm());
1805 lookup.setParent(saved);
1806 return true;
1809 SavedFrame* SavedStacks::getOrCreateSavedFrame(
1810 JSContext* cx, Handle<SavedFrame::Lookup> lookup) {
1811 const SavedFrame::Lookup& lookupInstance = lookup.get();
1812 DependentAddPtr<SavedFrame::Set> p(cx, frames, lookupInstance);
1813 if (p) {
1814 MOZ_ASSERT(*p);
1815 return *p;
1818 Rooted<SavedFrame*> frame(cx, createFrameFromLookup(cx, lookup));
1819 if (!frame) {
1820 return nullptr;
1823 if (!p.add(cx, frames, lookupInstance, frame)) {
1824 return nullptr;
1827 return frame;
1830 SavedFrame* SavedStacks::createFrameFromLookup(
1831 JSContext* cx, Handle<SavedFrame::Lookup> lookup) {
1832 Rooted<SavedFrame*> frame(cx, SavedFrame::create(cx));
1833 if (!frame) {
1834 return nullptr;
1836 frame->initFromLookup(cx, lookup);
1838 if (!FreezeObject(cx, frame)) {
1839 return nullptr;
1842 return frame;
1845 bool SavedStacks::getLocation(JSContext* cx, const FrameIter& iter,
1846 MutableHandle<LocationValue> locationp) {
1847 // We should only ever be caching location values for scripts in this
1848 // compartment. Otherwise, we would get dead cross-compartment scripts in
1849 // the cache because our compartment's sweep method isn't called when their
1850 // compartment gets collected.
1851 MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this);
1852 cx->check(iter.compartment());
1854 // When we have a |JSScript| for this frame, use a potentially memoized
1855 // location from our PCLocationMap and copy it into |locationp|. When we do
1856 // not have a |JSScript| for this frame (wasm frames), we take a slow path
1857 // that doesn't employ memoization, and update |locationp|'s slots directly.
1859 if (iter.isWasm()) {
1860 // Only asm.js has a displayURL.
1861 if (const char16_t* displayURL = iter.displayURL()) {
1862 locationp.setSource(AtomizeChars(cx, displayURL, js_strlen(displayURL)));
1863 } else {
1864 const char* filename = iter.filename() ? iter.filename() : "";
1865 locationp.setSource(AtomizeUTF8Chars(cx, filename, strlen(filename)));
1867 if (!locationp.source()) {
1868 return false;
1871 JS::TaggedColumnNumberOneOrigin column;
1872 locationp.setLine(iter.computeLine(&column));
1873 locationp.setColumn(column);
1874 return true;
1877 RootedScript script(cx, iter.script());
1878 jsbytecode* pc = iter.pc();
1880 PCLocationMap::AddPtr p = pcLocationMap.lookupForAdd(PCKey(script, pc));
1882 if (!p) {
1883 Rooted<JSAtom*> source(cx);
1884 if (const char16_t* displayURL = iter.displayURL()) {
1885 source = AtomizeChars(cx, displayURL, js_strlen(displayURL));
1886 } else {
1887 const char* filename = script->filename() ? script->filename() : "";
1888 source = AtomizeUTF8Chars(cx, filename, strlen(filename));
1890 if (!source) {
1891 return false;
1894 uint32_t sourceId = script->scriptSource()->id();
1895 JS::LimitedColumnNumberOneOrigin column;
1896 uint32_t line = PCToLineNumber(script, pc, &column);
1898 PCKey key(script, pc);
1899 LocationValue value(source, sourceId, line,
1900 JS::TaggedColumnNumberOneOrigin(column));
1901 if (!pcLocationMap.add(p, key, value)) {
1902 ReportOutOfMemory(cx);
1903 return false;
1907 locationp.set(p->value());
1908 return true;
1911 void SavedStacks::chooseSamplingProbability(Realm* realm) {
1913 JSRuntime* runtime = realm->runtimeFromMainThread();
1914 if (runtime->recordAllocationCallback) {
1915 // The runtime is tracking allocations across all realms, in this case
1916 // ignore all of the debugger values, and use the runtime's probability.
1917 this->setSamplingProbability(runtime->allocationSamplingProbability);
1918 return;
1922 // Use unbarriered version to prevent triggering read barrier while
1923 // collecting, this is safe as long as global does not escape.
1924 GlobalObject* global = realm->unsafeUnbarrieredMaybeGlobal();
1925 if (!global) {
1926 return;
1929 Maybe<double> probability = DebugAPI::allocationSamplingProbability(global);
1930 if (probability.isNothing()) {
1931 return;
1934 this->setSamplingProbability(*probability);
1937 void SavedStacks::setSamplingProbability(double probability) {
1938 if (!bernoulliSeeded) {
1939 mozilla::Array<uint64_t, 2> seed;
1940 GenerateXorShift128PlusSeed(seed);
1941 bernoulli.setRandomState(seed[0], seed[1]);
1942 bernoulliSeeded = true;
1945 bernoulli.setProbability(probability);
1948 JSObject* SavedStacks::MetadataBuilder::build(
1949 JSContext* cx, HandleObject target,
1950 AutoEnterOOMUnsafeRegion& oomUnsafe) const {
1951 RootedObject obj(cx, target);
1953 SavedStacks& stacks = cx->realm()->savedStacks();
1954 if (!stacks.bernoulli.trial()) {
1955 return nullptr;
1958 Rooted<SavedFrame*> frame(cx);
1959 if (!stacks.saveCurrentStack(cx, &frame)) {
1960 oomUnsafe.crash("SavedStacksMetadataBuilder");
1963 if (!DebugAPI::onLogAllocationSite(cx, obj, frame,
1964 mozilla::TimeStamp::Now())) {
1965 oomUnsafe.crash("SavedStacksMetadataBuilder");
1968 auto recordAllocationCallback =
1969 cx->realm()->runtimeFromMainThread()->recordAllocationCallback;
1970 if (recordAllocationCallback) {
1971 // The following code translates the JS-specific information, into an
1972 // RecordAllocationInfo object that can be consumed outside of SpiderMonkey.
1974 auto node = JS::ubi::Node(obj.get());
1976 // Pass the non-SpiderMonkey specific information back to the
1977 // callback to get it out of the JS engine.
1978 recordAllocationCallback(JS::RecordAllocationInfo{
1979 node.typeName(), node.jsObjectClassName(), node.descriptiveTypeName(),
1980 JS::ubi::CoarseTypeToString(node.coarseType()),
1981 node.size(cx->runtime()->debuggerMallocSizeOf),
1982 gc::IsInsideNursery(obj)});
1985 MOZ_ASSERT_IF(frame, !frame->is<WrapperObject>());
1986 return frame;
1989 const SavedStacks::MetadataBuilder SavedStacks::metadataBuilder;
1991 /* static */
1992 MOZ_CONSTINIT ReconstructedSavedFramePrincipals
1993 ReconstructedSavedFramePrincipals::IsSystem;
1994 /* static */
1995 MOZ_CONSTINIT ReconstructedSavedFramePrincipals
1996 ReconstructedSavedFramePrincipals::IsNotSystem;
1998 UniqueChars BuildUTF8StackString(JSContext* cx, JSPrincipals* principals,
1999 HandleObject stack) {
2000 RootedString stackStr(cx);
2001 if (!JS::BuildStackString(cx, principals, stack, &stackStr)) {
2002 return nullptr;
2005 return JS_EncodeStringToUTF8(cx, stackStr);
2008 } /* namespace js */
2010 namespace JS {
2011 namespace ubi {
2013 bool ConcreteStackFrame<SavedFrame>::isSystem() const {
2014 auto trustedPrincipals = get().runtimeFromAnyThread()->trustedPrincipals();
2015 return get().getPrincipals() == trustedPrincipals ||
2016 get().getPrincipals() ==
2017 &js::ReconstructedSavedFramePrincipals::IsSystem;
2020 bool ConcreteStackFrame<SavedFrame>::constructSavedFrameStack(
2021 JSContext* cx, MutableHandleObject outSavedFrameStack) const {
2022 outSavedFrameStack.set(&get());
2023 if (!cx->compartment()->wrap(cx, outSavedFrameStack)) {
2024 outSavedFrameStack.set(nullptr);
2025 return false;
2027 return true;
2030 // A `mozilla::Variant` matcher that converts the inner value of a
2031 // `JS::ubi::AtomOrTwoByteChars` string to a `JSAtom*`.
2032 struct MOZ_STACK_CLASS AtomizingMatcher {
2033 JSContext* cx;
2034 size_t length;
2036 explicit AtomizingMatcher(JSContext* cx, size_t length)
2037 : cx(cx), length(length) {}
2039 JSAtom* operator()(JSAtom* atom) {
2040 MOZ_ASSERT(atom);
2041 return atom;
2044 JSAtom* operator()(const char16_t* chars) {
2045 MOZ_ASSERT(chars);
2046 return AtomizeChars(cx, chars, length);
2050 JS_PUBLIC_API bool ConstructSavedFrameStackSlow(
2051 JSContext* cx, JS::ubi::StackFrame& frame,
2052 MutableHandleObject outSavedFrameStack) {
2053 Rooted<js::GCLookupVector> stackChain(cx, js::GCLookupVector(cx));
2054 Rooted<JS::ubi::StackFrame> ubiFrame(cx, frame);
2056 while (ubiFrame.get()) {
2057 // Convert the source and functionDisplayName strings to atoms.
2059 Rooted<JSAtom*> source(cx);
2060 AtomizingMatcher atomizer(cx, ubiFrame.get().sourceLength());
2061 source = ubiFrame.get().source().match(atomizer);
2062 if (!source) {
2063 return false;
2066 Rooted<JSAtom*> functionDisplayName(cx);
2067 auto nameLength = ubiFrame.get().functionDisplayNameLength();
2068 if (nameLength > 0) {
2069 AtomizingMatcher atomizer(cx, nameLength);
2070 functionDisplayName =
2071 ubiFrame.get().functionDisplayName().match(atomizer);
2072 if (!functionDisplayName) {
2073 return false;
2077 auto principals =
2078 js::ReconstructedSavedFramePrincipals::getSingleton(ubiFrame.get());
2080 if (!stackChain.emplaceBack(source, ubiFrame.get().sourceId(),
2081 ubiFrame.get().line(), ubiFrame.get().column(),
2082 functionDisplayName,
2083 /* asyncCause */ nullptr,
2084 /* parent */ nullptr, principals,
2085 /* mutedErrors */ true)) {
2086 ReportOutOfMemory(cx);
2087 return false;
2090 ubiFrame = ubiFrame.get().parent();
2093 Rooted<js::SavedFrame*> parentFrame(cx);
2094 for (size_t i = stackChain.length(); i != 0; i--) {
2095 MutableHandle<SavedFrame::Lookup> lookup = stackChain[i - 1];
2096 lookup.setParent(parentFrame);
2097 parentFrame = cx->realm()->savedStacks().getOrCreateSavedFrame(cx, lookup);
2098 if (!parentFrame) {
2099 return false;
2103 outSavedFrameStack.set(parentFrame);
2104 return true;
2107 } // namespace ubi
2108 } // namespace JS