Bug 1885337 - Part 1: Implement to/from hex methods. r=dminor
[gecko.git] / js / src / vm / SavedStacks.cpp
blob4f72e547ecffc1cb76525f0f5ef932354f0c89e1
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "vm/SavedStacks.h"
9 #include "mozilla/Attributes.h"
10 #include "mozilla/DebugOnly.h"
12 #include <algorithm>
13 #include <utility>
15 #include "jsapi.h"
16 #include "jsmath.h"
17 #include "jsnum.h"
19 #include "gc/GCContext.h"
20 #include "gc/HashUtil.h"
21 #include "js/CharacterEncoding.h"
22 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
23 #include "js/ErrorReport.h" // JSErrorBase
24 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
25 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty
26 #include "js/PropertySpec.h"
27 #include "js/SavedFrameAPI.h"
28 #include "js/Stack.h"
29 #include "js/Vector.h"
30 #include "util/DifferentialTesting.h"
31 #include "util/StringBuffer.h"
32 #include "vm/Compartment.h"
33 #include "vm/FrameIter.h"
34 #include "vm/GeckoProfiler.h"
35 #include "vm/JSScript.h"
36 #include "vm/Realm.h"
37 #include "vm/SavedFrame.h"
38 #include "vm/WrapperObject.h"
40 #include "debugger/DebugAPI-inl.h"
41 #include "gc/StableCellHasher-inl.h"
42 #include "vm/GeckoProfiler-inl.h"
43 #include "vm/JSContext-inl.h"
45 using mozilla::AddToHash;
46 using mozilla::DebugOnly;
47 using mozilla::Maybe;
48 using mozilla::Nothing;
49 using mozilla::Some;
51 namespace js {
53 /**
54 * Maximum number of saved frames returned for an async stack.
56 const uint32_t ASYNC_STACK_MAX_FRAME_COUNT = 60;
58 void LiveSavedFrameCache::trace(JSTracer* trc) {
59 if (!initialized()) {
60 return;
63 for (auto* entry = frames->begin(); entry < frames->end(); entry++) {
64 TraceEdge(trc, &entry->savedFrame,
65 "LiveSavedFrameCache::frames SavedFrame");
69 bool LiveSavedFrameCache::insert(JSContext* cx, FramePtr&& framePtr,
70 const jsbytecode* pc,
71 Handle<SavedFrame*> savedFrame) {
72 MOZ_ASSERT(savedFrame);
73 MOZ_ASSERT(initialized());
75 #ifdef DEBUG
76 // There should not already be an entry for this frame. Checking the full
77 // stack really slows down some tests, so just check the first and last five
78 // hundred.
79 size_t limit = std::min(frames->length() / 2, size_t(500));
80 for (size_t i = 0; i < limit; i++) {
81 MOZ_ASSERT(Key(framePtr) != (*frames)[i].key);
82 MOZ_ASSERT(Key(framePtr) != (*frames)[frames->length() - 1 - i].key);
84 #endif
86 if (!frames->emplaceBack(framePtr, pc, savedFrame)) {
87 ReportOutOfMemory(cx);
88 return false;
91 framePtr.setHasCachedSavedFrame();
93 return true;
96 void LiveSavedFrameCache::find(JSContext* cx, FramePtr& framePtr,
97 const jsbytecode* pc,
98 MutableHandle<SavedFrame*> frame) const {
99 MOZ_ASSERT(initialized());
100 MOZ_ASSERT(framePtr.hasCachedSavedFrame());
102 // The assertions here check that either 1) frames' hasCachedSavedFrame flags
103 // accurately indicate the presence of a cache entry for that frame (ignoring
104 // pc mismatches), or 2) the cache is completely empty, having been flushed
105 // for a realm mismatch.
107 // If we flushed the cache due to a realm mismatch, then we shouldn't
108 // expect to find any frames in the cache.
109 if (frames->empty()) {
110 frame.set(nullptr);
111 return;
114 // All our SavedFrames should be in the same realm. If the last
115 // entry's SavedFrame's realm doesn't match cx's, flush the cache.
116 if (frames->back().savedFrame->realm() != cx->realm()) {
117 #ifdef DEBUG
118 // Check that they are, indeed, all in the same realm.
119 auto realm = frames->back().savedFrame->realm();
120 for (const auto& f : (*frames)) {
121 MOZ_ASSERT(realm == f.savedFrame->realm());
123 #endif
124 frames->clear();
125 frame.set(nullptr);
126 return;
129 Key key(framePtr);
130 while (key != frames->back().key) {
131 MOZ_ASSERT(frames->back().savedFrame->realm() == cx->realm());
133 // framePtr must have an entry, but apparently it's below this one on the
134 // stack; frames->back() must correspond to a frame younger than framePtr's.
135 // SavedStacks::insertFrames is going to push new cache entries for
136 // everything younger than framePtr, so this entry should be popped.
137 frames->popBack();
139 // If the frame's bit was set, the frame should always have an entry in
140 // the cache. (If we purged the entire cache because its SavedFrames had
141 // been captured for a different realm, then we would have
142 // returned early above.)
143 MOZ_RELEASE_ASSERT(!frames->empty());
146 // The youngest valid frame may have run some code, so its current pc may
147 // not match its cache entry's pc. In this case, just treat it as a miss. No
148 // older frame has executed any code; it would have been necessary to pop
149 // this frame for that to happen, but this frame's bit is set.
150 if (pc != frames->back().pc) {
151 frames->popBack();
152 frame.set(nullptr);
153 return;
156 frame.set(frames->back().savedFrame);
159 void LiveSavedFrameCache::findWithoutInvalidation(
160 const FramePtr& framePtr, MutableHandle<SavedFrame*> frame) const {
161 MOZ_ASSERT(initialized());
162 MOZ_ASSERT(framePtr.hasCachedSavedFrame());
164 Key key(framePtr);
165 for (auto& entry : (*frames)) {
166 if (entry.key == key) {
167 frame.set(entry.savedFrame);
168 return;
172 frame.set(nullptr);
175 struct MOZ_STACK_CLASS SavedFrame::Lookup {
176 Lookup(JSAtom* source, uint32_t sourceId, uint32_t line,
177 JS::TaggedColumnNumberOneOrigin column, JSAtom* functionDisplayName,
178 JSAtom* asyncCause, SavedFrame* parent, JSPrincipals* principals,
179 bool mutedErrors,
180 const Maybe<LiveSavedFrameCache::FramePtr>& framePtr = Nothing(),
181 jsbytecode* pc = nullptr, Activation* activation = nullptr)
182 : source(source),
183 sourceId(sourceId),
184 line(line),
185 column(column),
186 functionDisplayName(functionDisplayName),
187 asyncCause(asyncCause),
188 parent(parent),
189 principals(principals),
190 mutedErrors(mutedErrors),
191 framePtr(framePtr),
192 pc(pc),
193 activation(activation) {
194 MOZ_ASSERT(source);
195 MOZ_ASSERT_IF(framePtr.isSome(), activation);
196 if (js::SupportDifferentialTesting()) {
197 this->column = JS::TaggedColumnNumberOneOrigin::forDifferentialTesting();
201 explicit Lookup(SavedFrame& savedFrame)
202 : source(savedFrame.getSource()),
203 sourceId(savedFrame.getSourceId()),
204 line(savedFrame.getLine()),
205 column(savedFrame.getColumn()),
206 functionDisplayName(savedFrame.getFunctionDisplayName()),
207 asyncCause(savedFrame.getAsyncCause()),
208 parent(savedFrame.getParent()),
209 principals(savedFrame.getPrincipals()),
210 mutedErrors(savedFrame.getMutedErrors()),
211 framePtr(Nothing()),
212 pc(nullptr),
213 activation(nullptr) {
214 MOZ_ASSERT(source);
217 JSAtom* source;
218 uint32_t sourceId;
220 // Line number (1-origin).
221 uint32_t line;
223 // Columm number in UTF-16 code units.
224 JS::TaggedColumnNumberOneOrigin column;
226 JSAtom* functionDisplayName;
227 JSAtom* asyncCause;
228 SavedFrame* parent;
229 JSPrincipals* principals;
230 bool mutedErrors;
232 // These are used only by the LiveSavedFrameCache and not used for identity or
233 // hashing.
234 Maybe<LiveSavedFrameCache::FramePtr> framePtr;
235 jsbytecode* pc;
236 Activation* activation;
238 void trace(JSTracer* trc) {
239 TraceRoot(trc, &source, "SavedFrame::Lookup::source");
240 TraceNullableRoot(trc, &functionDisplayName,
241 "SavedFrame::Lookup::functionDisplayName");
242 TraceNullableRoot(trc, &asyncCause, "SavedFrame::Lookup::asyncCause");
243 TraceNullableRoot(trc, &parent, "SavedFrame::Lookup::parent");
247 using GCLookupVector =
248 GCVector<SavedFrame::Lookup, ASYNC_STACK_MAX_FRAME_COUNT>;
250 template <class Wrapper>
251 class WrappedPtrOperations<SavedFrame::Lookup, Wrapper> {
252 const SavedFrame::Lookup& value() const {
253 return static_cast<const Wrapper*>(this)->get();
256 public:
257 JSAtom* source() { return value().source; }
258 uint32_t sourceId() { return value().sourceId; }
259 uint32_t line() { return value().line; }
260 JS::TaggedColumnNumberOneOrigin column() { return value().column; }
261 JSAtom* functionDisplayName() { return value().functionDisplayName; }
262 JSAtom* asyncCause() { return value().asyncCause; }
263 SavedFrame* parent() { return value().parent; }
264 JSPrincipals* principals() { return value().principals; }
265 bool mutedErrors() { return value().mutedErrors; }
266 Maybe<LiveSavedFrameCache::FramePtr> framePtr() { return value().framePtr; }
267 jsbytecode* pc() { return value().pc; }
268 Activation* activation() { return value().activation; }
271 template <typename Wrapper>
272 class MutableWrappedPtrOperations<SavedFrame::Lookup, Wrapper>
273 : public WrappedPtrOperations<SavedFrame::Lookup, Wrapper> {
274 SavedFrame::Lookup& value() { return static_cast<Wrapper*>(this)->get(); }
276 public:
277 void setParent(SavedFrame* parent) { value().parent = parent; }
279 void setAsyncCause(Handle<JSAtom*> asyncCause) {
280 value().asyncCause = asyncCause;
284 /* static */
285 bool SavedFrame::HashPolicy::maybeGetHash(const Lookup& l,
286 HashNumber* hashOut) {
287 HashNumber parentHash;
288 if (!SavedFramePtrHasher::maybeGetHash(l.parent, &parentHash)) {
289 return false;
291 *hashOut = calculateHash(l, parentHash);
292 return true;
295 /* static */
296 bool SavedFrame::HashPolicy::ensureHash(const Lookup& l, HashNumber* hashOut) {
297 HashNumber parentHash;
298 if (!SavedFramePtrHasher::ensureHash(l.parent, &parentHash)) {
299 return false;
301 *hashOut = calculateHash(l, parentHash);
302 return true;
305 /* static */
306 HashNumber SavedFrame::HashPolicy::hash(const Lookup& lookup) {
307 return calculateHash(lookup, SavedFramePtrHasher::hash(lookup.parent));
310 /* static */
311 HashNumber SavedFrame::HashPolicy::calculateHash(const Lookup& lookup,
312 HashNumber parentHash) {
313 JS::AutoCheckCannotGC nogc;
314 // Assume that we can take line mod 2^32 without losing anything of
315 // interest. If that assumption changes, we'll just need to start with 0
316 // and add another overload of AddToHash with more arguments.
317 return AddToHash(lookup.line, lookup.column.rawValue(), lookup.source,
318 lookup.functionDisplayName, lookup.asyncCause,
319 lookup.mutedErrors, parentHash,
320 JSPrincipalsPtrHasher::hash(lookup.principals));
323 /* static */
324 bool SavedFrame::HashPolicy::match(SavedFrame* existing, const Lookup& lookup) {
325 MOZ_ASSERT(existing);
327 if (existing->getLine() != lookup.line) {
328 return false;
331 if (existing->getColumn() != lookup.column) {
332 return false;
335 if (existing->getParent() != lookup.parent) {
336 return false;
339 if (existing->getPrincipals() != lookup.principals) {
340 return false;
343 JSAtom* source = existing->getSource();
344 if (source != lookup.source) {
345 return false;
348 JSAtom* functionDisplayName = existing->getFunctionDisplayName();
349 if (functionDisplayName != lookup.functionDisplayName) {
350 return false;
353 JSAtom* asyncCause = existing->getAsyncCause();
354 if (asyncCause != lookup.asyncCause) {
355 return false;
358 return true;
361 /* static */
362 void SavedFrame::HashPolicy::rekey(Key& key, const Key& newKey) {
363 key = newKey;
366 /* static */
367 bool SavedFrame::finishSavedFrameInit(JSContext* cx, HandleObject ctor,
368 HandleObject proto) {
369 return FreezeObject(cx, proto);
372 static const JSClassOps SavedFrameClassOps = {
373 nullptr, // addProperty
374 nullptr, // delProperty
375 nullptr, // enumerate
376 nullptr, // newEnumerate
377 nullptr, // resolve
378 nullptr, // mayResolve
379 SavedFrame::finalize, // finalize
380 nullptr, // call
381 nullptr, // construct
382 nullptr, // trace
385 const ClassSpec SavedFrame::classSpec_ = {
386 GenericCreateConstructor<SavedFrame::construct, 0, gc::AllocKind::FUNCTION>,
387 GenericCreatePrototype<SavedFrame>,
388 SavedFrame::staticFunctions,
389 nullptr,
390 SavedFrame::protoFunctions,
391 SavedFrame::protoAccessors,
392 SavedFrame::finishSavedFrameInit,
393 ClassSpec::DontDefineConstructor};
395 /* static */ const JSClass SavedFrame::class_ = {
396 "SavedFrame",
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),
422 JS_PS_END};
424 /* static */
425 void SavedFrame::finalize(JS::GCContext* gcx, JSObject* obj) {
426 MOZ_ASSERT(gcx->onMainThread());
427 JSPrincipals* p = obj->as<SavedFrame>().getPrincipals();
428 if (p) {
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();
437 return &s->asAtom();
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);
457 if (v.isNull()) {
458 return nullptr;
460 JSString* s = v.toString();
461 return &s->asAtom();
464 JSAtom* SavedFrame::getAsyncCause() {
465 const Value& v = getReservedSlot(JSSLOT_ASYNCCAUSE);
466 if (v.isNull()) {
467 return nullptr;
469 JSString* s = v.toString();
470 return &s->asAtom();
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()) {
481 return nullptr;
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()) {
489 return true;
491 return bool(uintptr_t(v.toPrivate()) & 0b1);
494 void SavedFrame::initSource(JSAtom* source) {
495 MOZ_ASSERT(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,
515 bool mutedErrors) {
516 if (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());
570 /* static */
571 SavedFrame* SavedFrame::create(JSContext* cx) {
572 Rooted<GlobalObject*> global(cx, cx->global());
573 cx->check(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));
582 if (!proto) {
583 return nullptr;
585 cx->check(proto);
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());
603 return getLine();
606 /* static */
607 bool SavedFrame::construct(JSContext* cx, unsigned argc, Value* vp) {
608 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
609 "SavedFrame");
610 return false;
613 static bool SavedFrameSubsumedByPrincipals(JSContext* cx,
614 JSPrincipals* principals,
615 Handle<SavedFrame*> frame) {
616 auto subsumes = cx->runtime()->securityCallbacks->subsumes;
617 if (!subsumes) {
618 return true;
621 MOZ_ASSERT(!ReconstructedSavedFramePrincipals::is(principals));
623 auto framePrincipals = frame->getPrincipals();
625 // Handle SavedFrames that have been reconstructed from stacks in a heap
626 // snapshot.
627 if (framePrincipals == &ReconstructedSavedFramePrincipals::IsSystem) {
628 return cx->runningWithTrustedPrincipals();
630 if (framePrincipals == &ReconstructedSavedFramePrincipals::IsNotSystem) {
631 return true;
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,
643 Matcher& matches,
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)) {
654 return rootedFrame;
657 if (rootedFrame->getAsyncCause()) {
658 skippedAsync = true;
661 rootedFrame = rootedFrame->getParent();
664 return nullptr;
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
671 // set to false.
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) {
684 if (!savedFrame) {
685 return nullptr;
688 auto subsumes = cx->runtime()->securityCallbacks->subsumes;
689 if (!subsumes) {
690 return nullptr;
693 auto matcher = [subsumes](JSContext* cx, JSPrincipals* principals,
694 Handle<SavedFrame*> frame) -> bool {
695 return subsumes(principals, frame->getPrincipals());
698 bool skippedAsync;
699 Rooted<SavedFrame*> frame(cx, &savedFrame->as<SavedFrame>());
700 return GetFirstMatchedFrame(cx, principals, matcher, frame, selfHosted,
701 skippedAsync);
704 [[nodiscard]] static bool SavedFrame_checkThis(JSContext* cx, CallArgs& args,
705 const char* fnName,
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));
713 return false;
716 if (!thisValue.toObject().canUnwrapAs<SavedFrame>()) {
717 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
718 JSMSG_INCOMPATIBLE_PROTO, SavedFrame::class_.name,
719 fnName, "object");
720 return false;
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());
727 return true;
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:
734 // - JSContext* cx
735 // - unsigned argc
736 // - Value* vp
737 // - const char* fnName
738 // These parameters will be defined after calling this macro:
739 // - CallArgs args
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;
746 } /* namespace js */
748 js::SavedFrame* js::UnwrapSavedFrame(JSContext* cx, JSPrincipals* principals,
749 HandleObject obj,
750 JS::SavedFrameSelfHosted selfHosted,
751 bool& skippedAsync) {
752 if (!obj) {
753 return nullptr;
756 Rooted<SavedFrame*> frame(cx, obj->maybeUnwrapAs<SavedFrame>());
757 if (!frame) {
758 return nullptr;
761 return GetFirstSubsumedFrame(cx, principals, frame, selfHosted, skippedAsync);
764 namespace JS {
766 JS_PUBLIC_API SavedFrameResult GetSavedFrameSource(
767 JSContext* cx, JSPrincipals* principals, HandleObject savedFrame,
768 MutableHandleString sourcep,
769 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) {
770 js::AssertHeapIsIdle();
771 CHECK_THREAD(cx);
772 MOZ_RELEASE_ASSERT(cx->realm());
775 bool skippedAsync;
776 Rooted<js::SavedFrame*> frame(
778 UnwrapSavedFrame(cx, principals, savedFrame, selfHosted, skippedAsync));
779 if (!frame) {
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,
793 uint32_t* sourceIdp,
794 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) {
795 js::AssertHeapIsIdle();
796 CHECK_THREAD(cx);
797 MOZ_RELEASE_ASSERT(cx->realm());
799 bool skippedAsync;
800 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame,
801 selfHosted, skippedAsync));
802 if (!frame) {
803 *sourceIdp = 0;
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,
812 uint32_t* linep,
813 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) {
814 js::AssertHeapIsIdle();
815 CHECK_THREAD(cx);
816 MOZ_RELEASE_ASSERT(cx->realm());
817 MOZ_ASSERT(linep);
819 bool skippedAsync;
820 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame,
821 selfHosted, skippedAsync));
822 if (!frame) {
823 *linep = 0;
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();
835 CHECK_THREAD(cx);
836 MOZ_RELEASE_ASSERT(cx->realm());
837 MOZ_ASSERT(columnp);
839 bool skippedAsync;
840 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame,
841 selfHosted, skippedAsync));
842 if (!frame) {
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();
855 CHECK_THREAD(cx);
856 MOZ_RELEASE_ASSERT(cx->realm());
859 bool skippedAsync;
860 Rooted<js::SavedFrame*> frame(
862 UnwrapSavedFrame(cx, principals, savedFrame, selfHosted, skippedAsync));
863 if (!frame) {
864 namep.set(nullptr);
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();
880 CHECK_THREAD(cx);
881 MOZ_RELEASE_ASSERT(cx->realm());
884 bool skippedAsync;
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));
893 if (!frame) {
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();
913 CHECK_THREAD(cx);
914 MOZ_RELEASE_ASSERT(cx->realm());
916 bool skippedAsync;
917 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame,
918 selfHosted, skippedAsync));
919 if (!frame) {
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);
937 } else {
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();
948 CHECK_THREAD(cx);
949 MOZ_RELEASE_ASSERT(cx->realm());
951 bool skippedAsync;
952 Rooted<js::SavedFrame*> frame(cx, UnwrapSavedFrame(cx, principals, savedFrame,
953 selfHosted, skippedAsync));
954 if (!frame) {
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)) {
971 parentp.set(parent);
972 } else {
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) &&
984 sb.append(']');
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;
995 size_t cstrlen;
996 const char* cstr =
997 Uint32ToHexCString(&cbuf, frame->wasmBytecodeOffset(), &cstrlen);
998 MOZ_ASSERT(cstr);
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,
1026 bool lastFrame) {
1027 Rooted<JSAtom*> name(cx, frame->getFunctionDisplayName());
1028 return sb.appendN(' ', indent + 4) && sb.append('a') && sb.append('t') &&
1029 sb.append(' ') &&
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,
1038 HandleObject stack,
1039 MutableHandleString stringp, size_t indent,
1040 js::StackFormat format) {
1041 js::AssertHeapIsIdle();
1042 CHECK_THREAD(cx);
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.
1057 bool skippedAsync;
1058 Rooted<js::SavedFrame*> frame(
1059 cx, UnwrapSavedFrame(cx, principals, stack,
1060 SavedFrameSelfHosted::Exclude, skippedAsync));
1061 if (!frame) {
1062 stringp.set(cx->runtime()->emptyString);
1063 return true;
1066 Rooted<js::SavedFrame*> parent(cx);
1067 do {
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,
1076 skippedNextAsync));
1078 switch (format) {
1079 case js::StackFormat::SpiderMonkey:
1080 if (!FormatSpiderMonkeyStackFrame(cx, sb, frame, indent,
1081 skippedAsync)) {
1082 return false;
1084 break;
1085 case js::StackFormat::V8:
1086 if (!FormatV8StackFrame(cx, sb, frame, indent, !nextFrame)) {
1087 return false;
1089 break;
1090 case js::StackFormat::Default:
1091 MOZ_CRASH("Unexpected value");
1092 break;
1095 frame = nextFrame;
1096 skippedAsync = skippedNextAsync;
1097 } while (frame);
1100 JSString* str = sb.finishString();
1101 if (!str) {
1102 return false;
1104 cx->check(str);
1105 stringp.set(str);
1106 return true;
1109 JS_PUBLIC_API bool IsMaybeWrappedSavedFrame(JSObject* obj) {
1110 MOZ_ASSERT(obj);
1111 return obj->canUnwrapAs<js::SavedFrame>();
1114 JS_PUBLIC_API bool IsUnwrappedSavedFrame(JSObject* obj) {
1115 MOZ_ASSERT(obj);
1116 return obj->is<js::SavedFrame>();
1119 static bool AssignProperty(JSContext* cx, HandleObject dst, HandleObject src,
1120 const char* property) {
1121 RootedValue v(cx);
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);
1133 RootedValue v(cx);
1135 baseConverted = lastConverted = JS_NewObject(cx, nullptr);
1136 if (!baseConverted) {
1137 return nullptr;
1140 bool foundParent;
1141 do {
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")) {
1148 return nullptr;
1151 const char* parentProperties[] = {"parent", "asyncParent"};
1152 foundParent = false;
1153 for (const char* prop : parentProperties) {
1154 if (!JS_GetProperty(cx, savedFrame, prop, &v)) {
1155 return nullptr;
1157 if (v.isObject()) {
1158 RootedObject nextConverted(cx, JS_NewObject(cx, nullptr));
1159 if (!nextConverted ||
1160 !JS_DefineProperty(cx, lastConverted, prop, nextConverted,
1161 JSPROP_ENUMERATE)) {
1162 return nullptr;
1164 lastConverted = nextConverted;
1165 savedFrame = &v.toObject();
1166 foundParent = true;
1167 break;
1170 } while (foundParent);
1172 return baseConverted;
1175 } /* namespace JS */
1177 namespace js {
1179 /* static */
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)) {
1187 return false;
1189 args.rval().setString(source);
1190 } else {
1191 args.rval().setNull();
1193 return true;
1196 /* static */
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();
1200 uint32_t sourceId;
1201 if (JS::GetSavedFrameSourceId(cx, principals, frame, &sourceId) ==
1202 JS::SavedFrameResult::Ok) {
1203 args.rval().setNumber(sourceId);
1204 } else {
1205 args.rval().setNull();
1207 return true;
1210 /* static */
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();
1214 uint32_t line;
1215 if (JS::GetSavedFrameLine(cx, principals, frame, &line) ==
1216 JS::SavedFrameResult::Ok) {
1217 args.rval().setNumber(line);
1218 } else {
1219 args.rval().setNull();
1221 return true;
1224 /* static */
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());
1232 } else {
1233 args.rval().setNull();
1235 return true;
1238 /* static */
1239 bool SavedFrame::functionDisplayNameProperty(JSContext* cx, unsigned argc,
1240 Value* vp) {
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)) {
1248 return false;
1250 args.rval().setString(name);
1251 } else {
1252 args.rval().setNull();
1254 return true;
1257 /* static */
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)) {
1266 return false;
1268 args.rval().setString(asyncCause);
1269 } else {
1270 args.rval().setNull();
1272 return true;
1275 /* static */
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)) {
1282 return false;
1284 args.rval().setObjectOrNull(asyncParent);
1285 return true;
1288 /* static */
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)) {
1295 return false;
1297 args.rval().setObjectOrNull(parent);
1298 return true;
1301 /* static */
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)) {
1307 return false;
1309 args.rval().setString(string);
1310 return true;
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)) {
1321 frame.set(nullptr);
1322 return true;
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) {
1338 return false;
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)) {
1347 return false;
1350 return true;
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) {
1376 class Matcher {
1377 JSContext* cx_;
1378 JSPrincipals* framePrincipals_;
1379 const JSAtom* frameSource_;
1381 public:
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
1439 // link.
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());
1465 if (framePtr) {
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)) {
1491 return false;
1496 if (capture.is<JS::AllFrames>() && framePtr &&
1497 framePtr->hasCachedSavedFrame()) {
1498 auto* cache = activation.getLiveSavedFrameCache(cx);
1499 if (!cache) {
1500 return false;
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()) {
1510 break;
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
1518 // any new entries.
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)) {
1528 return false;
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)) {
1543 return false;
1546 if (captureIsSatisfied(cx, principals, location.source(), capture)) {
1547 break;
1550 ++iter;
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
1568 // stack.
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)));
1578 if (!causeAtom) {
1579 return false;
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
1587 ? Nothing()
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)) {
1594 return false;
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.
1607 break;
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
1631 // created.
1632 if (capture.is<JS::AllFrames>() && lookup.framePtr()) {
1633 if (!checkForEvalInFramePrev(cx, lookup)) {
1634 return false;
1638 frame.set(getOrCreateSavedFrame(cx, lookup));
1639 if (!frame) {
1640 return false;
1643 if (capture.is<JS::AllFrames>() && lookup.framePtr()) {
1644 auto* cache = lookup.activation()->getLiveSavedFrameCache(cx);
1645 if (!cache ||
1646 !cache->insert(cx, *lookup.framePtr(), lookup.pc(), frame)) {
1647 return false;
1652 return true;
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);
1675 return false;
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));
1710 if (!asyncStack) {
1711 return false;
1713 stackChain.popBack();
1716 return true;
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()) {
1735 return true;
1738 InterpreterFrame& interpreterFrame = lookup.framePtr()->asInterpreterFrame();
1739 if (!interpreterFrame.isDebuggerEvalFrame()) {
1740 return true;
1743 FrameIter iter(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
1744 while (!iter.done() &&
1745 (!iter.hasUsableAbstractFramePtr() ||
1746 iter.abstractFramePtr() != interpreterFrame.evalInFramePrev())) {
1747 ++iter;
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
1766 // already.
1767 auto* cache = act->getLiveSavedFrameCache(cx);
1768 if (!cache) {
1769 return false;
1772 cache->findWithoutInvalidation(target, &saved);
1773 if (saved) {
1774 break;
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);
1789 return true;
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);
1796 if (p) {
1797 MOZ_ASSERT(*p);
1798 return *p;
1801 Rooted<SavedFrame*> frame(cx, createFrameFromLookup(cx, lookup));
1802 if (!frame) {
1803 return nullptr;
1806 if (!p.add(cx, frames, lookupInstance, frame)) {
1807 return nullptr;
1810 return frame;
1813 SavedFrame* SavedStacks::createFrameFromLookup(
1814 JSContext* cx, Handle<SavedFrame::Lookup> lookup) {
1815 Rooted<SavedFrame*> frame(cx, SavedFrame::create(cx));
1816 if (!frame) {
1817 return nullptr;
1819 frame->initFromLookup(cx, lookup);
1821 if (!FreezeObject(cx, frame)) {
1822 return nullptr;
1825 return 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)));
1846 } else {
1847 const char* filename = iter.filename() ? iter.filename() : "";
1848 locationp.setSource(AtomizeUTF8Chars(cx, filename, strlen(filename)));
1850 if (!locationp.source()) {
1851 return false;
1854 JS::TaggedColumnNumberOneOrigin column;
1855 locationp.setLine(iter.computeLine(&column));
1856 locationp.setColumn(column);
1857 return true;
1860 RootedScript script(cx, iter.script());
1861 jsbytecode* pc = iter.pc();
1863 PCLocationMap::AddPtr p = pcLocationMap.lookupForAdd(PCKey(script, pc));
1865 if (!p) {
1866 Rooted<JSAtom*> source(cx);
1867 if (const char16_t* displayURL = iter.displayURL()) {
1868 source = AtomizeChars(cx, displayURL, js_strlen(displayURL));
1869 } else {
1870 const char* filename = script->filename() ? script->filename() : "";
1871 source = AtomizeUTF8Chars(cx, filename, strlen(filename));
1873 if (!source) {
1874 return false;
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);
1886 return false;
1890 locationp.set(p->value());
1891 return true;
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);
1901 return;
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();
1908 if (!global) {
1909 return;
1912 Maybe<double> probability = DebugAPI::allocationSamplingProbability(global);
1913 if (probability.isNothing()) {
1914 return;
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()) {
1938 return nullptr;
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>());
1969 return frame;
1972 const SavedStacks::MetadataBuilder SavedStacks::metadataBuilder;
1974 /* static */
1975 ReconstructedSavedFramePrincipals ReconstructedSavedFramePrincipals::IsSystem;
1976 /* static */
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)) {
1984 return nullptr;
1987 return JS_EncodeStringToUTF8(cx, stackStr);
1990 } /* namespace js */
1992 namespace JS {
1993 namespace ubi {
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);
2007 return false;
2009 return true;
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 {
2015 JSContext* cx;
2016 size_t length;
2018 explicit AtomizingMatcher(JSContext* cx, size_t length)
2019 : cx(cx), length(length) {}
2021 JSAtom* operator()(JSAtom* atom) {
2022 MOZ_ASSERT(atom);
2023 return atom;
2026 JSAtom* operator()(const char16_t* chars) {
2027 MOZ_ASSERT(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);
2044 if (!source) {
2045 return false;
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) {
2055 return false;
2059 auto principals =
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);
2069 return false;
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);
2080 if (!parentFrame) {
2081 return false;
2085 outSavedFrameStack.set(parentFrame);
2086 return true;
2089 } // namespace ubi
2090 } // namespace JS