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