Bug 1216819 - Allow JSAPI SavedFrame accessors to skip past self-hosted frames. r=bz
authorNick Fitzgerald <fitzgen@gmail.com>
Wed, 21 Oct 2015 10:39:00 -0400
changeset 304096 dbfe8e642f5645790d4fdba8532bd9132171d86c
parent 304095 b08adc2a4a9273b0a08b823ff99e32c48e9395d0
child 304097 46cc647fabf317628228ebe920251b703a2b5333
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1216819
milestone44.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 1216819 - Allow JSAPI SavedFrame accessors to skip past self-hosted frames. r=bz
dom/bindings/Exceptions.cpp
js/src/jsapi-tests/testSavedStacks.cpp
js/src/jsapi.h
js/src/jsfriendapi.h
js/src/vm/SavedStacks.cpp
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -100,17 +100,17 @@ Throw(JSContext* aCx, nsresult aRv, cons
     // Don't clobber the existing exception.
     return false;
   }
 
   CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
   nsCOMPtr<nsIException> existingException = runtime->GetPendingException();
   if (existingException) {
     nsresult nr;
-    if (NS_SUCCEEDED(existingException->GetResult(&nr)) && 
+    if (NS_SUCCEEDED(existingException->GetResult(&nr)) &&
         aRv == nr) {
       // Reuse the existing exception.
 
       // Clear pending exception
       runtime->SetPendingException(nullptr);
 
       if (!ThrowExceptionObject(aCx, existingException)) {
         // If we weren't able to throw an exception we're
@@ -382,17 +382,18 @@ NS_IMETHODIMP JSStackFrame::GetLanguageN
 // @argument [out] aCanCache whether the value can get cached.
 // @argument [out] aUseCachedValue if true, just use the cached value.
 // @argument [out] aValue the value we got from the stack.
 template<typename ReturnType, typename GetterOutParamType>
 static void
 GetValueIfNotCached(JSContext* aCx, JSObject* aStack,
                     JS::SavedFrameResult (*aPropGetter)(JSContext*,
                                                         JS::Handle<JSObject*>,
-                                                        GetterOutParamType),
+                                                        GetterOutParamType,
+                                                        JS::SavedFrameSelfHosted),
                     bool aIsCached, bool* aCanCache, bool* aUseCachedValue,
                     ReturnType aValue)
 {
   MOZ_ASSERT(aStack);
 
   JS::Rooted<JSObject*> stack(aCx, aStack);
   // Allow caching if aCx and stack are same-compartment.  Otherwise take the
   // slow path.
@@ -400,17 +401,17 @@ GetValueIfNotCached(JSContext* aCx, JSOb
   if (*aCanCache && aIsCached) {
     *aUseCachedValue = true;
     return;
   }
 
   *aUseCachedValue = false;
   JS::ExposeObjectToActiveJS(stack);
 
-  aPropGetter(aCx, stack, aValue);
+  aPropGetter(aCx, stack, aValue, JS::SavedFrameSelfHosted::Exclude);
 }
 
 NS_IMETHODIMP JSStackFrame::GetFilename(nsAString& aFilename)
 {
   if (!mStack) {
     aFilename.Truncate();
     return NS_OK;
   }
--- a/js/src/jsapi-tests/testSavedStacks.cpp
+++ b/js/src/jsapi-tests/testSavedStacks.cpp
@@ -106,8 +106,99 @@ BEGIN_TEST(testSavedStacks_RangeBasedFor
         CHECK(frame == rf);
         rf = rf->getParent();
     }
     CHECK(rf == nullptr);
 
     return true;
 }
 END_TEST(testSavedStacks_RangeBasedForLoops)
+
+BEGIN_TEST(testSavedStacks_selfHostedFrames)
+{
+    CHECK(js::DefineTestingFunctions(cx, global, false, false));
+
+    JS::RootedValue val(cx);
+    //             0         1         2         3
+    //             0123456789012345678901234567890123456789
+    CHECK(evaluate("(function one() {                      \n"  // 1
+                   "  try {                                \n"  // 2
+                   "    [1].map(function two() {           \n"  // 3
+                   "      throw saveStack();               \n"  // 4
+                   "    });                                \n"  // 5
+                   "  } catch (stack) {                    \n"  // 6
+                   "    return stack;                      \n"  // 7
+                   "  }                                    \n"  // 8
+                   "}())                                   \n", // 9
+                   "filename.js",
+                   1,
+                   &val));
+
+    CHECK(val.isObject());
+    JS::RootedObject obj(cx, &val.toObject());
+
+    CHECK(obj->is<js::SavedFrame>());
+    JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
+
+    JS::Rooted<js::SavedFrame*> selfHostedFrame(cx, savedFrame->getParent());
+    CHECK(selfHostedFrame->isSelfHosted());
+
+    // Source
+    JS::RootedString str(cx);
+    JS::SavedFrameResult result = JS::GetSavedFrameSource(cx, selfHostedFrame, &str,
+                                                          JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    JSLinearString* lin = str->ensureLinear(cx);
+    CHECK(lin);
+    CHECK(js::StringEqualsAscii(lin, "filename.js"));
+
+    // Source, including self-hosted frames
+    result = JS::GetSavedFrameSource(cx, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Include);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    lin = str->ensureLinear(cx);
+    CHECK(lin);
+    CHECK(js::StringEqualsAscii(lin, "self-hosted"));
+
+    // Line
+    uint32_t line = 123;
+    result = JS::GetSavedFrameLine(cx, selfHostedFrame, &line, JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    CHECK_EQUAL(line, 3U);
+
+    // Column
+    uint32_t column = 123;
+    result = JS::GetSavedFrameColumn(cx, selfHostedFrame, &column,
+                                     JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    CHECK_EQUAL(column, 5U);
+
+    // Function display name
+    result = JS::GetSavedFrameFunctionDisplayName(cx, selfHostedFrame, &str,
+                                                  JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    lin = str->ensureLinear(cx);
+    CHECK(lin);
+    CHECK(js::StringEqualsAscii(lin, "one"));
+
+    // Parent
+    JS::RootedObject parent(cx);
+    result = JS::GetSavedFrameParent(cx, savedFrame, &parent, JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    // JS::GetSavedFrameParent does this super funky and potentially unexpected
+    // thing where it doesn't return the next subsumed parent but any next
+    // parent. This so that callers can still get the "asyncParent" property
+    // which is only on the first frame of the async parent stack and that frame
+    // might not be subsumed by the caller. It is expected that callers will
+    // still interact with the frame through the JSAPI accessors, so this should
+    // be safe and should not leak privileged info to unprivileged
+    // callers. However, because of that, we don't test that the parent we get
+    // here is the selfHostedFrame's parent (because, as just explained, it
+    // isn't) and instead check that asking for the source property gives us the
+    // expected value.
+    result = JS::GetSavedFrameSource(cx, parent, &str, JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    lin = str->ensureLinear(cx);
+    CHECK(lin);
+    CHECK(js::StringEqualsAscii(lin, "filename.js"));
+
+    return true;
+}
+END_TEST(testSavedStacks_selfHostedFrames)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5545,79 +5545,95 @@ CaptureCurrentStack(JSContext* cx, Mutab
  * Each of these functions will find the first SavedFrame object in the chain
  * whose underlying stack frame principals are subsumed by the cx's current
  * compartment's principals, and operate on that SavedFrame object. This
  * prevents leaking information about privileged frames to un-privileged
  * callers. As a result, the SavedFrame in parameters do _NOT_ need to be in the
  * same compartment as the cx, and the various out parameters are _NOT_
  * guaranteed to be in the same compartment as cx.
  *
+ * You may consider or skip over self-hosted frames by passing
+ * `SavedFrameSelfHosted::Include` or `SavedFrameSelfHosted::Exclude`
+ * respectively.
+ *
  * Additionally, it may be the case that there is no such SavedFrame object
  * whose captured frame's principals are subsumed by the caller's compartment's
  * principals! If the `HandleObject savedFrame` argument is null, or the
  * caller's principals do not subsume any of the chained SavedFrame object's
  * principals, `SavedFrameResult::AccessDenied` is returned and a (hopefully)
  * sane default value is chosen for the out param.
  *
  * See also `js/src/doc/SavedFrame/SavedFrame.md`.
  */
 
 enum class SavedFrameResult {
     Ok,
     AccessDenied
 };
 
+enum class SavedFrameSelfHosted {
+    Include,
+    Exclude
+};
+
 /**
  * Given a SavedFrame JSObject, get its source property. Defaults to the empty
  * string.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep);
+GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep,
+                    SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its line property. Defaults to 0.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep);
+GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep,
+                  SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its column property. Defaults to 0.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp);
+GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp,
+                    SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its functionDisplayName string, or nullptr
  * if SpiderMonkey was unable to infer a name for the captured frame's
  * function. Defaults to nullptr.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep);
+GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep,
+                                 SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its asyncCause string. Defaults to nullptr.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep);
+GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep,
+                        SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its asyncParent SavedFrame object or nullptr
  * if there is no asyncParent. The `asyncParentp` out parameter is _NOT_
  * guaranteed to be in the cx's compartment. Defaults to nullptr.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp);
+GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp,
+                SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its parent SavedFrame object or nullptr if
  * it is the oldest frame in the stack. The `parentp` out parameter is _NOT_
  * guaranteed to be in the cx's compartment. Defaults to nullptr.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp);
+GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp,
+                    SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject stack, stringify it in the same format as
  * Error.prototype.stack. The stringified stack out parameter is placed in the
  * cx's compartment. Defaults to the empty string.
  *
  * The same notes above about SavedFrame accessors applies here as well: cx
  * doesn't need to be in stack's compartment, and stack can be null, a
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2843,17 +2843,17 @@ GetNearestEnclosingWithScopeObjectForFun
  * Get the first SavedFrame object in this SavedFrame stack whose principals are
  * subsumed by the cx's principals. If there is no such frame, return nullptr.
  *
  * Do NOT pass a non-SavedFrame object here.
  *
  * The savedFrame and cx do not need to be in the same compartment.
  */
 extern JS_FRIEND_API(JSObject*)
-GetFirstSubsumedSavedFrame(JSContext* cx, JS::HandleObject savedFrame);
+GetFirstSubsumedSavedFrame(JSContext* cx, JS::HandleObject savedFrame, JS::SavedFrameSelfHosted selfHosted);
 
 extern JS_FRIEND_API(bool)
 ReportIsNotFunction(JSContext* cx, JS::HandleValue v);
 
 extern JS_FRIEND_API(JSObject*)
 ConvertArgsToArray(JSContext* cx, const JS::CallArgs& args);
 
 } /* namespace js */
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -531,38 +531,47 @@ SavedFrameSubsumedByCaller(JSContext* cx
 }
 
 // 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)
+GetFirstSubsumedFrame(JSContext* cx, HandleSavedFrame frame, JS::SavedFrameSelfHosted selfHosted,
+                      bool& skippedAsync)
 {
     skippedAsync = false;
 
     RootedSavedFrame rootedFrame(cx, frame);
-    while (rootedFrame && !SavedFrameSubsumedByCaller(cx, rootedFrame)) {
+    while (rootedFrame) {
+        if ((selfHosted == JS::SavedFrameSelfHosted::Include || !rootedFrame->isSelfHosted()) &&
+            SavedFrameSubsumedByCaller(cx, rootedFrame))
+        {
+            return rootedFrame;
+        }
+
         if (rootedFrame->getAsyncCause())
             skippedAsync = true;
+
         rootedFrame = rootedFrame->getParent();
     }
 
-    return rootedFrame;
+    return nullptr;
 }
 
 JS_FRIEND_API(JSObject*)
-GetFirstSubsumedSavedFrame(JSContext* cx, HandleObject savedFrame)
+GetFirstSubsumedSavedFrame(JSContext* cx, HandleObject savedFrame,
+                           JS::SavedFrameSelfHosted selfHosted)
 {
     if (!savedFrame)
         return nullptr;
     bool skippedAsync;
     RootedSavedFrame frame(cx, &savedFrame->as<SavedFrame>());
-    return GetFirstSubsumedFrame(cx, frame, skippedAsync);
+    return GetFirstSubsumedFrame(cx, frame, selfHosted, skippedAsync);
 }
 
 /* static */ bool
 SavedFrame::checkThis(JSContext* cx, CallArgs& args, const char* fnName,
                       MutableHandleObject frame)
 {
     const Value& thisValue = args.thisv();
 
@@ -654,144 +663,157 @@ public:
  private:
     Maybe<JSAutoCompartment> ac_;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 } // namespace
 
 static inline js::SavedFrame*
-UnwrapSavedFrame(JSContext* cx, HandleObject obj, bool& skippedAsync)
+UnwrapSavedFrame(JSContext* cx, HandleObject obj, SavedFrameSelfHosted selfHosted,
+                 bool& skippedAsync)
 {
     if (!obj)
         return nullptr;
+
     RootedObject savedFrameObj(cx, CheckedUnwrap(obj));
-    MOZ_ASSERT(savedFrameObj);
+    if (!savedFrameObj)
+        return nullptr;
+
     MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*savedFrameObj));
     js::RootedSavedFrame frame(cx, &savedFrameObj->as<js::SavedFrame>());
-    return GetFirstSubsumedFrame(cx, frame, skippedAsync);
+    return GetFirstSubsumedFrame(cx, frame, selfHosted, skippedAsync);
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep)
+GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep,
+                    SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         sourcep.set(cx->runtime()->emptyString);
         return SavedFrameResult::AccessDenied;
     }
     sourcep.set(frame->getSource());
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep)
+GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep,
+                  SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     MOZ_ASSERT(linep);
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         *linep = 0;
         return SavedFrameResult::AccessDenied;
     }
     *linep = frame->getLine();
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp)
+GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp,
+                    SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     MOZ_ASSERT(columnp);
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         *columnp = 0;
         return SavedFrameResult::AccessDenied;
     }
     *columnp = frame->getColumn();
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep)
+GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep,
+                                 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         namep.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     namep.set(frame->getFunctionDisplayName());
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep)
+GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep,
+                        SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         asyncCausep.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     asyncCausep.set(frame->getAsyncCause());
     if (!asyncCausep && skippedAsync)
         asyncCausep.set(cx->names().Async);
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp)
+GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp,
+                         SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         asyncParentp.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     js::RootedSavedFrame parent(cx, frame->getParent());
 
     // The current value of |skippedAsync| is not interesting, because we are
     // interested in whether we would cross any async parents to get from here
     // to the first subsumed parent frame instead.
-    js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, skippedAsync));
+    js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, selfHosted,
+                                                                  skippedAsync));
 
     // Even if |parent| is not subsumed, we still want to return a pointer to it
     // rather than |subsumedParent| so it can pick up any |asyncCause| from the
     // inaccessible part of the chain.
     if (subsumedParent && (subsumedParent->getAsyncCause() || skippedAsync))
         asyncParentp.set(parent);
     else
         asyncParentp.set(nullptr);
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp)
+GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp,
+                    SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         parentp.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     js::RootedSavedFrame parent(cx, frame->getParent());
 
     // The current value of |skippedAsync| is not interesting, because we are
     // interested in whether we would cross any async parents to get from here
     // to the first subsumed parent frame instead.
