Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
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 : #ifndef vm_SavedStacks_h
8 : #define vm_SavedStacks_h
9 :
10 : #include "mozilla/Attributes.h"
11 : #include "mozilla/FastBernoulliTrial.h"
12 :
13 : #include "jscntxt.h"
14 : #include "jsmath.h"
15 : #include "jswrapper.h"
16 : #include "js/HashTable.h"
17 : #include "vm/SavedFrame.h"
18 : #include "vm/Stack.h"
19 :
20 : namespace js {
21 :
22 : // # Saved Stacks
23 : //
24 : // The `SavedStacks` class provides a compact way to capture and save JS stacks
25 : // as `SavedFrame` `JSObject` subclasses. A single `SavedFrame` object
26 : // represents one frame that was on the stack, and has a strong reference to its
27 : // parent `SavedFrame` (the next youngest frame). This reference is null when
28 : // the `SavedFrame` object is the oldest frame that was on the stack.
29 : //
30 : // This comment documents implementation. For usage documentation, see the
31 : // `js/src/doc/SavedFrame/SavedFrame.md` file and relevant `SavedFrame`
32 : // functions in `js/src/jsapi.h`.
33 : //
34 : // ## Compact
35 : //
36 : // Older saved stack frame tails are shared via hash consing, to deduplicate
37 : // structurally identical data. `SavedStacks` contains a hash table of weakly
38 : // held `SavedFrame` objects, and when the owning compartment is swept, it
39 : // removes entries from this table that aren't held alive in any other way. When
40 : // saving new stacks, we use this table to find pre-existing `SavedFrame`
41 : // objects. If such an object is already extant, it is reused; otherwise a new
42 : // `SavedFrame` is allocated and inserted into the table.
43 : //
44 : // Naive | Hash Consing
45 : // --------------+------------------
46 : // c -> b -> a | c -> b -> a
47 : // | ^
48 : // d -> b -> a | d ---|
49 : // | |
50 : // e -> b -> a | e ---'
51 : //
52 : // This technique is effective because of the nature of the events that trigger
53 : // capturing the stack. Currently, these events consist primarily of `JSObject`
54 : // allocation (when an observing `Debugger` has such tracking), `Promise`
55 : // settlement, and `Error` object creation. While these events may occur many
56 : // times, they tend to occur only at a few locations in the JS source. For
57 : // example, if we enable Object allocation tracking and run the esprima
58 : // JavaScript parser on its own JavaScript source, there are approximately 54700
59 : // total `Object` allocations, but just ~1400 unique JS stacks at allocation
60 : // time. There's only ~200 allocation sites if we capture only the youngest
61 : // stack frame.
62 : //
63 : // ## Security and Wrappers
64 : //
65 : // We save every frame on the stack, regardless of whether the `SavedStack`'s
66 : // compartment's principals subsume the frame's compartment's principals or
67 : // not. This gives us maximum flexibility down the line when accessing and
68 : // presenting captured stacks, but at the price of some complication involved in
69 : // preventing the leakage of privileged stack frames to unprivileged callers.
70 : //
71 : // When a `SavedFrame` method or accessor is called, we compare the caller's
72 : // compartment's principals to each `SavedFrame`'s captured principals. We avoid
73 : // using the usual `CallNonGenericMethod` and `nativeCall` machinery which
74 : // enters the `SavedFrame` object's compartment before we can check these
75 : // principals, because we need access to the original caller's compartment's
76 : // principals (unlike other `CallNonGenericMethod` users) to determine what view
77 : // of the stack to present. Instead, we take a similar approach to that used by
78 : // DOM methods, and manually unwrap wrappers until we get the underlying
79 : // `SavedFrame` object, find the first `SavedFrame` in its stack whose captured
80 : // principals are subsumed by the caller's principals, access the reserved slots
81 : // we care about, and then rewrap return values as necessary.
82 : //
83 : // Consider the following diagram:
84 : //
85 : // Content Compartment
86 : // +---------------------------------------+
87 : // | |
88 : // | +------------------------+ |
89 : // Chrome Compartment | | | |
90 : // +--------------------+ | | SavedFrame C (content) | |
91 : // | | | | | |
92 : // | +--------------+ +------------------------+ |
93 : // | | | ^ |
94 : // | var x -----> | Xray Wrapper |-----. | |
95 : // | | | | | |
96 : // | +--------------+ | +------------------------+ |
97 : // | | | | | | |
98 : // | +--------------+ | | SavedFrame B (content) | |
99 : // | | | | | | |
100 : // | var y -----> | CCW (waived) |--. | +------------------------+ |
101 : // | | | | | ^ |
102 : // | +--------------+ | | | |
103 : // | | | | | | |
104 : // +--------------------+ | | | +------------------------+ |
105 : // | | '-> | | |
106 : // | | | SavedFrame A (chrome) | |
107 : // | '----> | | |
108 : // | +------------------------+ |
109 : // | ^ |
110 : // | | |
111 : // | var z -----' |
112 : // | |
113 : // +---------------------------------------+
114 : //
115 : // CCW is a plain cross-compartment wrapper, yielded by waiving Xray vision. A
116 : // is the youngest `SavedFrame` and represents a frame that was from the chrome
117 : // compartment, while B and C are from frames from the content compartment. C is
118 : // the oldest frame.
119 : //
120 : // Note that it is always safe to waive an Xray around a SavedFrame object,
121 : // because SavedFrame objects and the SavedFrame prototype are always frozen you
122 : // will never run untrusted code.
123 : //
124 : // Depending on who the caller is, the view of the stack will be different, and
125 : // is summarized in the table below.
126 : //
127 : // Var | View
128 : // -----+------------
129 : // x | A -> B -> C
130 : // y, z | B -> C
131 : //
132 : // In the case of x, the `SavedFrame` accessors are called with an Xray wrapper
133 : // around the `SavedFrame` object as the `this` value, and the chrome
134 : // compartment as the cx's current principals. Because the chrome compartment's
135 : // principals subsume both itself and the content compartment's principals, x
136 : // has the complete view of the stack.
137 : //
138 : // In the case of y, the cross-compartment machinery automatically enters the
139 : // content compartment, and calls the `SavedFrame` accessors with the wrapped
140 : // `SavedFrame` object as the `this` value. Because the cx's current compartment
141 : // is the content compartment, and the content compartment's principals do not
142 : // subsume the chrome compartment's principals, it can only see the B and C
143 : // frames.
144 : //
145 : // In the case of z, the `SavedFrame` accessors are called with the `SavedFrame`
146 : // object in the `this` value, and the content compartment as the cx's current
147 : // compartment. Similar to the case of y, only the B and C frames are exposed
148 : // because the cx's current compartment's principals do not subsume A's captured
149 : // principals.
150 :
151 0 : class SavedStacks {
152 : friend class SavedFrame;
153 : friend bool JS::ubi::ConstructSavedFrameStackSlow(JSContext* cx,
154 : JS::ubi::StackFrame& ubiFrame,
155 : MutableHandleObject outSavedFrameStack);
156 :
157 : public:
158 315 : SavedStacks()
159 315 : : frames(),
160 : bernoulliSeeded(false),
161 : bernoulli(1.0, 0x59fdad7f6b4cc573, 0x91adf38db96a9354),
162 315 : creatingSavedFrame(false)
163 315 : { }
164 :
165 : MOZ_MUST_USE bool init();
166 2976 : bool initialized() const { return frames.initialized(); }
167 : MOZ_MUST_USE bool saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame,
168 : JS::StackCapture&& capture = JS::StackCapture(JS::AllFrames()));
169 : MOZ_MUST_USE bool copyAsyncStack(JSContext* cx, HandleObject asyncStack,
170 : HandleString asyncCause,
171 : MutableHandleSavedFrame adoptedStack,
172 : uint32_t maxFrameCount = 0);
173 : void sweep();
174 : void trace(JSTracer* trc);
175 : uint32_t count();
176 : void clear();
177 : void chooseSamplingProbability(JSCompartment*);
178 :
179 : // Set the sampling random number generator's state to |state0| and
180 : // |state1|. One or the other must be non-zero. See the comments for
181 : // mozilla::non_crypto::XorShift128PlusRNG::setState for details.
182 0 : void setRNGState(uint64_t state0, uint64_t state1) { bernoulli.setRandomState(state0, state1); }
183 :
184 : size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
185 :
186 : // An alloction metadata builder that marks cells with the JavaScript stack
187 : // at which they were allocated.
188 : struct MetadataBuilder : public AllocationMetadataBuilder {
189 3 : MetadataBuilder() : AllocationMetadataBuilder() { }
190 : virtual JSObject* build(JSContext *cx, HandleObject obj,
191 : AutoEnterOOMUnsafeRegion& oomUnsafe) const override;
192 : };
193 :
194 : static const MetadataBuilder metadataBuilder;
195 :
196 : private:
197 : SavedFrame::Set frames;
198 : bool bernoulliSeeded;
199 : mozilla::FastBernoulliTrial bernoulli;
200 : bool creatingSavedFrame;
201 :
202 : // Similar to mozilla::ReentrancyGuard, but instead of asserting against
203 : // reentrancy, just change the behavior of SavedStacks::saveCurrentStack to
204 : // return a nullptr SavedFrame.
205 : struct MOZ_RAII AutoReentrancyGuard {
206 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
207 : SavedStacks& stacks;
208 :
209 2016 : explicit AutoReentrancyGuard(SavedStacks& stacks MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
210 2016 : : stacks(stacks)
211 : {
212 2016 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
213 2016 : stacks.creatingSavedFrame = true;
214 2016 : }
215 :
216 2016 : ~AutoReentrancyGuard()
217 2016 : {
218 2016 : stacks.creatingSavedFrame = false;
219 2016 : }
220 : };
221 :
222 : MOZ_MUST_USE bool insertFrames(JSContext* cx, FrameIter& iter,
223 : MutableHandleSavedFrame frame,
224 : JS::StackCapture&& capture);
225 : MOZ_MUST_USE bool adoptAsyncStack(JSContext* cx, HandleSavedFrame asyncStack,
226 : HandleString asyncCause,
227 : MutableHandleSavedFrame adoptedStack,
228 : uint32_t maxFrameCount);
229 : SavedFrame* getOrCreateSavedFrame(JSContext* cx, SavedFrame::HandleLookup lookup);
230 : SavedFrame* createFrameFromLookup(JSContext* cx, SavedFrame::HandleLookup lookup);
231 :
232 : // Cache for memoizing PCToLineNumber lookups.
233 :
234 13713 : struct PCKey {
235 12463 : PCKey(JSScript* script, jsbytecode* pc) : script(script), pc(pc) { }
236 :
237 : HeapPtr<JSScript*> script;
238 : jsbytecode* pc;
239 :
240 0 : void trace(JSTracer* trc) { /* PCKey is weak. */ }
241 0 : bool needsSweep() { return IsAboutToBeFinalized(&script); }
242 : };
243 :
244 : public:
245 51824 : struct LocationValue {
246 12463 : LocationValue() : source(nullptr), line(0), column(0) { }
247 722 : LocationValue(JSAtom* source, size_t line, uint32_t column)
248 722 : : source(source), line(line), column(column)
249 722 : { }
250 :
251 0 : void trace(JSTracer* trc) {
252 0 : TraceNullableEdge(trc, &source, "SavedStacks::LocationValue::source");
253 0 : }
254 :
255 0 : bool needsSweep() {
256 : // LocationValue is always held strongly, but in a weak map.
257 : // Assert that it has been marked already, but allow it to be
258 : // ejected from the map when the key dies.
259 0 : MOZ_ASSERT(source);
260 0 : MOZ_ASSERT(!IsAboutToBeFinalized(&source));
261 0 : return true;
262 : }
263 :
264 : HeapPtr<JSAtom*> source;
265 : size_t line;
266 : uint32_t column;
267 : };
268 :
269 : private:
270 : struct PCLocationHasher : public DefaultHasher<PCKey> {
271 : using ScriptPtrHasher = DefaultHasher<JSScript*>;
272 : using BytecodePtrHasher = DefaultHasher<jsbytecode*>;
273 :
274 12463 : static HashNumber hash(const PCKey& key) {
275 12463 : return mozilla::AddToHash(ScriptPtrHasher::hash(key.script),
276 12463 : BytecodePtrHasher::hash(key.pc));
277 : }
278 :
279 11741 : static bool match(const PCKey& l, const PCKey& k) {
280 23482 : return ScriptPtrHasher::match(l.script, k.script) &&
281 23482 : BytecodePtrHasher::match(l.pc, k.pc);
282 : }
283 : };
284 :
285 : // We eagerly Atomize the script source stored in LocationValue because
286 : // wasm does not always have a JSScript and the source might not be
287 : // available when we need it later. However, since the JSScript does not
288 : // actually hold this atom, we have to trace it strongly to keep it alive.
289 : // Thus, it takes two GC passes to fully clean up this table: the first GC
290 : // removes the dead script; the second will clear out the source atom since
291 : // it is no longer held by the table.
292 : using PCLocationMap = GCHashMap<PCKey, LocationValue, PCLocationHasher, SystemAllocPolicy>;
293 : PCLocationMap pcLocationMap;
294 :
295 : MOZ_MUST_USE bool getLocation(JSContext* cx, const FrameIter& iter,
296 : MutableHandle<LocationValue> locationp);
297 : };
298 :
299 : template <typename Wrapper>
300 24926 : struct WrappedPtrOperations<SavedStacks::LocationValue, Wrapper>
301 : {
302 24926 : JSAtom* source() const { return loc().source; }
303 12463 : size_t line() const { return loc().line; }
304 12463 : uint32_t column() const { return loc().column; }
305 :
306 : private:
307 49852 : const SavedStacks::LocationValue& loc() const {
308 49852 : return static_cast<const Wrapper*>(this)->get();
309 : }
310 : };
311 :
312 : template <typename Wrapper>
313 24926 : struct MutableWrappedPtrOperations<SavedStacks::LocationValue, Wrapper>
314 : : public WrappedPtrOperations<SavedStacks::LocationValue, Wrapper>
315 : {
316 0 : void setSource(JSAtom* v) { loc().source = v; }
317 0 : void setLine(size_t v) { loc().line = v; }
318 0 : void setColumn(uint32_t v) { loc().column = v; }
319 :
320 : private:
321 0 : SavedStacks::LocationValue& loc() {
322 0 : return static_cast<Wrapper*>(this)->get();
323 : }
324 : };
325 :
326 : UTF8CharsZ
327 : BuildUTF8StackString(JSContext* cx, HandleObject stack);
328 :
329 : } /* namespace js */
330 :
331 : #endif /* vm_SavedStacks_h */
|