diff --git a/js/src/doc/SavedFrame/SavedFrame.md b/js/src/doc/SavedFrame/SavedFrame.md index 3dcafe998a83..7d06b445b806 100644 --- a/js/src/doc/SavedFrame/SavedFrame.md +++ b/js/src/doc/SavedFrame/SavedFrame.md @@ -59,10 +59,27 @@ as the content compartment sees it, waive the xray wrapper with : Either SpiderMonkey's inferred name for this stack frame's function, or `null`. -`parent` -: Either this stack frame's parent stack frame (the next older frame), or - `null` if this is the oldest frame in the captured stack. +`asyncCause` +: If this stack frame is the `asyncParent` of other stack frames, then this is + a string representing the type of asynchronous call by which this frame + invoked its children. For example, if this frame's children are calls to + handlers for a promise this frame created, this frame's `asyncCause` would + be `"Promise"`. If the asynchronous call was started in a descendant frame + to which the requester of the property does not have access, this will be + the generic string `"Async"`. If this is not an asynchronous call point, + this will be `null`. +`asyncParent` +: If this stack frame was called as a result of an asynchronous operation, for + example if the function referenced by this frame is a promise handler, this + property points to the stack frame responsible for the asynchronous call, + for example where the promise was created. If the frame responsible for the + call is not accessible to the caller, this points to the youngest accessible + ancestor of the real frame, if any. In all other cases, this is `null`. + +`parent` +: This stack frame's caller, or `null` if this is the oldest frame on the + stack. In this case, there might be an `asyncParent` instead. ## Function Properties of the `SavedFrame.prototype` Object diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 0dddad77eb77..6ed6ec97d631 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5105,6 +5105,20 @@ GetSavedFrameColumn(JSContext *cx, HandleObject savedFrame, uint32_t *columnp); extern JS_PUBLIC_API(SavedFrameResult) GetSavedFrameFunctionDisplayName(JSContext *cx, HandleObject savedFrame, MutableHandleString namep); +/* + * Given a SavedFrame JSObject, get its asyncCause string. Defaults to nullptr. + */ +extern JS_PUBLIC_API(SavedFrameResult) +GetSavedFrameAsyncCause(JSContext *cx, HandleObject savedFrame, MutableHandleString asyncCausep); + +/* + * 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); + /* * 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_ diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index d26b5cd18fa9..c7e95fe17e88 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -21,6 +21,7 @@ macro(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \ macro(ArrayType, ArrayType, "ArrayType") \ macro(ArrayValues, ArrayValues, "ArrayValues") \ + macro(Async, Async, "Async") \ macro(buffer, buffer, "buffer") \ macro(builder, builder, "builder") \ macro(byteLength, byteLength, "byteLength") \ diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index e5e9cf1de577..02ac5c47cbb6 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -39,12 +39,14 @@ using mozilla::Maybe; namespace js { struct SavedFrame::Lookup { - Lookup(JSAtom *source, uint32_t line, uint32_t column, JSAtom *functionDisplayName, - SavedFrame *parent, JSPrincipals *principals) + Lookup(JSAtom *source, uint32_t line, uint32_t column, + JSAtom *functionDisplayName, JSAtom *asyncCause, SavedFrame *parent, + JSPrincipals *principals) : source(source), line(line), column(column), functionDisplayName(functionDisplayName), + asyncCause(asyncCause), parent(parent), principals(principals) { @@ -55,6 +57,7 @@ struct SavedFrame::Lookup { uint32_t line; uint32_t column; JSAtom *functionDisplayName; + JSAtom *asyncCause; SavedFrame *parent; JSPrincipals *principals; @@ -64,6 +67,10 @@ struct SavedFrame::Lookup { gc::MarkStringUnbarriered(trc, &functionDisplayName, "SavedFrame::Lookup::functionDisplayName"); } + if (asyncCause) { + gc::MarkStringUnbarriered(trc, &asyncCause, + "SavedFrame::Lookup::asyncCause"); + } if (parent) { gc::MarkObjectUnbarriered(trc, &parent, "SavedFrame::Lookup::parent"); @@ -102,6 +109,7 @@ SavedFrame::HashPolicy::hash(const Lookup &lookup) lookup.column, lookup.source, lookup.functionDisplayName, + lookup.asyncCause, SavedFramePtrHasher::hash(lookup.parent), JSPrincipalsPtrHasher::hash(lookup.principals)); } @@ -129,6 +137,10 @@ SavedFrame::HashPolicy::match(SavedFrame *existing, const Lookup &lookup) if (functionDisplayName != lookup.functionDisplayName) return false; + JSAtom *asyncCause = existing->getAsyncCause(); + if (asyncCause != lookup.asyncCause) + return false; + return true; } @@ -197,6 +209,8 @@ SavedFrame::protoAccessors[] = { JS_PSG("line", SavedFrame::lineProperty, 0), JS_PSG("column", SavedFrame::columnProperty, 0), JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty, 0), + JS_PSG("asyncCause", SavedFrame::asyncCauseProperty, 0), + JS_PSG("asyncParent", SavedFrame::asyncParentProperty, 0), JS_PSG("parent", SavedFrame::parentProperty, 0), JS_PS_END }; @@ -243,6 +257,16 @@ SavedFrame::getFunctionDisplayName() return &s->asAtom(); } +JSAtom * +SavedFrame::getAsyncCause() +{ + const Value &v = getReservedSlot(JSSLOT_ASYNCCAUSE); + if (v.isNull()) + return nullptr; + JSString *s = v.toString(); + return &s->asAtom(); +} + SavedFrame * SavedFrame::getParent() { @@ -272,6 +296,10 @@ SavedFrame::initFromLookup(SavedFrame::HandleLookup lookup) lookup->functionDisplayName ? StringValue(lookup->functionDisplayName) : NullValue()); + setReservedSlot(JSSLOT_ASYNCCAUSE, + lookup->asyncCause + ? StringValue(lookup->asyncCause) + : NullValue()); setReservedSlot(JSSLOT_PARENT, ObjectOrNullValue(lookup->parent)); setReservedSlot(JSSLOT_PRIVATE_PARENT, PrivateValue(lookup->parent)); @@ -312,10 +340,14 @@ SavedFrame::construct(JSContext *cx, unsigned argc, Value *vp) // 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. +// 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) +GetFirstSubsumedFrame(JSContext *cx, HandleSavedFrame frame, bool &skippedAsync) { + skippedAsync = false; + JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; if (!subsumes) return frame; @@ -323,8 +355,11 @@ GetFirstSubsumedFrame(JSContext *cx, HandleSavedFrame frame) JSPrincipals *principals = cx->compartment()->principals; RootedSavedFrame rootedFrame(cx, frame); - while (rootedFrame && !subsumes(principals, rootedFrame->getPrincipals())) + while (rootedFrame && !subsumes(principals, rootedFrame->getPrincipals())) { + if (rootedFrame->getAsyncCause()) + skippedAsync = true; rootedFrame = rootedFrame->getParent(); + } return rootedFrame; } @@ -334,8 +369,9 @@ GetFirstSubsumedSavedFrame(JSContext *cx, HandleObject savedFrame) { if (!savedFrame) return nullptr; + bool skippedAsync; RootedSavedFrame frame(cx, &savedFrame->as()); - return GetFirstSubsumedFrame(cx, frame); + return GetFirstSubsumedFrame(cx, frame, skippedAsync); } /* static */ bool @@ -437,7 +473,7 @@ public: } // anonymous namespace static inline js::SavedFrame * -UnwrapSavedFrame(JSContext *cx, HandleObject obj) +UnwrapSavedFrame(JSContext *cx, HandleObject obj, bool &skippedAsync) { if (!obj) return nullptr; @@ -445,14 +481,15 @@ UnwrapSavedFrame(JSContext *cx, HandleObject obj) MOZ_ASSERT(savedFrameObj); MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*savedFrameObj)); js::RootedSavedFrame frame(cx, &savedFrameObj->as()); - return GetFirstSubsumedFrame(cx, frame); + return GetFirstSubsumedFrame(cx, frame, skippedAsync); } JS_PUBLIC_API(SavedFrameResult) GetSavedFrameSource(JSContext *cx, HandleObject savedFrame, MutableHandleString sourcep) { AutoMaybeEnterFrameCompartment ac(cx, savedFrame); - js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame)); + bool skippedAsync; + js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync)); if (!frame) { sourcep.set(cx->runtime()->emptyString); return SavedFrameResult::AccessDenied; @@ -466,7 +503,8 @@ GetSavedFrameLine(JSContext *cx, HandleObject savedFrame, uint32_t *linep) { MOZ_ASSERT(linep); AutoMaybeEnterFrameCompartment ac(cx, savedFrame); - js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame)); + bool skippedAsync; + js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync)); if (!frame) { *linep = 0; return SavedFrameResult::AccessDenied; @@ -480,7 +518,8 @@ GetSavedFrameColumn(JSContext *cx, HandleObject savedFrame, uint32_t *columnp) { MOZ_ASSERT(columnp); AutoMaybeEnterFrameCompartment ac(cx, savedFrame); - js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame)); + bool skippedAsync; + js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync)); if (!frame) { *columnp = 0; return SavedFrameResult::AccessDenied; @@ -493,7 +532,8 @@ JS_PUBLIC_API(SavedFrameResult) GetSavedFrameFunctionDisplayName(JSContext *cx, HandleObject savedFrame, MutableHandleString namep) { AutoMaybeEnterFrameCompartment ac(cx, savedFrame); - js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame)); + bool skippedAsync; + js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync)); if (!frame) { namep.set(nullptr); return SavedFrameResult::AccessDenied; @@ -502,17 +542,73 @@ GetSavedFrameFunctionDisplayName(JSContext *cx, HandleObject savedFrame, Mutable return SavedFrameResult::Ok; } +JS_PUBLIC_API(SavedFrameResult) +GetSavedFrameAsyncCause(JSContext *cx, HandleObject savedFrame, MutableHandleString asyncCausep) +{ + AutoMaybeEnterFrameCompartment ac(cx, savedFrame); + bool skippedAsync; + js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, 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) +{ + AutoMaybeEnterFrameCompartment ac(cx, savedFrame); + bool skippedAsync; + js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, 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)); + + // 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) { AutoMaybeEnterFrameCompartment ac(cx, savedFrame); - js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame)); + bool skippedAsync; + js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync)); if (!frame) { parentp.set(nullptr); return SavedFrameResult::AccessDenied; } js::RootedSavedFrame parent(cx, frame->getParent()); - parentp.set(js::GetFirstSubsumedFrame(cx, parent)); + + // 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)); + + // 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); return SavedFrameResult::Ok; } @@ -520,7 +616,8 @@ JS_PUBLIC_API(bool) StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString stringp) { AutoMaybeEnterFrameCompartment ac(cx, stack); - js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack)); + bool skippedAsync; + js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, skippedAsync)); if (!frame) { stringp.set(cx->runtime()->emptyString); return true; @@ -535,8 +632,13 @@ StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString MOZ_ASSERT_IF(subsumes, (*subsumes)(principals, frame->getPrincipals())); if (!frame->isSelfHosted()) { + RootedString asyncCause(cx, frame->getAsyncCause()); + if (!asyncCause && skippedAsync) { + asyncCause.set(cx->names().Async); + } js::RootedAtom name(cx, frame->getFunctionDisplayName()); - if ((name && !sb.append(name)) + if ((asyncCause && (!sb.append(asyncCause) || !sb.append('*'))) + || (name && !sb.append(name)) || !sb.append('@') || !sb.append(frame->getSource()) || !sb.append(':') @@ -550,7 +652,7 @@ StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString } parent = frame->getParent(); - frame = js::GetFirstSubsumedFrame(cx, parent); + frame = js::GetFirstSubsumedFrame(cx, parent, skippedAsync); } while (frame); JSString *str = sb.finishString(); @@ -613,6 +715,29 @@ SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp) return true; } +/* static */ bool +SavedFrame::asyncCauseProperty(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_SAVEDFRAME(cx, argc, vp, "(get asyncCause)", args, frame); + RootedString asyncCause(cx); + JS::SavedFrameResult result = JS::GetSavedFrameAsyncCause(cx, frame, &asyncCause); + if (result == JS::SavedFrameResult::Ok && asyncCause) + args.rval().setString(asyncCause); + else + args.rval().setNull(); + return true; +} + +/* static */ bool +SavedFrame::asyncParentProperty(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_SAVEDFRAME(cx, argc, vp, "(get asyncParent)", args, frame); + RootedObject asyncParent(cx); + (void) JS::GetSavedFrameAsyncParent(cx, frame, &asyncParent); + args.rval().setObjectOrNull(asyncParent); + return true; +} + /* static */ bool SavedFrame::parentProperty(JSContext *cx, unsigned argc, Value *vp) { @@ -676,6 +801,7 @@ SavedStacks::sweep(JSRuntime *rt) frame->getLine(), frame->getColumn(), frame->getFunctionDisplayName(), + frame->getAsyncCause(), frame->getParent(), frame->getPrincipals()), ReadBarriered(frame)); @@ -759,6 +885,7 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram location->column, iter.isNonEvalFunctionFrame() ? iter.functionDisplayAtom() : nullptr, nullptr, + nullptr, iter.compartment()->principals ); diff --git a/js/src/vm/SavedStacks.h b/js/src/vm/SavedStacks.h index 4e4199a400b0..c5206f24541b 100644 --- a/js/src/vm/SavedStacks.h +++ b/js/src/vm/SavedStacks.h @@ -35,6 +35,8 @@ class SavedFrame : public NativeObject { static bool lineProperty(JSContext *cx, unsigned argc, Value *vp); static bool columnProperty(JSContext *cx, unsigned argc, Value *vp); static bool functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp); + static bool asyncCauseProperty(JSContext *cx, unsigned argc, Value *vp); + static bool asyncParentProperty(JSContext *cx, unsigned argc, Value *vp); static bool parentProperty(JSContext *cx, unsigned argc, Value *vp); static bool toStringMethod(JSContext *cx, unsigned argc, Value *vp); @@ -43,6 +45,7 @@ class SavedFrame : public NativeObject { uint32_t getLine(); uint32_t getColumn(); JSAtom *getFunctionDisplayName(); + JSAtom *getAsyncCause(); SavedFrame *getParent(); JSPrincipals *getPrincipals(); @@ -86,6 +89,7 @@ class SavedFrame : public NativeObject { JSSLOT_LINE, JSSLOT_COLUMN, JSSLOT_FUNCTIONDISPLAYNAME, + JSSLOT_ASYNCCAUSE, JSSLOT_PARENT, JSSLOT_PRINCIPALS, JSSLOT_PRIVATE_PARENT,