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"
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"
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"
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
;
48 using mozilla::Nothing
;
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
) {
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
,
71 Handle
<SavedFrame
*> savedFrame
) {
72 MOZ_ASSERT(savedFrame
);
73 MOZ_ASSERT(initialized());
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
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
);
86 if (!frames
->emplaceBack(framePtr
, pc
, savedFrame
)) {
87 ReportOutOfMemory(cx
);
91 framePtr
.setHasCachedSavedFrame();
96 void LiveSavedFrameCache::find(JSContext
* cx
, FramePtr
& framePtr
,
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()) {
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()) {
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());
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.
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
) {
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());
165 for (auto& entry
: (*frames
)) {
166 if (entry
.key
== key
) {
167 frame
.set(entry
.savedFrame
);
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
,
180 const Maybe
<LiveSavedFrameCache::FramePtr
>& framePtr
= Nothing(),
181 jsbytecode
* pc
= nullptr, Activation
* activation
= nullptr)
186 functionDisplayName(functionDisplayName
),
187 asyncCause(asyncCause
),
189 principals(principals
),
190 mutedErrors(mutedErrors
),
193 activation(activation
) {
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()),
213 activation(nullptr) {
220 // Line number (1-origin).
223 // Columm number in UTF-16 code units.
224 JS::TaggedColumnNumberOneOrigin column
;
226 JSAtom
* functionDisplayName
;
229 JSPrincipals
* principals
;
232 // These are used only by the LiveSavedFrameCache and not used for identity or
234 Maybe
<LiveSavedFrameCache::FramePtr
> framePtr
;
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();
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(); }
277 void setParent(SavedFrame
* parent
) { value().parent
= parent
; }
279 void setAsyncCause(Handle
<JSAtom
*> asyncCause
) {
280 value().asyncCause
= asyncCause
;
285 bool SavedFrame::HashPolicy::maybeGetHash(const Lookup
& l
,
286 HashNumber
* hashOut
) {
287 HashNumber parentHash
;
288 if (!SavedFramePtrHasher::maybeGetHash(l
.parent
, &parentHash
)) {
291 *hashOut
= calculateHash(l
, parentHash
);
296 bool SavedFrame::HashPolicy::ensureHash(const Lookup
& l
, HashNumber
* hashOut
) {
297 HashNumber parentHash
;
298 if (!SavedFramePtrHasher::ensureHash(l
.parent
, &parentHash
)) {
301 *hashOut
= calculateHash(l
, parentHash
);
306 HashNumber
SavedFrame::HashPolicy::hash(const Lookup
& lookup
) {
307 return calculateHash(lookup
, SavedFramePtrHasher::hash(lookup
.parent
));
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
));
324 bool SavedFrame::HashPolicy::match(SavedFrame
* existing
, const Lookup
& lookup
) {
325 MOZ_ASSERT(existing
);
327 if (existing
->getLine() != lookup
.line
) {
331 if (existing
->getColumn() != lookup
.column
) {
335 if (existing
->getParent() != lookup
.parent
) {
339 if (existing
->getPrincipals() != lookup
.principals
) {
343 JSAtom
* source
= existing
->getSource();
344 if (source
!= lookup
.source
) {
348 JSAtom
* functionDisplayName
= existing
->getFunctionDisplayName();
349 if (functionDisplayName
!= lookup
.functionDisplayName
) {
353 JSAtom
* asyncCause
= existing
->getAsyncCause();
354 if (asyncCause
!= lookup
.asyncCause
) {
362 void SavedFrame::HashPolicy::rekey(Key
& key
, const Key
& newKey
) {
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
378 nullptr, // mayResolve
379 SavedFrame::finalize
, // finalize
381 nullptr, // construct
385 const ClassSpec
SavedFrame::classSpec_
= {
386 GenericCreateConstructor
<SavedFrame::construct
, 0, gc::AllocKind::FUNCTION
>,
387 GenericCreatePrototype
<SavedFrame
>,
388 SavedFrame::staticFunctions
,
390 SavedFrame::protoFunctions
,
391 SavedFrame::protoAccessors
,
392 SavedFrame::finishSavedFrameInit
,
393 ClassSpec::DontDefineConstructor
,
396 /* static */ const JSClass
SavedFrame::class_
= {
398 JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT
) |
399 JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame
) |
400 JSCLASS_FOREGROUND_FINALIZE
,
402 &SavedFrame::classSpec_
,
405 const JSClass
SavedFrame::protoClass_
= {
406 "SavedFrame.prototype",
407 JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame
),
409 &SavedFrame::classSpec_
,
412 /* static */ const JSFunctionSpec
SavedFrame::staticFunctions
[] = {
416 /* static */ const JSFunctionSpec
SavedFrame::protoFunctions
[] = {
417 JS_FN("constructor", SavedFrame::construct
, 0, 0),
418 JS_FN("toString", SavedFrame::toStringMethod
, 0, 0),
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
),
436 void SavedFrame::finalize(JS::GCContext
* gcx
, JSObject
* obj
) {
437 MOZ_ASSERT(gcx
->onMainThread());
438 JSPrincipals
* p
= obj
->as
<SavedFrame
>().getPrincipals();
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();
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
);
471 JSString
* s
= v
.toString();
475 JSAtom
* SavedFrame::getAsyncCause() {
476 const Value
& v
= getReservedSlot(JSSLOT_ASYNCCAUSE
);
480 JSString
* s
= v
.toString();
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()) {
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()) {
502 return bool(uintptr_t(v
.toPrivate()) & 0b1);
505 void SavedFrame::initSource(JSAtom
* 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
,
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());
582 SavedFrame
* SavedFrame::create(JSContext
* cx
) {
583 Rooted
<GlobalObject
*> global(cx
, cx
->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
));
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());
618 bool SavedFrame::construct(JSContext
* cx
, unsigned argc
, Value
* vp
) {
619 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_NO_CONSTRUCTOR
,
624 static bool SavedFrameSubsumedByPrincipals(JSContext
* cx
,
625 JSPrincipals
* principals
,
626 Handle
<SavedFrame
*> frame
) {
627 auto subsumes
= cx
->runtime()->securityCallbacks
->subsumes
;
632 MOZ_ASSERT(!ReconstructedSavedFramePrincipals::is(principals
));
634 auto framePrincipals
= frame
->getPrincipals();
636 // Handle SavedFrames that have been reconstructed from stacks in a heap
638 if (framePrincipals
== &ReconstructedSavedFramePrincipals::IsSystem
) {
639 return cx
->runningWithTrustedPrincipals();
641 if (framePrincipals
== &ReconstructedSavedFramePrincipals::IsNotSystem
) {
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
,
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
)) {
668 if (rootedFrame
->getAsyncCause()) {
672 rootedFrame
= rootedFrame
->getParent();
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
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
) {
699 auto subsumes
= cx
->runtime()->securityCallbacks
->subsumes
;
704 auto matcher
= [subsumes
](JSContext
* cx
, JSPrincipals
* principals
,
705 Handle
<SavedFrame
*> frame
) -> bool {
706 return subsumes(principals
, frame
->getPrincipals());
710 Rooted
<SavedFrame
*> frame(cx
, &savedFrame
->as
<SavedFrame
>());
711 return GetFirstMatchedFrame(cx
, principals
, matcher
, frame
, selfHosted
,
715 [[nodiscard
]] static bool SavedFrame_checkThis(JSContext
* cx
, CallArgs
& args
,
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
));
727 if (!thisValue
.toObject().canUnwrapAs
<SavedFrame
>()) {
728 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
729 JSMSG_INCOMPATIBLE_PROTO
, SavedFrame::class_
.name
,
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());
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:
748 // - const char* fnName
749 // These parameters will be defined after calling this macro:
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;
759 js::SavedFrame
* js::UnwrapSavedFrame(JSContext
* cx
, JSPrincipals
* principals
,
761 JS::SavedFrameSelfHosted selfHosted
,
762 bool& skippedAsync
) {
767 Rooted
<SavedFrame
*> frame(cx
, obj
->maybeUnwrapAs
<SavedFrame
>());
772 return GetFirstSubsumedFrame(cx
, principals
, frame
, selfHosted
, skippedAsync
);
777 JS_PUBLIC_API SavedFrameResult
GetSavedFrameSource(
778 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
779 MutableHandleString sourcep
,
780 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
781 js::AssertHeapIsIdle();
783 MOZ_RELEASE_ASSERT(cx
->realm());
787 Rooted
<js::SavedFrame
*> frame(
789 UnwrapSavedFrame(cx
, principals
, savedFrame
, selfHosted
, skippedAsync
));
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
,
805 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
806 js::AssertHeapIsIdle();
808 MOZ_RELEASE_ASSERT(cx
->realm());
811 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
812 selfHosted
, skippedAsync
));
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
,
824 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
825 js::AssertHeapIsIdle();
827 MOZ_RELEASE_ASSERT(cx
->realm());
831 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
832 selfHosted
, skippedAsync
));
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();
847 MOZ_RELEASE_ASSERT(cx
->realm());
851 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
852 selfHosted
, skippedAsync
));
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();
867 MOZ_RELEASE_ASSERT(cx
->realm());
871 Rooted
<js::SavedFrame
*> frame(
873 UnwrapSavedFrame(cx
, principals
, savedFrame
, selfHosted
, skippedAsync
));
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();
892 MOZ_RELEASE_ASSERT(cx
->realm());
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
));
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();
925 MOZ_RELEASE_ASSERT(cx
->realm());
928 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
929 selfHosted
, skippedAsync
));
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
);
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();
960 MOZ_RELEASE_ASSERT(cx
->realm());
963 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
964 selfHosted
, skippedAsync
));
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
)) {
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()),
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
;
1009 Uint32ToHexCString(&cbuf
, frame
->wasmBytecodeOffset(), &cstrlen
);
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
,
1039 Rooted
<JSAtom
*> name(cx
, frame
->getFunctionDisplayName());
1040 return sb
.appendN(' ', indent
+ 4) && sb
.append('a') && sb
.append('t') &&
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
,
1051 MutableHandleString stringp
, size_t indent
,
1052 js::StackFormat format
) {
1053 js::AssertHeapIsIdle();
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.
1070 Rooted
<js::SavedFrame
*> frame(
1071 cx
, UnwrapSavedFrame(cx
, principals
, stack
,
1072 SavedFrameSelfHosted::Exclude
, skippedAsync
));
1074 stringp
.set(cx
->runtime()->emptyString
);
1078 Rooted
<js::SavedFrame
*> parent(cx
);
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
,
1091 case js::StackFormat::SpiderMonkey
:
1092 if (!FormatSpiderMonkeyStackFrame(cx
, sb
, frame
, indent
,
1097 case js::StackFormat::V8
:
1098 if (!FormatV8StackFrame(cx
, sb
, frame
, indent
, !nextFrame
)) {
1102 case js::StackFormat::Default
:
1103 MOZ_CRASH("Unexpected value");
1108 skippedAsync
= skippedNextAsync
;
1112 JSString
* str
= sb
.finishString();
1121 JS_PUBLIC_API
bool IsMaybeWrappedSavedFrame(JSObject
* obj
) {
1123 return obj
->canUnwrapAs
<js::SavedFrame
>();
1126 JS_PUBLIC_API
bool IsUnwrappedSavedFrame(JSObject
* obj
) {
1128 return obj
->is
<js::SavedFrame
>();
1131 static bool AssignProperty(JSContext
* cx
, HandleObject dst
, HandleObject src
,
1132 const char* property
) {
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
);
1147 baseConverted
= lastConverted
= JS_NewObject(cx
, nullptr);
1148 if (!baseConverted
) {
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")) {
1163 const char* parentProperties
[] = {"parent", "asyncParent"};
1164 foundParent
= false;
1165 for (const char* prop
: parentProperties
) {
1166 if (!JS_GetProperty(cx
, savedFrame
, prop
, &v
)) {
1170 RootedObject
nextConverted(cx
, JS_NewObject(cx
, nullptr));
1171 if (!nextConverted
||
1172 !JS_DefineProperty(cx
, lastConverted
, prop
, nextConverted
,
1173 JSPROP_ENUMERATE
)) {
1176 lastConverted
= nextConverted
;
1177 savedFrame
= &v
.toObject();
1182 } while (foundParent
);
1184 return baseConverted
;
1187 } /* namespace JS */
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
)) {
1201 args
.rval().setString(source
);
1203 args
.rval().setNull();
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();
1213 if (JS::GetSavedFrameSourceId(cx
, principals
, frame
, &sourceId
) ==
1214 JS::SavedFrameResult::Ok
) {
1215 args
.rval().setNumber(sourceId
);
1217 args
.rval().setNull();
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();
1227 if (JS::GetSavedFrameLine(cx
, principals
, frame
, &line
) ==
1228 JS::SavedFrameResult::Ok
) {
1229 args
.rval().setNumber(line
);
1231 args
.rval().setNull();
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());
1245 args
.rval().setNull();
1251 bool SavedFrame::functionDisplayNameProperty(JSContext
* cx
, unsigned argc
,
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
)) {
1262 args
.rval().setString(name
);
1264 args
.rval().setNull();
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
)) {
1280 args
.rval().setString(asyncCause
);
1282 args
.rval().setNull();
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
)) {
1296 args
.rval().setObjectOrNull(asyncParent
);
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
)) {
1309 args
.rval().setObjectOrNull(parent
);
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
)) {
1321 args
.rval().setString(string
);
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
)) {
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
) {
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
)) {
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
) {
1390 JSPrincipals
* framePrincipals_
;
1391 const JSAtom
* frameSource_
;
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
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());
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
)) {
1503 if (capture
.is
<JS::AllFrames
>() && framePtr
&&
1504 framePtr
->hasCachedSavedFrame()) {
1505 auto* cache
= activation
.getLiveSavedFrameCache(cx
);
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()) {
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
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
)) {
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
)) {
1553 if (captureIsSatisfied(cx
, principals
, location
.source(), capture
)) {
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
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
)));
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
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
)) {
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.
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.
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
1649 if (capture
.is
<JS::AllFrames
>() && lookup
.framePtr()) {
1650 if (!checkForEvalInFramePrev(cx
, lookup
)) {
1655 frame
.set(getOrCreateSavedFrame(cx
, lookup
));
1660 if (capture
.is
<JS::AllFrames
>() && lookup
.framePtr()) {
1661 auto* cache
= lookup
.activation()->getLiveSavedFrameCache(cx
);
1663 !cache
->insert(cx
, *lookup
.framePtr(), lookup
.pc(), frame
)) {
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
);
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
));
1730 stackChain
.popBack();
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()) {
1755 InterpreterFrame
& interpreterFrame
= lookup
.framePtr()->asInterpreterFrame();
1756 if (!interpreterFrame
.isDebuggerEvalFrame()) {
1760 FrameIter
iter(cx
, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK
);
1761 while (!iter
.done() &&
1762 (!iter
.hasUsableAbstractFramePtr() ||
1763 iter
.abstractFramePtr() != interpreterFrame
.evalInFramePrev())) {
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
1784 auto* cache
= act
->getLiveSavedFrameCache(cx
);
1789 cache
->findWithoutInvalidation(target
, &saved
);
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
);
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
);
1818 Rooted
<SavedFrame
*> frame(cx
, createFrameFromLookup(cx
, lookup
));
1823 if (!p
.add(cx
, frames
, lookupInstance
, frame
)) {
1830 SavedFrame
* SavedStacks::createFrameFromLookup(
1831 JSContext
* cx
, Handle
<SavedFrame::Lookup
> lookup
) {
1832 Rooted
<SavedFrame
*> frame(cx
, SavedFrame::create(cx
));
1836 frame
->initFromLookup(cx
, lookup
);
1838 if (!FreezeObject(cx
, 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
)));
1864 const char* filename
= iter
.filename() ? iter
.filename() : "";
1865 locationp
.setSource(AtomizeUTF8Chars(cx
, filename
, strlen(filename
)));
1867 if (!locationp
.source()) {
1871 JS::TaggedColumnNumberOneOrigin column
;
1872 locationp
.setLine(iter
.computeLine(&column
));
1873 locationp
.setColumn(column
);
1877 RootedScript
script(cx
, iter
.script());
1878 jsbytecode
* pc
= iter
.pc();
1880 PCLocationMap::AddPtr p
= pcLocationMap
.lookupForAdd(PCKey(script
, pc
));
1883 Rooted
<JSAtom
*> source(cx
);
1884 if (const char16_t
* displayURL
= iter
.displayURL()) {
1885 source
= AtomizeChars(cx
, displayURL
, js_strlen(displayURL
));
1887 const char* filename
= script
->filename() ? script
->filename() : "";
1888 source
= AtomizeUTF8Chars(cx
, filename
, strlen(filename
));
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
);
1907 locationp
.set(p
->value());
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
);
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();
1929 Maybe
<double> probability
= DebugAPI::allocationSamplingProbability(global
);
1930 if (probability
.isNothing()) {
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()) {
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
>());
1989 const SavedStacks::MetadataBuilder
SavedStacks::metadataBuilder
;
1992 MOZ_CONSTINIT ReconstructedSavedFramePrincipals
1993 ReconstructedSavedFramePrincipals::IsSystem
;
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
)) {
2005 return JS_EncodeStringToUTF8(cx
, stackStr
);
2008 } /* namespace js */
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);
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
{
2036 explicit AtomizingMatcher(JSContext
* cx
, size_t length
)
2037 : cx(cx
), length(length
) {}
2039 JSAtom
* operator()(JSAtom
* atom
) {
2044 JSAtom
* operator()(const char16_t
* 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
);
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
) {
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
);
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
);
2103 outSavedFrameStack
.set(parentFrame
);