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