-    js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, skippedAsync));
+    js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, selfHosted,
+                                                                  skippedAsync));
 
     // Even if |parent| is not subsumed, we still want to return a pointer to it
     // rather than |subsumedParent| so it can pick up any |asyncCause| from the
     // inaccessible part of the chain.
     if (subsumedParent && !(subsumedParent->getAsyncCause() || skippedAsync))
         parentp.set(parent);
     else
         parentp.set(nullptr);
@@ -805,49 +827,49 @@ BuildStackString(JSContext* cx, HandleOb
 
     // Enter a new block to constrain the scope of possibly entering the stack's
     // compartment. This ensures that when we finish the StringBuffer, we are
     // back in the cx's original compartment, and fulfill our contract with
     // callers to place the output string in the cx's current compartment.
     {
         AutoMaybeEnterFrameCompartment ac(cx, stack);
         bool skippedAsync;
-        js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, skippedAsync));
+        js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, SavedFrameSelfHosted::Exclude,
+                                                        skippedAsync));
         if (!frame) {
             stringp.set(cx->runtime()->emptyString);
             return true;
         }
 
         js::RootedSavedFrame parent(cx);
         do {
             MOZ_ASSERT(SavedFrameSubsumedByCaller(cx, frame));
+            MOZ_ASSERT(!frame->isSelfHosted());
 
-            if (!frame->isSelfHosted()) {
-                RootedString asyncCause(cx, frame->getAsyncCause());
-                if (!asyncCause && skippedAsync)
-                    asyncCause.set(cx->names().Async);
+            RootedString asyncCause(cx, frame->getAsyncCause());
+            if (!asyncCause && skippedAsync)
+                asyncCause.set(cx->names().Async);
 
-                js::RootedAtom name(cx, frame->getFunctionDisplayName());
-                if ((indent && !sb.appendN(' ', indent))
-                    || (asyncCause && (!sb.append(asyncCause) || !sb.append('*')))
-                    || (name && !sb.append(name))
-                    || !sb.append('@')
-                    || !sb.append(frame->getSource())
-                    || !sb.append(':')
-                    || !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
-                    || !sb.append(':')
-                    || !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
-                    || !sb.append('\n'))
-                {
-                    return false;
-                }
+            js::RootedAtom name(cx, frame->getFunctionDisplayName());
+            if ((indent && !sb.appendN(' ', indent))
+                || (asyncCause && (!sb.append(asyncCause) || !sb.append('*')))
+                || (name && !sb.append(name))
+                || !sb.append('@')
+                || !sb.append(frame->getSource())
+                || !sb.append(':')
+                || !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
+                || !sb.append(':')
+                || !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
+                || !sb.append('\n'))
+            {
+                return false;
             }
 
             parent = frame->getParent();
-            frame = js::GetFirstSubsumedFrame(cx, parent, skippedAsync);
+            frame = js::GetFirstSubsumedFrame(cx, parent, SavedFrameSelfHosted::Exclude, skippedAsync);
         } while (frame);
     }
 
     JSString* str = sb.finishString();
     if (!str)
         return false;
     assertSameCompartment(cx, str);
     stringp.set(str);