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