LCOV - code coverage report
Current view: top level - js/src/vm - SavedStacks.h (source / functions) Hit Total Coverage
Test: output.info Lines: 35 51 68.6 %
Date: 2017-07-14 16:53:18 Functions: 25 39 64.1 %
Legend: Lines: hit not hit

          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 */

Generated by: LCOV version 1.13