Bug 1194424 - Part 3: Implement a concrete JS::ubi::StackFrame specialization backed by a frame deserialized from a core dump; r=sfink
authorNick Fitzgerald <fitzgen@gmail.com>
Mon, 24 Aug 2015 09:29:44 -0700
changeset 259062 01d41f4336916afa54849ff4e1a3c2ee071304fb
parent 259061 97dfefc2b1596c1dddb60f3d695977eb523579ec
child 259063 fbca64555f063b0dfcc4217208ddaac4b0783770
push id29268
push userryanvm@gmail.com
push dateTue, 25 Aug 2015 00:37:23 +0000
treeherdermozilla-central@08015770c9d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1194424
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1194424 - Part 3: Implement a concrete JS::ubi::StackFrame specialization backed by a frame deserialized from a core dump; r=sfink
js/public/Principals.h
js/public/UbiNode.h
js/src/jsapi.cpp
js/src/vm/SavedFrame.h
js/src/vm/SavedStacks.cpp
js/src/vm/SavedStacks.h
toolkit/devtools/server/DeserializedNode.cpp
toolkit/devtools/server/DeserializedNode.h
toolkit/devtools/server/HeapSnapshot.h
--- a/js/public/Principals.h
+++ b/js/public/Principals.h
@@ -81,17 +81,17 @@ JS_GetSecurityCallbacks(JSRuntime* rt);
  *
  * This principals is not held (via JS_HoldPrincipals/JS_DropPrincipals) since
  * there is no available JSContext. Instead, the caller must ensure that the
  * given principals stays valid for as long as 'rt' may point to it. If the
  * principals would be destroyed before 'rt', JS_SetTrustedPrincipals must be
  * called again, passing nullptr for 'prin'.
  */
 extern JS_PUBLIC_API(void)
