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