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/StringBuffer.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
};
395 /* static */ const JSClass
SavedFrame::class_
= {
397 JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT
) |
398 JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame
) |
399 JSCLASS_FOREGROUND_FINALIZE
,
400 &SavedFrameClassOps
, &SavedFrame::classSpec_
};
402 const JSClass
SavedFrame::protoClass_
= {
403 "SavedFrame.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame
),
404 JS_NULL_CLASS_OPS
, &SavedFrame::classSpec_
};
406 /* static */ const JSFunctionSpec
SavedFrame::staticFunctions
[] = {JS_FS_END
};
408 /* static */ const JSFunctionSpec
SavedFrame::protoFunctions
[] = {
409 JS_FN("constructor", SavedFrame::construct
, 0, 0),
410 JS_FN("toString", SavedFrame::toStringMethod
, 0, 0), JS_FS_END
};
412 /* static */ const JSPropertySpec
SavedFrame::protoAccessors
[] = {
413 JS_PSG("source", SavedFrame::sourceProperty
, 0),
414 JS_PSG("sourceId", SavedFrame::sourceIdProperty
, 0),
415 JS_PSG("line", SavedFrame::lineProperty
, 0),
416 JS_PSG("column", SavedFrame::columnProperty
, 0),
417 JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty
, 0),
418 JS_PSG("asyncCause", SavedFrame::asyncCauseProperty
, 0),
419 JS_PSG("asyncParent", SavedFrame::asyncParentProperty
, 0),
420 JS_PSG("parent", SavedFrame::parentProperty
, 0),
421 JS_STRING_SYM_PS(toStringTag
, "SavedFrame", JSPROP_READONLY
),
425 void SavedFrame::finalize(JS::GCContext
* gcx
, JSObject
* obj
) {
426 MOZ_ASSERT(gcx
->onMainThread());
427 JSPrincipals
* p
= obj
->as
<SavedFrame
>().getPrincipals();
429 JSRuntime
* rt
= obj
->runtimeFromMainThread();
430 JS_DropPrincipals(rt
->mainContextFromOwnThread(), p
);
434 JSAtom
* SavedFrame::getSource() {
435 const Value
& v
= getReservedSlot(JSSLOT_SOURCE
);
436 JSString
* s
= v
.toString();
440 uint32_t SavedFrame::getSourceId() {
441 const Value
& v
= getReservedSlot(JSSLOT_SOURCEID
);
442 return v
.toPrivateUint32();
445 uint32_t SavedFrame::getLine() {
446 const Value
& v
= getReservedSlot(JSSLOT_LINE
);
447 return v
.toPrivateUint32();
450 JS::TaggedColumnNumberOneOrigin
SavedFrame::getColumn() {
451 const Value
& v
= getReservedSlot(JSSLOT_COLUMN
);
452 return JS::TaggedColumnNumberOneOrigin::fromRaw(v
.toPrivateUint32());
455 JSAtom
* SavedFrame::getFunctionDisplayName() {
456 const Value
& v
= getReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME
);
460 JSString
* s
= v
.toString();
464 JSAtom
* SavedFrame::getAsyncCause() {
465 const Value
& v
= getReservedSlot(JSSLOT_ASYNCCAUSE
);
469 JSString
* s
= v
.toString();
473 SavedFrame
* SavedFrame::getParent() const {
474 const Value
& v
= getReservedSlot(JSSLOT_PARENT
);
475 return v
.isObject() ? &v
.toObject().as
<SavedFrame
>() : nullptr;
478 JSPrincipals
* SavedFrame::getPrincipals() {
479 const Value
& v
= getReservedSlot(JSSLOT_PRINCIPALS
);
480 if (v
.isUndefined()) {
483 return reinterpret_cast<JSPrincipals
*>(uintptr_t(v
.toPrivate()) & ~0b1);
486 bool SavedFrame::getMutedErrors() {
487 const Value
& v
= getReservedSlot(JSSLOT_PRINCIPALS
);
488 if (v
.isUndefined()) {
491 return bool(uintptr_t(v
.toPrivate()) & 0b1);
494 void SavedFrame::initSource(JSAtom
* source
) {
496 initReservedSlot(JSSLOT_SOURCE
, StringValue(source
));
499 void SavedFrame::initSourceId(uint32_t sourceId
) {
500 initReservedSlot(JSSLOT_SOURCEID
, PrivateUint32Value(sourceId
));
503 void SavedFrame::initLine(uint32_t line
) {
504 initReservedSlot(JSSLOT_LINE
, PrivateUint32Value(line
));
507 void SavedFrame::initColumn(JS::TaggedColumnNumberOneOrigin column
) {
508 if (js::SupportDifferentialTesting()) {
509 column
= JS::TaggedColumnNumberOneOrigin::forDifferentialTesting();
511 initReservedSlot(JSSLOT_COLUMN
, PrivateUint32Value(column
.rawValue()));
514 void SavedFrame::initPrincipalsAndMutedErrors(JSPrincipals
* principals
,
517 JS_HoldPrincipals(principals
);
519 initPrincipalsAlreadyHeldAndMutedErrors(principals
, mutedErrors
);
522 void SavedFrame::initPrincipalsAlreadyHeldAndMutedErrors(
523 JSPrincipals
* principals
, bool mutedErrors
) {
524 MOZ_ASSERT_IF(principals
, principals
->refcount
> 0);
525 uintptr_t ptr
= uintptr_t(principals
) | mutedErrors
;
526 initReservedSlot(JSSLOT_PRINCIPALS
,
527 PrivateValue(reinterpret_cast<void*>(ptr
)));
530 void SavedFrame::initFunctionDisplayName(JSAtom
* maybeName
) {
531 initReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME
,
532 maybeName
? StringValue(maybeName
) : NullValue());
535 void SavedFrame::initAsyncCause(JSAtom
* maybeCause
) {
536 initReservedSlot(JSSLOT_ASYNCCAUSE
,
537 maybeCause
? StringValue(maybeCause
) : NullValue());
540 void SavedFrame::initParent(SavedFrame
* maybeParent
) {
541 initReservedSlot(JSSLOT_PARENT
, ObjectOrNullValue(maybeParent
));
544 void SavedFrame::initFromLookup(JSContext
* cx
, Handle
<Lookup
> lookup
) {
545 // Make sure any atoms used in the lookup are marked in the current zone.
546 // Normally we would try to keep these mark bits up to date around the
547 // points where the context moves between compartments, but Lookups live on
548 // the stack (where the atoms are kept alive regardless) and this is a
549 // more convenient pinchpoint.
550 if (lookup
.source()) {
551 cx
->markAtom(lookup
.source());
553 if (lookup
.functionDisplayName()) {
554 cx
->markAtom(lookup
.functionDisplayName());
556 if (lookup
.asyncCause()) {
557 cx
->markAtom(lookup
.asyncCause());
560 initSource(lookup
.source());
561 initSourceId(lookup
.sourceId());
562 initLine(lookup
.line());
563 initColumn(lookup
.column());
564 initFunctionDisplayName(lookup
.functionDisplayName());
565 initAsyncCause(lookup
.asyncCause());
566 initParent(lookup
.parent());
567 initPrincipalsAndMutedErrors(lookup
.principals(), lookup
.mutedErrors());
571 SavedFrame
* SavedFrame::create(JSContext
* cx
) {
572 Rooted
<GlobalObject
*> global(cx
, cx
->global());
575 // Ensure that we don't try to capture the stack again in the
576 // `SavedStacksMetadataBuilder` for this new SavedFrame object, and
577 // accidentally cause O(n^2) behavior.
578 SavedStacks::AutoReentrancyGuard
guard(cx
->realm()->savedStacks());
580 RootedObject
proto(cx
,
581 GlobalObject::getOrCreateSavedFramePrototype(cx
, global
));
587 return NewTenuredObjectWithGivenProto
<SavedFrame
>(cx
, proto
);
590 bool SavedFrame::isSelfHosted(JSContext
* cx
) {
591 JSAtom
* source
= getSource();
592 return source
== cx
->names().self_hosted_
;
595 bool SavedFrame::isWasm() { return getColumn().isWasmFunctionIndex(); }
597 uint32_t SavedFrame::wasmFuncIndex() {
598 return getColumn().toWasmFunctionIndex().value();
601 uint32_t SavedFrame::wasmBytecodeOffset() {
602 MOZ_ASSERT(isWasm());
607 bool SavedFrame::construct(JSContext
* cx
, unsigned argc
, Value
* vp
) {
608 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_NO_CONSTRUCTOR
,
613 static bool SavedFrameSubsumedByPrincipals(JSContext
* cx
,
614 JSPrincipals
* principals
,
615 Handle
<SavedFrame
*> frame
) {
616 auto subsumes
= cx
->runtime()->securityCallbacks
->subsumes
;
621 MOZ_ASSERT(!ReconstructedSavedFramePrincipals::is(principals
));
623 auto framePrincipals
= frame
->getPrincipals();
625 // Handle SavedFrames that have been reconstructed from stacks in a heap
627 if (framePrincipals
== &ReconstructedSavedFramePrincipals::IsSystem
) {
628 return cx
->runningWithTrustedPrincipals();
630 if (framePrincipals
== &ReconstructedSavedFramePrincipals::IsNotSystem
) {
634 return subsumes(principals
, framePrincipals
);
637 // Return the first SavedFrame in the chain that starts with |frame| whose
638 // for which the given match function returns true. If there is no such frame,
639 // return nullptr. |skippedAsync| is set to true if any of the skipped frames
640 // had the |asyncCause| property set, otherwise it is explicitly set to false.
641 template <typename Matcher
>
642 static SavedFrame
* GetFirstMatchedFrame(JSContext
* cx
, JSPrincipals
* principals
,
644 Handle
<SavedFrame
*> frame
,
645 JS::SavedFrameSelfHosted selfHosted
,
646 bool& skippedAsync
) {
647 skippedAsync
= false;
649 Rooted
<SavedFrame
*> rootedFrame(cx
, frame
);
650 while (rootedFrame
) {
651 if ((selfHosted
== JS::SavedFrameSelfHosted::Include
||
652 !rootedFrame
->isSelfHosted(cx
)) &&
653 matches(cx
, principals
, rootedFrame
)) {
657 if (rootedFrame
->getAsyncCause()) {
661 rootedFrame
= rootedFrame
->getParent();
667 // Return the first SavedFrame in the chain that starts with |frame| whose
668 // principals are subsumed by |principals|, according to |subsumes|. If there is
669 // no such frame, return nullptr. |skippedAsync| is set to true if any of the
670 // skipped frames had the |asyncCause| property set, otherwise it is explicitly
672 static SavedFrame
* GetFirstSubsumedFrame(JSContext
* cx
,
673 JSPrincipals
* principals
,
674 Handle
<SavedFrame
*> frame
,
675 JS::SavedFrameSelfHosted selfHosted
,
676 bool& skippedAsync
) {
677 return GetFirstMatchedFrame(cx
, principals
, SavedFrameSubsumedByPrincipals
,
678 frame
, selfHosted
, skippedAsync
);
681 JS_PUBLIC_API JSObject
* GetFirstSubsumedSavedFrame(
682 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
683 JS::SavedFrameSelfHosted selfHosted
) {
688 auto subsumes
= cx
->runtime()->securityCallbacks
->subsumes
;
693 auto matcher
= [subsumes
](JSContext
* cx
, JSPrincipals
* principals
,
694 Handle
<SavedFrame
*> frame
) -> bool {
695 return subsumes(principals
, frame
->getPrincipals());
699 Rooted
<SavedFrame
*> frame(cx
, &savedFrame
->as
<SavedFrame
>());
700 return GetFirstMatchedFrame(cx
, principals
, matcher
, frame
, selfHosted
,
704 [[nodiscard
]] static bool SavedFrame_checkThis(JSContext
* cx
, CallArgs
& args
,
706 MutableHandleObject frame
) {
707 const Value
& thisValue
= args
.thisv();
709 if (!thisValue
.isObject()) {
710 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
711 JSMSG_OBJECT_REQUIRED
,
712 InformalValueTypeName(thisValue
));
716 if (!thisValue
.toObject().canUnwrapAs
<SavedFrame
>()) {
717 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
718 JSMSG_INCOMPATIBLE_PROTO
, SavedFrame::class_
.name
,
723 // Now set "frame" to the actual object we were invoked in (which may be a
724 // wrapper), not the unwrapped version. Consumers will need to know what
725 // that original object was, and will do principal checks as needed.
726 frame
.set(&thisValue
.toObject());
730 // Get the SavedFrame * from the current this value and handle any errors that
731 // might occur therein.
733 // These parameters must already exist when calling this macro:
737 // - const char* fnName
738 // These parameters will be defined after calling this macro:
740 // - Rooted<SavedFrame*> frame (will be non-null)
741 #define THIS_SAVEDFRAME(cx, argc, vp, fnName, args, frame) \
742 CallArgs args = CallArgsFromVp(argc, vp); \
743 RootedObject frame(cx); \
744 if (!SavedFrame_checkThis(cx, args, fnName, &frame)) return false;
748 js::SavedFrame
* js::UnwrapSavedFrame(JSContext
* cx
, JSPrincipals
* principals
,
750 JS::SavedFrameSelfHosted selfHosted
,
751 bool& skippedAsync
) {
756 Rooted
<SavedFrame
*> frame(cx
, obj
->maybeUnwrapAs
<SavedFrame
>());
761 return GetFirstSubsumedFrame(cx
, principals
, frame
, selfHosted
, skippedAsync
);
766 JS_PUBLIC_API SavedFrameResult
GetSavedFrameSource(
767 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
768 MutableHandleString sourcep
,
769 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
770 js::AssertHeapIsIdle();
772 MOZ_RELEASE_ASSERT(cx
->realm());
776 Rooted
<js::SavedFrame
*> frame(
778 UnwrapSavedFrame(cx
, principals
, savedFrame
, selfHosted
, skippedAsync
));
780 sourcep
.set(cx
->runtime()->emptyString
);
781 return SavedFrameResult::AccessDenied
;
783 sourcep
.set(frame
->getSource());
785 if (sourcep
->isAtom()) {
786 cx
->markAtom(&sourcep
->asAtom());
788 return SavedFrameResult::Ok
;
791 JS_PUBLIC_API SavedFrameResult
GetSavedFrameSourceId(
792 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
794 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
795 js::AssertHeapIsIdle();
797 MOZ_RELEASE_ASSERT(cx
->realm());
800 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
801 selfHosted
, skippedAsync
));
804 return SavedFrameResult::AccessDenied
;
806 *sourceIdp
= frame
->getSourceId();
807 return SavedFrameResult::Ok
;
810 JS_PUBLIC_API SavedFrameResult
GetSavedFrameLine(
811 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
813 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
814 js::AssertHeapIsIdle();
816 MOZ_RELEASE_ASSERT(cx
->realm());
820 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
821 selfHosted
, skippedAsync
));
824 return SavedFrameResult::AccessDenied
;
826 *linep
= frame
->getLine();
827 return SavedFrameResult::Ok
;
830 JS_PUBLIC_API SavedFrameResult
GetSavedFrameColumn(
831 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
832 JS::TaggedColumnNumberOneOrigin
* columnp
,
833 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
834 js::AssertHeapIsIdle();
836 MOZ_RELEASE_ASSERT(cx
->realm());
840 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
841 selfHosted
, skippedAsync
));
843 *columnp
= JS::TaggedColumnNumberOneOrigin();
844 return SavedFrameResult::AccessDenied
;
846 *columnp
= frame
->getColumn();
847 return SavedFrameResult::Ok
;
850 JS_PUBLIC_API SavedFrameResult
GetSavedFrameFunctionDisplayName(
851 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
852 MutableHandleString namep
,
853 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
854 js::AssertHeapIsIdle();
856 MOZ_RELEASE_ASSERT(cx
->realm());
860 Rooted
<js::SavedFrame
*> frame(
862 UnwrapSavedFrame(cx
, principals
, savedFrame
, selfHosted
, skippedAsync
));
865 return SavedFrameResult::AccessDenied
;
867 namep
.set(frame
->getFunctionDisplayName());
869 if (namep
&& namep
->isAtom()) {
870 cx
->markAtom(&namep
->asAtom());
872 return SavedFrameResult::Ok
;
875 JS_PUBLIC_API SavedFrameResult
GetSavedFrameAsyncCause(
876 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
877 MutableHandleString asyncCausep
,
878 SavedFrameSelfHosted unused_
/* = SavedFrameSelfHosted::Include */) {
879 js::AssertHeapIsIdle();
881 MOZ_RELEASE_ASSERT(cx
->realm());
885 // This function is always called with self-hosted frames excluded by
886 // GetValueIfNotCached in dom/bindings/Exceptions.cpp. However, we want
887 // to include them because our Promise implementation causes us to have
888 // the async cause on a self-hosted frame. So we just ignore the
889 // parameter and always include self-hosted frames.
890 Rooted
<js::SavedFrame
*> frame(
891 cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
892 SavedFrameSelfHosted::Include
, skippedAsync
));
894 asyncCausep
.set(nullptr);
895 return SavedFrameResult::AccessDenied
;
897 asyncCausep
.set(frame
->getAsyncCause());
898 if (!asyncCausep
&& skippedAsync
) {
899 asyncCausep
.set(cx
->names().Async
);
902 if (asyncCausep
&& asyncCausep
->isAtom()) {
903 cx
->markAtom(&asyncCausep
->asAtom());
905 return SavedFrameResult::Ok
;
908 JS_PUBLIC_API SavedFrameResult
GetSavedFrameAsyncParent(
909 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
910 MutableHandleObject asyncParentp
,
911 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
912 js::AssertHeapIsIdle();
914 MOZ_RELEASE_ASSERT(cx
->realm());
917 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
918 selfHosted
, skippedAsync
));
920 asyncParentp
.set(nullptr);
921 return SavedFrameResult::AccessDenied
;
923 Rooted
<js::SavedFrame
*> parent(cx
, frame
->getParent());
925 // The current value of |skippedAsync| is not interesting, because we are
926 // interested in whether we would cross any async parents to get from here
927 // to the first subsumed parent frame instead.
928 Rooted
<js::SavedFrame
*> subsumedParent(
930 GetFirstSubsumedFrame(cx
, principals
, parent
, selfHosted
, skippedAsync
));
932 // Even if |parent| is not subsumed, we still want to return a pointer to it
933 // rather than |subsumedParent| so it can pick up any |asyncCause| from the
934 // inaccessible part of the chain.
935 if (subsumedParent
&& (subsumedParent
->getAsyncCause() || skippedAsync
)) {
936 asyncParentp
.set(parent
);
938 asyncParentp
.set(nullptr);
940 return SavedFrameResult::Ok
;
943 JS_PUBLIC_API SavedFrameResult
GetSavedFrameParent(
944 JSContext
* cx
, JSPrincipals
* principals
, HandleObject savedFrame
,
945 MutableHandleObject parentp
,
946 SavedFrameSelfHosted selfHosted
/* = SavedFrameSelfHosted::Include */) {
947 js::AssertHeapIsIdle();
949 MOZ_RELEASE_ASSERT(cx
->realm());
952 Rooted
<js::SavedFrame
*> frame(cx
, UnwrapSavedFrame(cx
, principals
, savedFrame
,
953 selfHosted
, skippedAsync
));
955 parentp
.set(nullptr);
956 return SavedFrameResult::AccessDenied
;
958 Rooted
<js::SavedFrame
*> parent(cx
, frame
->getParent());
960 // The current value of |skippedAsync| is not interesting, because we are
961 // interested in whether we would cross any async parents to get from here
962 // to the first subsumed parent frame instead.
963 Rooted
<js::SavedFrame
*> subsumedParent(
965 GetFirstSubsumedFrame(cx
, principals
, parent
, selfHosted
, skippedAsync
));
967 // Even if |parent| is not subsumed, we still want to return a pointer to it
968 // rather than |subsumedParent| so it can pick up any |asyncCause| from the
969 // inaccessible part of the chain.
970 if (subsumedParent
&& !(subsumedParent
->getAsyncCause() || skippedAsync
)) {
973 parentp
.set(nullptr);
975 return SavedFrameResult::Ok
;
978 static bool FormatStackFrameLine(js::StringBuffer
& sb
,
979 JS::Handle
<js::SavedFrame
*> frame
) {
980 if (frame
->isWasm()) {
981 // See comment in WasmFrameIter::computeLine().
982 return sb
.append("wasm-function[") &&
983 NumberValueToStringBuffer(NumberValue(frame
->wasmFuncIndex()), sb
) &&
987 return NumberValueToStringBuffer(NumberValue(frame
->getLine()), sb
);
990 static bool FormatStackFrameColumn(js::StringBuffer
& sb
,
991 JS::Handle
<js::SavedFrame
*> frame
) {
992 if (frame
->isWasm()) {
993 // See comment in WasmFrameIter::computeLine().
994 js::Int32ToCStringBuf cbuf
;
997 Uint32ToHexCString(&cbuf
, frame
->wasmBytecodeOffset(), &cstrlen
);
1000 return sb
.append("0x") && sb
.append(cstr
, cstrlen
);
1003 return NumberValueToStringBuffer(
1004 NumberValue(frame
->getColumn().oneOriginValue()), sb
);
1007 static bool FormatSpiderMonkeyStackFrame(JSContext
* cx
, js::StringBuffer
& sb
,
1008 JS::Handle
<js::SavedFrame
*> frame
,
1009 size_t indent
, bool skippedAsync
) {
1010 RootedString
asyncCause(cx
, frame
->getAsyncCause());
1011 if (!asyncCause
&& skippedAsync
) {
1012 asyncCause
.set(cx
->names().Async
);
1015 Rooted
<JSAtom
*> name(cx
, frame
->getFunctionDisplayName());
1016 return (!indent
|| sb
.appendN(' ', indent
)) &&
1017 (!asyncCause
|| (sb
.append(asyncCause
) && sb
.append('*'))) &&
1018 (!name
|| sb
.append(name
)) && sb
.append('@') &&
1019 sb
.append(frame
->getSource()) && sb
.append(':') &&
1020 FormatStackFrameLine(sb
, frame
) && sb
.append(':') &&
1021 FormatStackFrameColumn(sb
, frame
) && sb
.append('\n');
1024 static bool FormatV8StackFrame(JSContext
* cx
, js::StringBuffer
& sb
,
1025 JS::Handle
<js::SavedFrame
*> frame
, size_t indent
,
1027 Rooted
<JSAtom
*> name(cx
, frame
->getFunctionDisplayName());
1028 return sb
.appendN(' ', indent
+ 4) && sb
.append('a') && sb
.append('t') &&
1030 (!name
|| (sb
.append(name
) && sb
.append(' ') && sb
.append('('))) &&
1031 sb
.append(frame
->getSource()) && sb
.append(':') &&
1032 FormatStackFrameLine(sb
, frame
) && sb
.append(':') &&
1033 FormatStackFrameColumn(sb
, frame
) && (!name
|| sb
.append(')')) &&
1034 (lastFrame
|| sb
.append('\n'));
1037 JS_PUBLIC_API
bool BuildStackString(JSContext
* cx
, JSPrincipals
* principals
,
1039 MutableHandleString stringp
, size_t indent
,
1040 js::StackFormat format
) {
1041 js::AssertHeapIsIdle();
1043 MOZ_RELEASE_ASSERT(cx
->realm());
1045 js::JSStringBuilder
sb(cx
);
1047 if (format
== js::StackFormat::Default
) {
1048 format
= cx
->runtime()->stackFormat();
1050 MOZ_ASSERT(format
!= js::StackFormat::Default
);
1052 // Enter a new block to constrain the scope of possibly entering the stack's
1053 // realm. This ensures that when we finish the StringBuffer, we are back in
1054 // the cx's original compartment, and fulfill our contract with callers to
1055 // place the output string in the cx's current realm.
1058 Rooted
<js::SavedFrame
*> frame(
1059 cx
, UnwrapSavedFrame(cx
, principals
, stack
,
1060 SavedFrameSelfHosted::Exclude
, skippedAsync
));
1062 stringp
.set(cx
->runtime()->emptyString
);
1066 Rooted
<js::SavedFrame
*> parent(cx
);
1068 MOZ_ASSERT(SavedFrameSubsumedByPrincipals(cx
, principals
, frame
));
1069 MOZ_ASSERT(!frame
->isSelfHosted(cx
));
1071 parent
= frame
->getParent();
1072 bool skippedNextAsync
;
1073 Rooted
<js::SavedFrame
*> nextFrame(
1074 cx
, js::GetFirstSubsumedFrame(cx
, principals
, parent
,
1075 SavedFrameSelfHosted::Exclude
,
1079 case js::StackFormat::SpiderMonkey
:
1080 if (!FormatSpiderMonkeyStackFrame(cx
, sb
, frame
, indent
,
1085 case js::StackFormat::V8
:
1086 if (!FormatV8StackFrame(cx
, sb
, frame
, indent
, !nextFrame
)) {
1090 case js::StackFormat::Default
:
1091 MOZ_CRASH("Unexpected value");
1096 skippedAsync
= skippedNextAsync
;
1100 JSString
* str
= sb
.finishString();
1109 JS_PUBLIC_API
bool IsMaybeWrappedSavedFrame(JSObject
* obj
) {
1111 return obj
->canUnwrapAs
<js::SavedFrame
>();
1114 JS_PUBLIC_API
bool IsUnwrappedSavedFrame(JSObject
* obj
) {
1116 return obj
->is
<js::SavedFrame
>();
1119 static bool AssignProperty(JSContext
* cx
, HandleObject dst
, HandleObject src
,
1120 const char* property
) {
1122 return JS_GetProperty(cx
, src
, property
, &v
) &&
1123 JS_DefineProperty(cx
, dst
, property
, v
, JSPROP_ENUMERATE
);
1126 JS_PUBLIC_API JSObject
* ConvertSavedFrameToPlainObject(
1127 JSContext
* cx
, HandleObject savedFrameArg
,
1128 SavedFrameSelfHosted selfHosted
) {
1129 MOZ_ASSERT(savedFrameArg
);
1131 RootedObject
savedFrame(cx
, savedFrameArg
);
1132 RootedObject
baseConverted(cx
), lastConverted(cx
);
1135 baseConverted
= lastConverted
= JS_NewObject(cx
, nullptr);
1136 if (!baseConverted
) {
1142 if (!AssignProperty(cx
, lastConverted
, savedFrame
, "source") ||
1143 !AssignProperty(cx
, lastConverted
, savedFrame
, "sourceId") ||
1144 !AssignProperty(cx
, lastConverted
, savedFrame
, "line") ||
1145 !AssignProperty(cx
, lastConverted
, savedFrame
, "column") ||
1146 !AssignProperty(cx
, lastConverted
, savedFrame
, "functionDisplayName") ||
1147 !AssignProperty(cx
, lastConverted
, savedFrame
, "asyncCause")) {
1151 const char* parentProperties
[] = {"parent", "asyncParent"};
1152 foundParent
= false;
1153 for (const char* prop
: parentProperties
) {
1154 if (!JS_GetProperty(cx
, savedFrame
, prop
, &v
)) {
1158 RootedObject
nextConverted(cx
, JS_NewObject(cx
, nullptr));
1159 if (!nextConverted
||
1160 !JS_DefineProperty(cx
, lastConverted
, prop
, nextConverted
,
1161 JSPROP_ENUMERATE
)) {
1164 lastConverted
= nextConverted
;
1165 savedFrame
= &v
.toObject();
1170 } while (foundParent
);
1172 return baseConverted
;
1175 } /* namespace JS */
1180 bool SavedFrame::sourceProperty(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1181 THIS_SAVEDFRAME(cx
, argc
, vp
, "(get source)", args
, frame
);
1182 JSPrincipals
* principals
= cx
->realm()->principals();
1183 RootedString
source(cx
);
1184 if (JS::GetSavedFrameSource(cx
, principals
, frame
, &source
) ==
1185 JS::SavedFrameResult::Ok
) {
1186 if (!cx
->compartment()->wrap(cx
, &source
)) {
1189 args
.rval().setString(source
);
1191 args
.rval().setNull();
1197 bool SavedFrame::sourceIdProperty(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1198 THIS_SAVEDFRAME(cx
, argc
, vp
, "(get sourceId)", args
, frame
);
1199 JSPrincipals
* principals
= cx
->realm()->principals();
1201 if (JS::GetSavedFrameSourceId(cx
, principals
, frame
, &sourceId
) ==
1202 JS::SavedFrameResult::Ok
) {
1203 args
.rval().setNumber(sourceId
);
1205 args
.rval().setNull();
1211 bool SavedFrame::lineProperty(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1212 THIS_SAVEDFRAME(cx
, argc
, vp
, "(get line)", args
, frame
);
1213 JSPrincipals
* principals
= cx
->realm()->principals();
1215 if (JS::GetSavedFrameLine(cx
, principals
, frame
, &line
) ==
1216 JS::SavedFrameResult::Ok
) {
1217 args
.rval().setNumber(line
);
1219 args
.rval().setNull();
1225 bool SavedFrame::columnProperty(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1226 THIS_SAVEDFRAME(cx
, argc
, vp
, "(get column)", args
, frame
);
1227 JSPrincipals
* principals
= cx
->realm()->principals();
1228 JS::TaggedColumnNumberOneOrigin column
;
1229 if (JS::GetSavedFrameColumn(cx
, principals
, frame
, &column
) ==
1230 JS::SavedFrameResult::Ok
) {
1231 args
.rval().setNumber(column
.oneOriginValue());
1233 args
.rval().setNull();
1239 bool SavedFrame::functionDisplayNameProperty(JSContext
* cx
, unsigned argc
,
1241 THIS_SAVEDFRAME(cx
, argc
, vp
, "(get functionDisplayName)", args
, frame
);
1242 JSPrincipals
* principals
= cx
->realm()->principals();
1243 RootedString
name(cx
);
1244 JS::SavedFrameResult result
=
1245 JS::GetSavedFrameFunctionDisplayName(cx
, principals
, frame
, &name
);
1246 if (result
== JS::SavedFrameResult::Ok
&& name
) {
1247 if (!cx
->compartment()->wrap(cx
, &name
)) {
1250 args
.rval().setString(name
);
1252 args
.rval().setNull();
1258 bool SavedFrame::asyncCauseProperty(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1259 THIS_SAVEDFRAME(cx
, argc
, vp
, "(get asyncCause)", args
, frame
);
1260 JSPrincipals
* principals
= cx
->realm()->principals();
1261 RootedString
asyncCause(cx
);
1262 JS::SavedFrameResult result
=
1263 JS::GetSavedFrameAsyncCause(cx
, principals
, frame
, &asyncCause
);
1264 if (result
== JS::SavedFrameResult::Ok
&& asyncCause
) {
1265 if (!cx
->compartment()->wrap(cx
, &asyncCause
)) {
1268 args
.rval().setString(asyncCause
);
1270 args
.rval().setNull();
1276 bool SavedFrame::asyncParentProperty(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1277 THIS_SAVEDFRAME(cx
, argc
, vp
, "(get asyncParent)", args
, frame
);
1278 JSPrincipals
* principals
= cx
->realm()->principals();
1279 RootedObject
asyncParent(cx
);
1280 (void)JS::GetSavedFrameAsyncParent(cx
, principals
, frame
, &asyncParent
);
1281 if (!cx
->compartment()->wrap(cx
, &asyncParent
)) {
1284 args
.rval().setObjectOrNull(asyncParent
);
1289 bool SavedFrame::parentProperty(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1290 THIS_SAVEDFRAME(cx
, argc
, vp
, "(get parent)", args
, frame
);
1291 JSPrincipals
* principals
= cx
->realm()->principals();
1292 RootedObject
parent(cx
);
1293 (void)JS::GetSavedFrameParent(cx
, principals
, frame
, &parent
);
1294 if (!cx
->compartment()->wrap(cx
, &parent
)) {
1297 args
.rval().setObjectOrNull(parent
);
1302 bool SavedFrame::toStringMethod(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1303 THIS_SAVEDFRAME(cx
, argc
, vp
, "toString", args
, frame
);
1304 JSPrincipals
* principals
= cx
->realm()->principals();
1305 RootedString
string(cx
);
1306 if (!JS::BuildStackString(cx
, principals
, frame
, &string
)) {
1309 args
.rval().setString(string
);
1313 bool SavedStacks::saveCurrentStack(
1314 JSContext
* cx
, MutableHandle
<SavedFrame
*> frame
,
1315 JS::StackCapture
&& capture
/* = JS::StackCapture(JS::AllFrames()) */) {
1316 MOZ_RELEASE_ASSERT(cx
->realm());
1317 MOZ_DIAGNOSTIC_ASSERT(&cx
->realm()->savedStacks() == this);
1319 if (creatingSavedFrame
|| cx
->isExceptionPending() || !cx
->global() ||
1320 !cx
->global()->isStandardClassResolved(JSProto_Object
)) {
1325 AutoGeckoProfilerEntry
labelFrame(cx
, "js::SavedStacks::saveCurrentStack");
1326 return insertFrames(cx
, frame
, std::move(capture
));
1329 bool SavedStacks::copyAsyncStack(JSContext
* cx
, HandleObject asyncStack
,
1330 HandleString asyncCause
,
1331 MutableHandle
<SavedFrame
*> adoptedStack
,
1332 const Maybe
<size_t>& maxFrameCount
) {
1333 MOZ_RELEASE_ASSERT(cx
->realm());
1334 MOZ_DIAGNOSTIC_ASSERT(&cx
->realm()->savedStacks() == this);
1336 Rooted
<JSAtom
*> asyncCauseAtom(cx
, AtomizeString(cx
, asyncCause
));
1337 if (!asyncCauseAtom
) {
1341 Rooted
<SavedFrame
*> asyncStackObj(
1342 cx
, asyncStack
->maybeUnwrapAs
<js::SavedFrame
>());
1343 MOZ_RELEASE_ASSERT(asyncStackObj
);
1344 adoptedStack
.set(asyncStackObj
);
1346 if (!adoptAsyncStack(cx
, adoptedStack
, asyncCauseAtom
, maxFrameCount
)) {
1353 void SavedStacks::traceWeak(JSTracer
* trc
) {
1354 frames
.traceWeak(trc
);
1355 pcLocationMap
.traceWeak(trc
);
1358 void SavedStacks::trace(JSTracer
* trc
) { pcLocationMap
.trace(trc
); }
1360 uint32_t SavedStacks::count() { return frames
.count(); }
1362 void SavedStacks::clear() { frames
.clear(); }
1364 size_t SavedStacks::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
) {
1365 return frames
.shallowSizeOfExcludingThis(mallocSizeOf
) +
1366 pcLocationMap
.shallowSizeOfExcludingThis(mallocSizeOf
);
1369 // Given that we have captured a stack frame with the given principals and
1370 // source, return true if the requested `StackCapture` has been satisfied and
1371 // stack walking can halt. Return false otherwise (and stack walking and frame
1372 // capturing should continue).
1373 static inline bool captureIsSatisfied(JSContext
* cx
, JSPrincipals
* principals
,
1374 const JSAtom
* source
,
1375 JS::StackCapture
& capture
) {
1378 JSPrincipals
* framePrincipals_
;
1379 const JSAtom
* frameSource_
;
1382 Matcher(JSContext
* cx
, JSPrincipals
* principals
, const JSAtom
* source
)
1383 : cx_(cx
), framePrincipals_(principals
), frameSource_(source
) {}
1385 bool operator()(JS::FirstSubsumedFrame
& target
) {
1386 auto subsumes
= cx_
->runtime()->securityCallbacks
->subsumes
;
1387 return (!subsumes
|| subsumes(target
.principals
, framePrincipals_
)) &&
1388 (!target
.ignoreSelfHosted
||
1389 frameSource_
!= cx_
->names().self_hosted_
);
1392 bool operator()(JS::MaxFrames
& target
) { return target
.maxFrames
== 1; }
1394 bool operator()(JS::AllFrames
&) { return false; }
1397 Matcher
m(cx
, principals
, source
);
1398 return capture
.match(m
);
1401 bool SavedStacks::insertFrames(JSContext
* cx
, MutableHandle
<SavedFrame
*> frame
,
1402 JS::StackCapture
&& capture
) {
1403 // In order to look up a cached SavedFrame object, we need to have its parent
1404 // SavedFrame, which means we need to walk the stack from oldest frame to
1405 // youngest. However, FrameIter walks the stack from youngest frame to
1406 // oldest. The solution is to append stack frames to a vector as we walk the
1407 // stack with FrameIter, and then do a second pass through that vector in
1408 // reverse order after the traversal has completed and get or create the
1409 // SavedFrame objects at that time.
1411 // To avoid making many copies of FrameIter (whose copy constructor is
1412 // relatively slow), we use a vector of `SavedFrame::Lookup` objects, which
1413 // only contain the FrameIter data we need. The `SavedFrame::Lookup`
1414 // objects are partially initialized with everything except their parent
1415 // pointers on the first pass, and then we fill in the parent pointers as we
1416 // return in the second pass.
1418 // Accumulate the vector of Lookup objects here, youngest to oldest.
1419 Rooted
<js::GCLookupVector
> stackChain(cx
, js::GCLookupVector(cx
));
1421 // If we find a cached saved frame, then that supplies the parent of the
1422 // frames we have placed in stackChain. If we walk the stack all the way
1423 // to the end, this remains null.
1424 Rooted
<SavedFrame
*> cachedParentFrame(cx
, nullptr);
1426 // Choose the right frame iteration strategy to accomodate both
1427 // evalInFramePrev links and the LiveSavedFrameCache. For background, see
1428 // the LiveSavedFrameCache comments in Stack.h.
1430 // If we're using the LiveSavedFrameCache, then don't handle evalInFramePrev
1431 // links by skipping over the frames altogether; that violates the cache's
1432 // assumptions. Instead, traverse the entire stack, but choose each
1433 // SavedFrame's parent as directed by the evalInFramePrev link, if any.
1435 // If we're not using the LiveSavedFrameCache, it's hard to recover the
1436 // frame to which the evalInFramePrev link refers, so we just let FrameIter
1437 // skip those frames. Then each SavedFrame's parent is simply the frame that
1438 // follows it in the stackChain vector, even when it has an evalInFramePrev
1440 FrameIter
iter(cx
, capture
.is
<JS::AllFrames
>()
1441 ? FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK
1442 : FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK
);
1444 // Once we've seen one frame with its hasCachedSavedFrame bit set, all its
1445 // parents (that can be cached) ought to have it set too.
1446 DebugOnly
<bool> seenCached
= false;
1448 // If we are using evalInFramePrev links to adjust the parents of debugger
1449 // eval frames, we have to ensure the target frame is cached in the current
1450 // realm. (This might not happen by default if the target frame is
1451 // rematerialized, or if there is an async parent between the debugger eval
1452 // frame and the target frame.) To accomplish this, we keep track of eval
1453 // targets and ensure that we don't stop before they have all been reached.
1454 Vector
<AbstractFramePtr
, 4, TempAllocPolicy
> unreachedEvalTargets(cx
);
1456 while (!iter
.done()) {
1457 Activation
& activation
= *iter
.activation();
1458 Maybe
<LiveSavedFrameCache::FramePtr
> framePtr
=
1459 LiveSavedFrameCache::FramePtr::create(iter
);
1461 if (capture
.is
<JS::AllFrames
>() && iter
.hasUsableAbstractFramePtr()) {
1462 unreachedEvalTargets
.eraseIfEqual(iter
.abstractFramePtr());
1466 // In general, when we reach a frame with its hasCachedSavedFrame bit set,
1467 // all its parents will have the bit set as well. See the
1468 // LiveSavedFrameCache comment in Activation.h for more details. There are
1469 // a few exceptions:
1470 // - Rematerialized frames are always created with the bit clear.
1471 // - Captures using FirstSubsumedFrame ignore async parents and walk the
1472 // real stack. Because we're using different rules for walking the
1473 // stack, we can reach frames that weren't cached in a previous
1474 // AllFrames traversal.
1475 // - Similarly, if we've seen an evalInFrame frame but haven't reached
1476 // its target yet, we don't stop when we reach an async parent, so we
1477 // can reach frames that weren't cached in a previous traversal that
1478 // didn't include the evalInFrame.
1479 DebugOnly
<bool> hasGoodExcuse
= framePtr
->isRematerializedFrame() ||
1480 capture
.is
<JS::FirstSubsumedFrame
>() ||
1481 !unreachedEvalTargets
.empty();
1482 MOZ_ASSERT_IF(seenCached
,
1483 framePtr
->hasCachedSavedFrame() || hasGoodExcuse
);
1484 seenCached
|= framePtr
->hasCachedSavedFrame();
1486 if (capture
.is
<JS::AllFrames
>() && framePtr
->isInterpreterFrame() &&
1487 framePtr
->asInterpreterFrame().isDebuggerEvalFrame()) {
1488 AbstractFramePtr target
=
1489 framePtr
->asInterpreterFrame().evalInFramePrev();
1490 if (!unreachedEvalTargets
.append(target
)) {
1496 if (capture
.is
<JS::AllFrames
>() && framePtr
&&
1497 framePtr
->hasCachedSavedFrame()) {
1498 auto* cache
= activation
.getLiveSavedFrameCache(cx
);
1502 cache
->find(cx
, *framePtr
, iter
.pc(), &cachedParentFrame
);
1504 // Even though iter.hasCachedSavedFrame() was true, we may still get a
1505 // cache miss, if the frame's pc doesn't match the cache entry's, or if
1506 // the cache was emptied due to a realm mismatch. If we got a cache hit,
1507 // and we do not have to keep looking for unreached eval target frames,
1508 // we can stop traversing the stack and start building the chain.
1509 if (cachedParentFrame
&& unreachedEvalTargets
.empty()) {
1513 // This frame doesn't have a cache entry, despite its hasCachedSavedFrame
1514 // flag being set. If this was due to a pc mismatch, we can clear the flag
1515 // here and set things right. If the cache was emptied due to a realm
1516 // mismatch, we should clear all the frames' flags as we walk to the
1517 // bottom of the stack, so that they are all clear before we start pushing
1519 framePtr
->clearHasCachedSavedFrame();
1522 // We'll be pushing this frame onto stackChain. Gather the information
1523 // needed to construct the SavedFrame::Lookup.
1524 Rooted
<LocationValue
> location(cx
);
1526 AutoRealmUnchecked
ar(cx
, iter
.realm());
1527 if (!cx
->realm()->savedStacks().getLocation(cx
, iter
, &location
)) {
1532 Rooted
<JSAtom
*> displayAtom(cx
, iter
.maybeFunctionDisplayAtom());
1534 auto principals
= iter
.realm()->principals();
1535 MOZ_ASSERT_IF(framePtr
&& !iter
.isWasm(), iter
.pc());
1537 if (!stackChain
.emplaceBack(location
.source(), location
.sourceId(),
1538 location
.line(), location
.column(), displayAtom
,
1539 nullptr, // asyncCause
1540 nullptr, // parent (not known yet)
1541 principals
, iter
.mutedErrors(), framePtr
,
1542 iter
.pc(), &activation
)) {
1546 if (captureIsSatisfied(cx
, principals
, location
.source(), capture
)) {
1551 framePtr
= LiveSavedFrameCache::FramePtr::create(iter
);
1553 if (iter
.activation() != &activation
&& capture
.is
<JS::AllFrames
>()) {
1554 // If there were no cache hits in the entire activation, clear its
1555 // cache so we'll be able to push new ones when we build the
1556 // SavedFrame chain.
1557 activation
.clearLiveSavedFrameCache();
1560 // If we have crossed into a new activation, check whether the prior
1561 // activation had an async parent set.
1563 // If the async call was explicit (async function resumptions, most
1564 // testing facilities), then the async parent stack has priority over
1565 // any actual frames still on the JavaScript stack. If the async call
1566 // was implicit (DOM CallbackObject::CallSetup calls), then the async
1567 // parent stack is used only if there were no other frames on the
1570 // Captures using FirstSubsumedFrame expect us to ignore async parents.
1571 if (iter
.activation() != &activation
&& activation
.asyncStack() &&
1572 (activation
.asyncCallIsExplicit() || iter
.done()) &&
1573 !capture
.is
<JS::FirstSubsumedFrame
>()) {
1574 // Atomize the async cause string. There should only be a few
1575 // different strings used.
1576 const char* cause
= activation
.asyncCause();
1577 Rooted
<JSAtom
*> causeAtom(cx
, AtomizeUTF8Chars(cx
, cause
, strlen(cause
)));
1582 // Translate our capture into a frame count limit for
1583 // adoptAsyncStack, which will impose further limits.
1584 Maybe
<size_t> maxFrames
=
1585 !capture
.is
<JS::MaxFrames
>() ? Nothing()
1586 : capture
.as
<JS::MaxFrames
>().maxFrames
== 0
1588 : Some(capture
.as
<JS::MaxFrames
>().maxFrames
);
1590 // Clip the stack if needed, attach the async cause string to the
1591 // top frame, and copy it into our compartment if necessary.
1592 Rooted
<SavedFrame
*> asyncParent(cx
, activation
.asyncStack());
1593 if (!adoptAsyncStack(cx
, &asyncParent
, causeAtom
, maxFrames
)) {
1596 stackChain
[stackChain
.length() - 1].setParent(asyncParent
);
1597 if (!capture
.is
<JS::AllFrames
>() || unreachedEvalTargets
.empty()) {
1598 // In the case of a JS::AllFrames capture, we will be populating the
1599 // LiveSavedFrameCache in the second loop. In the case where there is
1600 // a debugger eval frame on the stack, the second loop will use
1601 // checkForEvalInFramePrev to skip from the eval frame to the "prev"
1602 // frame and assert that when this happens, the "prev"
1603 // frame is in the cache. In cases where there is an async stack
1604 // activation between the debugger eval frame and the "prev" frame,
1605 // breaking here would not populate the "prev" cache entry, causing
1606 // checkForEvalInFramePrev to fail.
1611 if (capture
.is
<JS::MaxFrames
>()) {
1612 capture
.as
<JS::MaxFrames
>().maxFrames
--;
1616 // Iterate through |stackChain| in reverse order and get or create the
1617 // actual SavedFrame instances.
1618 frame
.set(cachedParentFrame
);
1619 for (size_t i
= stackChain
.length(); i
!= 0; i
--) {
1620 MutableHandle
<SavedFrame::Lookup
> lookup
= stackChain
[i
- 1];
1621 if (!lookup
.parent()) {
1622 // The frame may already have an async parent frame set explicitly
1623 // on its activation.
1624 lookup
.setParent(frame
);
1627 // If necessary, adjust the parent of a debugger eval frame to point to
1628 // the frame in whose scope the eval occurs - if we're using
1629 // LiveSavedFrameCache. Otherwise, we simply ask the FrameIter to follow
1630 // evalInFramePrev links, so that the parent is always the last frame we
1632 if (capture
.is
<JS::AllFrames
>() && lookup
.framePtr()) {
1633 if (!checkForEvalInFramePrev(cx
, lookup
)) {
1638 frame
.set(getOrCreateSavedFrame(cx
, lookup
));
1643 if (capture
.is
<JS::AllFrames
>() && lookup
.framePtr()) {
1644 auto* cache
= lookup
.activation()->getLiveSavedFrameCache(cx
);
1646 !cache
->insert(cx
, *lookup
.framePtr(), lookup
.pc(), frame
)) {
1655 bool SavedStacks::adoptAsyncStack(JSContext
* cx
,
1656 MutableHandle
<SavedFrame
*> asyncStack
,
1657 Handle
<JSAtom
*> asyncCause
,
1658 const Maybe
<size_t>& maxFrameCount
) {
1659 MOZ_ASSERT(asyncStack
);
1660 MOZ_ASSERT(asyncCause
);
1662 // If maxFrameCount is Nothing, the caller asked for an unlimited number of
1663 // stack frames, but async stacks are not limited by the available stack
1664 // memory, so we need to set an arbitrary limit when collecting them. We
1665 // still don't enforce an upper limit if the caller requested more frames.
1666 size_t maxFrames
= maxFrameCount
.valueOr(ASYNC_STACK_MAX_FRAME_COUNT
);
1668 // Turn the chain of frames starting with asyncStack into a vector of Lookup
1669 // objects in |stackChain|, youngest to oldest.
1670 Rooted
<js::GCLookupVector
> stackChain(cx
, js::GCLookupVector(cx
));
1671 SavedFrame
* currentSavedFrame
= asyncStack
;
1672 while (currentSavedFrame
&& stackChain
.length() < maxFrames
) {
1673 if (!stackChain
.emplaceBack(*currentSavedFrame
)) {
1674 ReportOutOfMemory(cx
);
1678 currentSavedFrame
= currentSavedFrame
->getParent();
1681 // Attach the asyncCause to the youngest frame.
1682 stackChain
[0].setAsyncCause(asyncCause
);
1684 // If we walked the entire stack, and it's in cx's realm, we don't
1685 // need to rebuild the full chain again using the lookup objects - we can
1686 // just use the existing chain. Only the asyncCause on the youngest frame
1687 // needs to be changed.
1688 if (currentSavedFrame
== nullptr && asyncStack
->realm() == cx
->realm()) {
1689 MutableHandle
<SavedFrame::Lookup
> lookup
= stackChain
[0];
1690 lookup
.setParent(asyncStack
->getParent());
1691 asyncStack
.set(getOrCreateSavedFrame(cx
, lookup
));
1692 return !!asyncStack
;
1695 // If we captured the maximum number of frames and the caller requested no
1696 // specific limit, we only return half of them. This means that if we do
1697 // many subsequent captures with the same async stack, it's likely we can
1698 // use the optimization above.
1699 if (maxFrameCount
.isNothing() && currentSavedFrame
) {
1700 stackChain
.shrinkBy(ASYNC_STACK_MAX_FRAME_COUNT
/ 2);
1703 // Iterate through |stackChain| in reverse order and get or create the
1704 // actual SavedFrame instances.
1705 asyncStack
.set(nullptr);
1706 while (!stackChain
.empty()) {
1707 Rooted
<SavedFrame::Lookup
> lookup(cx
, stackChain
.back());
1708 lookup
.setParent(asyncStack
);
1709 asyncStack
.set(getOrCreateSavedFrame(cx
, lookup
));
1713 stackChain
.popBack();
1719 // Given a |lookup| for which we're about to construct a SavedFrame, if it
1720 // refers to a Debugger eval frame, adjust |lookup|'s parent to be the frame's
1721 // evalInFramePrev target.
1723 // Debugger eval frames run code in the scope of some random older frame on the
1724 // stack (the 'target' frame). It is our custom to report the target as the
1725 // immediate parent of the eval frame. The LiveSavedFrameCache requires us not
1726 // to skip frames, so instead we walk the entire stack, and just give Debugger
1727 // eval frames the right parents as we encounter them.
1729 // Call this function only if we are using the LiveSavedFrameCache; otherwise,
1730 // FrameIter has already taken care of getting us the right parent.
1731 bool SavedStacks::checkForEvalInFramePrev(
1732 JSContext
* cx
, MutableHandle
<SavedFrame::Lookup
> lookup
) {
1733 MOZ_ASSERT(lookup
.framePtr());
1734 if (!lookup
.framePtr()->isInterpreterFrame()) {
1738 InterpreterFrame
& interpreterFrame
= lookup
.framePtr()->asInterpreterFrame();
1739 if (!interpreterFrame
.isDebuggerEvalFrame()) {
1743 FrameIter
iter(cx
, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK
);
1744 while (!iter
.done() &&
1745 (!iter
.hasUsableAbstractFramePtr() ||
1746 iter
.abstractFramePtr() != interpreterFrame
.evalInFramePrev())) {
1750 Maybe
<LiveSavedFrameCache::FramePtr
> maybeTarget
=
1751 LiveSavedFrameCache::FramePtr::create(iter
);
1752 MOZ_ASSERT(maybeTarget
);
1754 LiveSavedFrameCache::FramePtr target
= *maybeTarget
;
1756 // If we're caching the frame to which |lookup| refers, then we should
1757 // definitely have the target frame in the cache as well.
1758 MOZ_ASSERT(target
.hasCachedSavedFrame());
1760 // Search the chain of activations for a LiveSavedFrameCache that has an
1761 // entry for target.
1762 Rooted
<SavedFrame
*> saved(cx
, nullptr);
1763 for (Activation
* act
= lookup
.activation(); act
; act
= act
->prev()) {
1764 // It's okay to force allocation of a cache here; we're about to put
1765 // something in the top cache, and all the lower ones should exist
1767 auto* cache
= act
->getLiveSavedFrameCache(cx
);
1772 cache
->findWithoutInvalidation(target
, &saved
);
1778 // Since |target| has its cached bit set, we should have found it.
1779 MOZ_ALWAYS_TRUE(saved
);
1781 // Because we use findWithoutInvalidation here, we can technically get a
1782 // SavedFrame here for any realm. That shouldn't happen here because
1783 // checkForEvalInFramePrev is only called _after_ the parent frames have
1784 // been constructed, but if something prevents the chain from being properly
1785 // reconstructed, that invariant could be accidentally broken.
1786 MOZ_ASSERT(saved
->realm() == cx
->realm());
1788 lookup
.setParent(saved
);
1792 SavedFrame
* SavedStacks::getOrCreateSavedFrame(
1793 JSContext
* cx
, Handle
<SavedFrame::Lookup
> lookup
) {
1794 const SavedFrame::Lookup
& lookupInstance
= lookup
.get();
1795 DependentAddPtr
<SavedFrame::Set
> p(cx
, frames
, lookupInstance
);
1801 Rooted
<SavedFrame
*> frame(cx
, createFrameFromLookup(cx
, lookup
));
1806 if (!p
.add(cx
, frames
, lookupInstance
, frame
)) {
1813 SavedFrame
* SavedStacks::createFrameFromLookup(
1814 JSContext
* cx
, Handle
<SavedFrame::Lookup
> lookup
) {
1815 Rooted
<SavedFrame
*> frame(cx
, SavedFrame::create(cx
));
1819 frame
->initFromLookup(cx
, lookup
);
1821 if (!FreezeObject(cx
, frame
)) {
1828 bool SavedStacks::getLocation(JSContext
* cx
, const FrameIter
& iter
,
1829 MutableHandle
<LocationValue
> locationp
) {
1830 // We should only ever be caching location values for scripts in this
1831 // compartment. Otherwise, we would get dead cross-compartment scripts in
1832 // the cache because our compartment's sweep method isn't called when their
1833 // compartment gets collected.
1834 MOZ_DIAGNOSTIC_ASSERT(&cx
->realm()->savedStacks() == this);
1835 cx
->check(iter
.compartment());
1837 // When we have a |JSScript| for this frame, use a potentially memoized
1838 // location from our PCLocationMap and copy it into |locationp|. When we do
1839 // not have a |JSScript| for this frame (wasm frames), we take a slow path
1840 // that doesn't employ memoization, and update |locationp|'s slots directly.
1842 if (iter
.isWasm()) {
1843 // Only asm.js has a displayURL.
1844 if (const char16_t
* displayURL
= iter
.displayURL()) {
1845 locationp
.setSource(AtomizeChars(cx
, displayURL
, js_strlen(displayURL
)));
1847 const char* filename
= iter
.filename() ? iter
.filename() : "";
1848 locationp
.setSource(AtomizeUTF8Chars(cx
, filename
, strlen(filename
)));
1850 if (!locationp
.source()) {
1854 JS::TaggedColumnNumberOneOrigin column
;
1855 locationp
.setLine(iter
.computeLine(&column
));
1856 locationp
.setColumn(column
);
1860 RootedScript
script(cx
, iter
.script());
1861 jsbytecode
* pc
= iter
.pc();
1863 PCLocationMap::AddPtr p
= pcLocationMap
.lookupForAdd(PCKey(script
, pc
));
1866 Rooted
<JSAtom
*> source(cx
);
1867 if (const char16_t
* displayURL
= iter
.displayURL()) {
1868 source
= AtomizeChars(cx
, displayURL
, js_strlen(displayURL
));
1870 const char* filename
= script
->filename() ? script
->filename() : "";
1871 source
= AtomizeUTF8Chars(cx
, filename
, strlen(filename
));
1877 uint32_t sourceId
= script
->scriptSource()->id();
1878 JS::LimitedColumnNumberOneOrigin column
;
1879 uint32_t line
= PCToLineNumber(script
, pc
, &column
);
1881 PCKey
key(script
, pc
);
1882 LocationValue
value(source
, sourceId
, line
,
1883 JS::TaggedColumnNumberOneOrigin(column
));
1884 if (!pcLocationMap
.add(p
, key
, value
)) {
1885 ReportOutOfMemory(cx
);
1890 locationp
.set(p
->value());
1894 void SavedStacks::chooseSamplingProbability(Realm
* realm
) {
1896 JSRuntime
* runtime
= realm
->runtimeFromMainThread();
1897 if (runtime
->recordAllocationCallback
) {
1898 // The runtime is tracking allocations across all realms, in this case
1899 // ignore all of the debugger values, and use the runtime's probability.
1900 this->setSamplingProbability(runtime
->allocationSamplingProbability
);
1905 // Use unbarriered version to prevent triggering read barrier while
1906 // collecting, this is safe as long as global does not escape.
1907 GlobalObject
* global
= realm
->unsafeUnbarrieredMaybeGlobal();
1912 Maybe
<double> probability
= DebugAPI::allocationSamplingProbability(global
);
1913 if (probability
.isNothing()) {
1917 this->setSamplingProbability(*probability
);
1920 void SavedStacks::setSamplingProbability(double probability
) {
1921 if (!bernoulliSeeded
) {
1922 mozilla::Array
<uint64_t, 2> seed
;
1923 GenerateXorShift128PlusSeed(seed
);
1924 bernoulli
.setRandomState(seed
[0], seed
[1]);
1925 bernoulliSeeded
= true;
1928 bernoulli
.setProbability(probability
);
1931 JSObject
* SavedStacks::MetadataBuilder::build(
1932 JSContext
* cx
, HandleObject target
,
1933 AutoEnterOOMUnsafeRegion
& oomUnsafe
) const {
1934 RootedObject
obj(cx
, target
);
1936 SavedStacks
& stacks
= cx
->realm()->savedStacks();
1937 if (!stacks
.bernoulli
.trial()) {
1941 Rooted
<SavedFrame
*> frame(cx
);
1942 if (!stacks
.saveCurrentStack(cx
, &frame
)) {
1943 oomUnsafe
.crash("SavedStacksMetadataBuilder");
1946 if (!DebugAPI::onLogAllocationSite(cx
, obj
, frame
,
1947 mozilla::TimeStamp::Now())) {
1948 oomUnsafe
.crash("SavedStacksMetadataBuilder");
1951 auto recordAllocationCallback
=
1952 cx
->realm()->runtimeFromMainThread()->recordAllocationCallback
;
1953 if (recordAllocationCallback
) {
1954 // The following code translates the JS-specific information, into an
1955 // RecordAllocationInfo object that can be consumed outside of SpiderMonkey.
1957 auto node
= JS::ubi::Node(obj
.get());
1959 // Pass the non-SpiderMonkey specific information back to the
1960 // callback to get it out of the JS engine.
1961 recordAllocationCallback(JS::RecordAllocationInfo
{
1962 node
.typeName(), node
.jsObjectClassName(), node
.descriptiveTypeName(),
1963 JS::ubi::CoarseTypeToString(node
.coarseType()),
1964 node
.size(cx
->runtime()->debuggerMallocSizeOf
),
1965 gc::IsInsideNursery(obj
)});
1968 MOZ_ASSERT_IF(frame
, !frame
->is
<WrapperObject
>());
1972 const SavedStacks::MetadataBuilder
SavedStacks::metadataBuilder
;
1975 ReconstructedSavedFramePrincipals
ReconstructedSavedFramePrincipals::IsSystem
;
1977 ReconstructedSavedFramePrincipals
1978 ReconstructedSavedFramePrincipals::IsNotSystem
;
1980 UniqueChars
BuildUTF8StackString(JSContext
* cx
, JSPrincipals
* principals
,
1981 HandleObject stack
) {
1982 RootedString
stackStr(cx
);
1983 if (!JS::BuildStackString(cx
, principals
, stack
, &stackStr
)) {
1987 return JS_EncodeStringToUTF8(cx
, stackStr
);
1990 } /* namespace js */
1995 bool ConcreteStackFrame
<SavedFrame
>::isSystem() const {
1996 auto trustedPrincipals
= get().runtimeFromAnyThread()->trustedPrincipals();
1997 return get().getPrincipals() == trustedPrincipals
||
1998 get().getPrincipals() ==
1999 &js::ReconstructedSavedFramePrincipals::IsSystem
;
2002 bool ConcreteStackFrame
<SavedFrame
>::constructSavedFrameStack(
2003 JSContext
* cx
, MutableHandleObject outSavedFrameStack
) const {
2004 outSavedFrameStack
.set(&get());
2005 if (!cx
->compartment()->wrap(cx
, outSavedFrameStack
)) {
2006 outSavedFrameStack
.set(nullptr);
2012 // A `mozilla::Variant` matcher that converts the inner value of a
2013 // `JS::ubi::AtomOrTwoByteChars` string to a `JSAtom*`.
2014 struct MOZ_STACK_CLASS AtomizingMatcher
{
2018 explicit AtomizingMatcher(JSContext
* cx
, size_t length
)
2019 : cx(cx
), length(length
) {}
2021 JSAtom
* operator()(JSAtom
* atom
) {
2026 JSAtom
* operator()(const char16_t
* chars
) {
2028 return AtomizeChars(cx
, chars
, length
);
2032 JS_PUBLIC_API
bool ConstructSavedFrameStackSlow(
2033 JSContext
* cx
, JS::ubi::StackFrame
& frame
,
2034 MutableHandleObject outSavedFrameStack
) {
2035 Rooted
<js::GCLookupVector
> stackChain(cx
, js::GCLookupVector(cx
));
2036 Rooted
<JS::ubi::StackFrame
> ubiFrame(cx
, frame
);
2038 while (ubiFrame
.get()) {
2039 // Convert the source and functionDisplayName strings to atoms.
2041 Rooted
<JSAtom
*> source(cx
);
2042 AtomizingMatcher
atomizer(cx
, ubiFrame
.get().sourceLength());
2043 source
= ubiFrame
.get().source().match(atomizer
);
2048 Rooted
<JSAtom
*> functionDisplayName(cx
);
2049 auto nameLength
= ubiFrame
.get().functionDisplayNameLength();
2050 if (nameLength
> 0) {
2051 AtomizingMatcher
atomizer(cx
, nameLength
);
2052 functionDisplayName
=
2053 ubiFrame
.get().functionDisplayName().match(atomizer
);
2054 if (!functionDisplayName
) {
2060 js::ReconstructedSavedFramePrincipals::getSingleton(ubiFrame
.get());
2062 if (!stackChain
.emplaceBack(source
, ubiFrame
.get().sourceId(),
2063 ubiFrame
.get().line(), ubiFrame
.get().column(),
2064 functionDisplayName
,
2065 /* asyncCause */ nullptr,
2066 /* parent */ nullptr, principals
,
2067 /* mutedErrors */ true)) {
2068 ReportOutOfMemory(cx
);
2072 ubiFrame
= ubiFrame
.get().parent();
2075 Rooted
<js::SavedFrame
*> parentFrame(cx
);
2076 for (size_t i
= stackChain
.length(); i
!= 0; i
--) {
2077 MutableHandle
<SavedFrame::Lookup
> lookup
= stackChain
[i
- 1];
2078 lookup
.setParent(parentFrame
);
2079 parentFrame
= cx
->realm()->savedStacks().getOrCreateSavedFrame(cx
, lookup
);
2085 outSavedFrameStack
.set(parentFrame
);