-JS_SetTrustedPrincipals(JSRuntime* rt, const JSPrincipals* prin);
+JS_SetTrustedPrincipals(JSRuntime* rt, JSPrincipals* prin);
 
 typedef void
 (* JSDestroyPrincipalsOp)(JSPrincipals* principals);
 
 /*
  * Initialize the callback that is called to destroy JSPrincipals instance
  * when its reference counter drops to zero. The initialization can be done
  * only once per JS runtime.
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -426,16 +426,19 @@ class ConcreteStackFrame<void> : public 
     uint32_t column() const override { MOZ_CRASH("null JS::ubi::StackFrame"); }
     AtomOrTwoByteChars source() const override { MOZ_CRASH("null JS::ubi::StackFrame"); }
     AtomOrTwoByteChars functionDisplayName() const override { MOZ_CRASH("null JS::ubi::StackFrame"); }
     StackFrame parent() const override { MOZ_CRASH("null JS::ubi::StackFrame"); }
     bool isSystem() const override { MOZ_CRASH("null JS::ubi::StackFrame"); }
     bool isSelfHosted() const override { MOZ_CRASH("null JS::ubi::StackFrame"); }
 };
 
+bool ConstructSavedFrameStackSlow(JSContext* cx, JS::ubi::StackFrame& frame,
+                                  MutableHandleObject outSavedFrameStack);
+
 
 /*** ubi::Node ************************************************************************************/
 
 // The base class implemented by each ubi::Node referent type. Subclasses must
 // not add data members to this class.
 class Base {
     friend class Node;
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3253,17 +3253,17 @@ JS_SetSecurityCallbacks(JSRuntime* rt, c
 
 JS_PUBLIC_API(const JSSecurityCallbacks*)
 JS_GetSecurityCallbacks(JSRuntime* rt)
 {
     return (rt->securityCallbacks != &NullSecurityCallbacks) ? rt->securityCallbacks : nullptr;
 }
 
 JS_PUBLIC_API(void)
-JS_SetTrustedPrincipals(JSRuntime* rt, const JSPrincipals* prin)
+JS_SetTrustedPrincipals(JSRuntime* rt, JSPrincipals* prin)
 {
     rt->setTrustedPrincipals(prin);
 }
 
 extern JS_PUBLIC_API(void)
 JS_InitDestroyPrincipalsCallback(JSRuntime* rt, JSDestroyPrincipalsOp destroyPrincipals)
 {
     MOZ_ASSERT(destroyPrincipals);
--- a/js/src/vm/SavedFrame.h
+++ b/js/src/vm/SavedFrame.h
@@ -104,16 +104,46 @@ struct SavedFrame::HashPolicy
     typedef ReadBarriered<SavedFrame*> Key;
     static void rekey(Key& key, const Key& newKey);
 };
 
 // Assert that if the given object is not null, that it must be either a
 // SavedFrame object or wrapper (Xray or CCW) around a SavedFrame object.
 inline void AssertObjectIsSavedFrameOrWrapper(JSContext* cx, HandleObject stack);
 
+// When we reconstruct a SavedFrame stack from a JS::ubi::StackFrame, we may not
+// have access to the principals that the original stack was captured
+// with. Instead, we use these two singleton principals based on whether
+// JS::ubi::StackFrame::isSystem or not. These singletons should never be passed
+// to the subsumes callback, and should be special cased with a shortcut before
+// that.
+struct ReconstructedSavedFramePrincipals : public JSPrincipals
+{
+    explicit ReconstructedSavedFramePrincipals()
+        : JSPrincipals()
+    {
+        MOZ_ASSERT(is(this));
+        this->refcount = 1;
+    }
+
+    static ReconstructedSavedFramePrincipals IsSystem;
+    static ReconstructedSavedFramePrincipals IsNotSystem;
+
+    // Return true if the given JSPrincipals* points to one of the
+    // ReconstructedSavedFramePrincipals singletons, false otherwise.
+    static bool is(JSPrincipals* p) { return p == &IsSystem || p == &IsNotSystem;}
+
+    // Get the appropriate ReconstructedSavedFramePrincipals singleton for the
+    // given JS::ubi::StackFrame that is being reconstructed as a SavedFrame
+    // stack.
+    static JSPrincipals* getSingleton(JS::ubi::StackFrame& f) {
+        return f.isSystem() ? &IsSystem : &IsNotSystem;
+    }
+};
+
 } // namespace js
 
 namespace JS {
 namespace ubi {
 
 using js::SavedFrame;
 
 // A concrete JS::ubi::StackFrame that is backed by a live SavedFrame object.
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -133,18 +133,18 @@ LiveSavedFrameCache::find(JSContext* cx,
     // Everything after the cached SavedFrame are stale younger frames we have
     // since popped.
     frames->shrinkBy(frames->length() - numberStillValid);
 }
 
 struct SavedFrame::Lookup {
     Lookup(JSAtom* source, uint32_t line, uint32_t column,
            JSAtom* functionDisplayName, JSAtom* asyncCause, SavedFrame* parent,
-           JSPrincipals* principals, Maybe<LiveSavedFrameCache::FramePtr> framePtr, jsbytecode* pc,
-           Activation* activation)
+           JSPrincipals* principals, Maybe<LiveSavedFrameCache::FramePtr> framePtr = Nothing(),
+           jsbytecode* pc = nullptr, Activation* activation = nullptr)
       : source(source),
         line(line),
         column(column),
         functionDisplayName(functionDisplayName),
         asyncCause(asyncCause),
         parent(parent),
         principals(principals),
         framePtr(framePtr),
@@ -440,34 +440,50 @@ SavedFrame::isSelfHosted()
 /* static */ bool
 SavedFrame::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
                          "SavedFrame");
     return false;
 }
 
+static bool
+SavedFrameSubsumedByCaller(JSContext* cx, HandleSavedFrame frame)
+{
+    auto subsumes = cx->runtime()->securityCallbacks->subsumes;
+    if (!subsumes)
+        return true;
+
+    auto currentCompartmentPrincipals = cx->compartment()->principals();
+    MOZ_ASSERT(!ReconstructedSavedFramePrincipals::is(currentCompartmentPrincipals));
+
+    auto framePrincipals = frame->getPrincipals();
+
+    // Handle SavedFrames that have been reconstructed from stacks in a heap
+    // snapshot.
+    if (framePrincipals == &ReconstructedSavedFramePrincipals::IsSystem)
+        return cx->runningWithTrustedPrincipals();
+    if (framePrincipals == &ReconstructedSavedFramePrincipals::IsNotSystem)
+        return true;
+
+    return subsumes(currentCompartmentPrincipals, framePrincipals);
+}
+
 // Return the first SavedFrame in the chain that starts with |frame| whose
 // principals are subsumed by |principals|, according to |subsumes|. If there is
 // no such frame, return nullptr. |skippedAsync| is set to true if any of the
 // skipped frames had the |asyncCause| property set, otherwise it is explicitly
 // set to false.
 static SavedFrame*
 GetFirstSubsumedFrame(JSContext* cx, HandleSavedFrame frame, bool& skippedAsync)
 {
     skippedAsync = false;
 
-    JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes;
-    if (!subsumes)
-        return frame;
-
-    JSPrincipals* principals = cx->compartment()->principals();
-
     RootedSavedFrame rootedFrame(cx, frame);
-    while (rootedFrame && !subsumes(principals, rootedFrame->getPrincipals())) {
+    while (rootedFrame && !SavedFrameSubsumedByCaller(cx, rootedFrame)) {
         if (rootedFrame->getAsyncCause())
             skippedAsync = true;
         rootedFrame = rootedFrame->getParent();
     }
 
     return rootedFrame;
 }
 
@@ -732,22 +748,19 @@ BuildStackString(JSContext* cx, HandleOb
         AutoMaybeEnterFrameCompartment ac(cx, stack);
         bool skippedAsync;
         js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, skippedAsync));
         if (!frame) {
             stringp.set(cx->runtime()->emptyString);
             return true;
         }
 
-        DebugOnly<JSSubsumesOp> subsumes = cx->runtime()->securityCallbacks->subsumes;
-        DebugOnly<JSPrincipals*> principals = cx->compartment()->principals();
-
         js::RootedSavedFrame parent(cx);
         do {
-            MOZ_ASSERT_IF(subsumes, (*subsumes)(principals, frame->getPrincipals()));
+            MOZ_ASSERT(SavedFrameSubsumedByCaller(cx, frame));
 
             if (!frame->isSelfHosted()) {
                 RootedString asyncCause(cx, frame->getAsyncCause());
                 if (!asyncCause && skippedAsync)
                     asyncCause.set(cx->names().Async);
 
                 js::RootedAtom name(cx, frame->getFunctionDisplayName());
                 if ((asyncCause && (!sb.append(asyncCause) || !sb.append('*')))
@@ -1370,35 +1383,115 @@ CompartmentChecker::check(SavedStacks* s
     if (&compartment->savedStacks() != stacks) {
         printf("*** Compartment SavedStacks mismatch: %p vs. %p\n",
                (void*) &compartment->savedStacks(), stacks);
         MOZ_CRASH();
     }
 }
 #endif /* JS_CRASH_DIAGNOSTICS */
 
+/* static */ ReconstructedSavedFramePrincipals ReconstructedSavedFramePrincipals::IsSystem;
+/* static */ ReconstructedSavedFramePrincipals ReconstructedSavedFramePrincipals::IsNotSystem;
+
 } /* namespace js */
 
 namespace JS {
 namespace ubi {
 
 bool
 ConcreteStackFrame<SavedFrame>::isSystem() const
 {
     auto trustedPrincipals = get().runtimeFromAnyThread()->trustedPrincipals();
-    return get().getPrincipals() == trustedPrincipals;
+    return get().getPrincipals() == trustedPrincipals ||
+           get().getPrincipals() == &ReconstructedSavedFramePrincipals::IsSystem;
 }
 
 bool
 ConcreteStackFrame<SavedFrame>::constructSavedFrameStack(JSContext* cx,
                                                          MutableHandleObject outSavedFrameStack)
     const
 {
     outSavedFrameStack.set(&get());
     if (!cx->compartment()->wrap(cx, outSavedFrameStack)) {
         outSavedFrameStack.set(nullptr);
         return false;
     }
     return true;
 }
 
+// A `mozilla::Variant` matcher that converts the inner value of a
+// `JS::ubi::AtomOrTwoByteChars` string to a `JSAtom*`.
+struct MOZ_STACK_CLASS AtomizingMatcher
+{
+    using ReturnType = JSAtom*;
+
+    JSContext* cx;
+    size_t     length;
+
+    explicit AtomizingMatcher(JSContext* cx, size_t length)
+      : cx(cx)
+      , length(length)
+    { }
+
+    JSAtom* match(JSAtom* atom) {
+        MOZ_ASSERT(atom);
+        return atom;
+    }
+
+    JSAtom* match(const char16_t* chars) {
+        MOZ_ASSERT(chars);
+        return AtomizeChars(cx, chars, length);
+    }
+};
+
+bool ConstructSavedFrameStackSlow(JSContext* cx, JS::ubi::StackFrame& frame,
+                                  MutableHandleObject outSavedFrameStack)
+{
+    SavedFrame::AutoLookupVector stackChain(cx);
+    Rooted<JS::ubi::StackFrame> ubiFrame(cx, frame);
+
+    while (ubiFrame.get()) {
+        // Convert the source and functionDisplayName strings to atoms.
+
+        RootedAtom source(cx);
+        AtomizingMatcher atomizer(cx, ubiFrame.get().sourceLength());
+        source = ubiFrame.get().source().match(atomizer);
+        if (!source)
+            return false;
+
+        RootedAtom functionDisplayName(cx);
+        auto nameLength = ubiFrame.get().functionDisplayNameLength();
+        if (nameLength > 0) {
+            AtomizingMatcher atomizer(cx, nameLength);
+            functionDisplayName = ubiFrame.get().functionDisplayName().match(atomizer);
+            if (!functionDisplayName)
+                return false;
+        }
+
+        auto principals = js::ReconstructedSavedFramePrincipals::getSingleton(ubiFrame.get());
+
+        if (!stackChain->emplaceBack(source, ubiFrame.get().line(), ubiFrame.get().column(),
+                                     functionDisplayName, /* asyncCause */ nullptr,
+                                     /* parent */ nullptr, principals))
+        {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+
+        ubiFrame = ubiFrame.get().parent();
+    }
+
+    RootedSavedFrame parentFrame(cx);
+    for (size_t i = stackChain->length(); i != 0; i--) {
+        SavedFrame::HandleLookup lookup = stackChain[i-1];
+        lookup->parent = parentFrame;
+        parentFrame = cx->compartment()->savedStacks().getOrCreateSavedFrame(cx, lookup);
+        if (!parentFrame)
+            return false;
+    }
+
+    outSavedFrameStack.set(parentFrame);
+    return true;
+}
+
+
 } // namespace ubi
 } // namespace JS
--- a/js/src/vm/SavedStacks.h
+++ b/js/src/vm/SavedStacks.h
@@ -142,16 +142,19 @@ namespace js {
 // In the case of z, the `SavedFrame` accessors are called with the `SavedFrame`
 // object in the `this` value, and the content compartment as the cx's current
 // compartment. Similar to the case of y, only the B and C frames are exposed
 // because the cx's current compartment's principals do not subsume A's captured
 // principals.
 
 class SavedStacks {
     friend JSObject* SavedStacksMetadataCallback(JSContext* cx, JSObject* target);
+    friend bool JS::ubi::ConstructSavedFrameStackSlow(JSContext* cx,
+                                                      JS::ubi::StackFrame& ubiFrame,
+                                                      MutableHandleObject outSavedFrameStack);
 
   public:
     SavedStacks()
       : frames(),
         allocationSamplingProbability(1.0),
         allocationSkipCount(0),
         rngState(0),
         creatingSavedFrame(false)
--- a/toolkit/devtools/server/DeserializedNode.cpp
+++ b/toolkit/devtools/server/DeserializedNode.cpp
@@ -92,16 +92,27 @@ DeserializedNode::getEdgeReferent(const 
   // value might change its hash, rendering it unfindable in the set.
   // Unfortunately, the `ubi::Node` constructor requires a non-const pointer to
   // its referent.  However, the only aspect of a `DeserializedNode` we hash on
   // is its id, which can't be changed via `ubi::Node`, so this cast can't cause
   // the trouble `HashSet` is concerned a non-const reference would cause.
   return JS::ubi::Node(const_cast<DeserializedNode*>(&*ptr));
 }
 
+JS::ubi::StackFrame
+DeserializedStackFrame::getParentStackFrame() const
+{
+  MOZ_ASSERT(parent.isSome());
+  auto ptr = owner->frames.lookup(parent.ref());
+  MOZ_ASSERT(ptr);
+  // See above comment in DeserializedNode::getEdgeReferent about why this
+  // const_cast is needed and safe.
+  return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr));
+}
+
 } // namespace devtools
 } // namespace mozilla
 
 namespace JS {
 namespace ubi {
 
 using mozilla::devtools::DeserializedEdge;
 
@@ -175,10 +186,25 @@ Concrete<DeserializedNode>::edges(JSCont
     js_new<DeserializedEdgeRange>(cx));
 
   if (!range || !range->init(get()))
     return nullptr;
 
   return UniquePtr<EdgeRange>(range.release());
 }
 
+StackFrame
+ConcreteStackFrame<DeserializedStackFrame>::parent() const
+{
+  return get().parent.isNothing() ? StackFrame() : get().getParentStackFrame();
+}
+
+bool
+ConcreteStackFrame<DeserializedStackFrame>::constructSavedFrameStack(
+  JSContext* cx,
+  MutableHandleObject outSavedFrameStack) const
+{
+  StackFrame f(&get());
+  return ConstructSavedFrameStackSlow(cx, f, outSavedFrameStack);
+}
+
 } // namespace ubi
 } // namespace JS
--- a/toolkit/devtools/server/DeserializedNode.h
+++ b/toolkit/devtools/server/DeserializedNode.h
@@ -160,16 +160,18 @@ struct DeserializedStackFrame {
     , functionDisplayName(functionDisplayName)
     , isSystem(isSystem)
     , isSelfHosted(isSelfHosted)
     , owner(&owner)
   {
     MOZ_ASSERT(source);
   }
 
+  JS::ubi::StackFrame getParentStackFrame() const;
+
   struct HashPolicy;
 };
 
 struct DeserializedStackFrame::HashPolicy {
   using Lookup = StackFrameId;
 
   static js::HashNumber hash(const Lookup& lookup) {
     return hashIdDerivedFromPtr(lookup);
@@ -182,16 +184,17 @@ struct DeserializedStackFrame::HashPolic
 
 } // namespace devtools
 } // namespace mozilla
 
 namespace JS {
 namespace ubi {
 
 using mozilla::devtools::DeserializedNode;
+using mozilla::devtools::DeserializedStackFrame;
 using mozilla::UniquePtr;
 
 template<>
 struct Concrete<DeserializedNode> : public Base
 {
 protected:
   explicit Concrete(DeserializedNode* ptr) : Base(ptr) { }
   DeserializedNode& get() const {
@@ -210,12 +213,48 @@ public:
   const char16_t* typeName() const override;
   size_t size(mozilla::MallocSizeOf mallocSizeof) const override;
 
   // We ignore the `bool wantNames` parameter because we can't control whether
   // the core dump was serialized with edge names or not.
   UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override;
 };
 
+template<>
+class ConcreteStackFrame<DeserializedStackFrame> : public BaseStackFrame
+{
+protected:
+  explicit ConcreteStackFrame(DeserializedStackFrame* ptr)
+    : BaseStackFrame(ptr)
+  { }
+
+  DeserializedStackFrame& get() const {
+    return *static_cast<DeserializedStackFrame*>(ptr);
+  }
+
+public:
+  static void construct(void* storage, DeserializedStackFrame* ptr) {
+    new (storage) ConcreteStackFrame(ptr);
+  }
+
+  uintptr_t identifier() const override { return get().id; }
+  uint32_t line() const override { return get().line; }
+  uint32_t column() const override { return get().column; }
+  bool isSystem() const override { return get().isSystem; }
+  bool isSelfHosted() const override { return get().isSelfHosted; }
+  void trace(JSTracer* trc) override { }
+  AtomOrTwoByteChars source() const override {
+    return AtomOrTwoByteChars(get().source);
+  }
+  AtomOrTwoByteChars functionDisplayName() const override {
+    return AtomOrTwoByteChars(get().functionDisplayName);
+  }
+
+  StackFrame parent() const override;
+  bool constructSavedFrameStack(JSContext* cx,
+                                MutableHandleObject outSavedFrameStack)
+    const override;
+};
+
 } // namespace ubi
 } // namespace JS
 
 #endif // mozilla_devtools_DeserializedNode__
--- a/toolkit/devtools/server/HeapSnapshot.h
+++ b/toolkit/devtools/server/HeapSnapshot.h
@@ -57,16 +57,17 @@ struct UniqueStringHashPolicy {
     return memcmp(existing.get(), lookup.str, lookup.length * sizeof(char16_t)) == 0;
   }
 };
 
 class HeapSnapshot final : public nsISupports
                          , public nsWrapperCache
 {
   friend struct DeserializedNode;
+  friend struct DeserializedStackFrame;
 
   explicit HeapSnapshot(JSContext* cx, nsISupports* aParent)
     : timestamp(Nothing())
     , rootId(0)
     , nodes(cx)
     , frames(cx)
     , strings(cx)
     , mParent(aParent)