Source code

Revision control

Other Tools

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