Bug 1083359 - Part 2 - Allow C++ code to provide an async stack when calling a JS function. r=jimb

This commit is contained in:
Paolo Amadini 2015-03-06 15:50:28 +00:00
parent 861e0237ae
commit b86edfa586
14 changed files with 611 additions and 20 deletions

View File

@ -115,8 +115,8 @@ AsmJSFrameIterator::computeLine(uint32_t *column) const
static const unsigned PushedRetAddr = 0;
static const unsigned PostStorePrePopFP = 0;
# endif
static const unsigned PushedFP = 10;
static const unsigned StoredFP = 14;
static const unsigned PushedFP = 13;
static const unsigned StoredFP = 20;
#elif defined(JS_CODEGEN_X86)
# if defined(DEBUG)
static const unsigned PushedRetAddr = 0;

View File

@ -1007,6 +1007,37 @@ SaveStack(JSContext *cx, unsigned argc, jsval *vp)
return true;
}
static bool
CallFunctionWithAsyncStack(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 3) {
JS_ReportError(cx, "The function takes exactly three arguments.");
return false;
}
if (!args[0].isObject() || !IsCallable(args[0])) {
JS_ReportError(cx, "The first argument should be a function.");
return false;
}
if (!args[1].isObject() || !args[1].toObject().is<SavedFrame>()) {
JS_ReportError(cx, "The second argument should be a SavedFrame.");
return false;
}
if (!args[2].isString() || args[2].toString()->empty()) {
JS_ReportError(cx, "The third argument should be a non-empty string.");
return false;
}
RootedObject function(cx, &args[0].toObject());
RootedObject stack(cx, &args[1].toObject());
RootedString asyncCause(cx, args[2].toString());
JS::AutoSetAsyncStackForNewCalls sas(cx, stack, asyncCause);
return Call(cx, UndefinedHandleValue, function,
JS::HandleValueArray::empty(), args.rval());
}
static bool
EnableTrackAllocations(JSContext *cx, unsigned argc, jsval *vp)
{
@ -2438,6 +2469,13 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = {
" of frames. If 'compartment' is given, allocate the js::SavedFrame instances\n"
" with the given object's compartment."),
JS_FN_HELP("callFunctionWithAsyncStack", CallFunctionWithAsyncStack, 0, 0,
"callFunctionWithAsyncStack(function, stack, asyncCause)",
" Call 'function', using the provided stack as the async stack responsible\n"
" for the call, and propagate its return value or the exception it throws.\n"
" The function is called with no arguments, and 'this' is 'undefined'. The\n"
" specified |asyncCause| is attached to the provided stack frame."),
JS_FN_HELP("enableTrackAllocations", EnableTrackAllocations, 0, 0,
"enableTrackAllocations()",
" Start capturing the JS stack at every allocation. Note that this sets an\n"

View File

@ -451,6 +451,14 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc,
MarkPersistentRootedChains(trc);
}
if (rt->asyncStackForNewActivations)
MarkObjectRoot(trc, &rt->asyncStackForNewActivations,
"asyncStackForNewActivations");
if (rt->asyncCauseForNewActivations)
MarkStringRoot(trc, &rt->asyncCauseForNewActivations,
"asyncCauseForNewActivations");
if (rt->scriptAndCountsVector) {
ScriptAndCountsVector &vec = *rt->scriptAndCountsVector;
for (size_t i = 0; i < vec.length(); i++)

View File

@ -0,0 +1,84 @@
// Test that async stacks are limited on recursion.
const defaultAsyncStackLimit = 60;
function recur(n, limit) {
if (n > 0) {
return callFunctionWithAsyncStack(recur.bind(undefined, n - 1, limit),
saveStack(limit), "Recurse");
}
return saveStack(limit);
}
function checkRecursion(n, limit) {
print("checkRecursion(" + uneval(n) + ", " + uneval(limit) + ")");
var stack = recur(n, limit);
// Async stacks are limited even if we didn't ask for a limit. There is a
// default limit on frames attached on top of any synchronous frames. In this
// case the synchronous frame is the last call to `recur`.
if (limit == 0) {
limit = defaultAsyncStackLimit + 1;
}
// The first `n` or `limit` frames should have `recur` as their `asyncParent`.
for (var i = 0; i < Math.min(n, limit); i++) {
assertEq(stack.functionDisplayName, "recur");
assertEq(stack.parent, null);
stack = stack.asyncParent;
}
// This frame should be the first call to `recur`.
if (limit > n) {
assertEq(stack.functionDisplayName, "recur");
assertEq(stack.asyncParent, null);
stack = stack.parent;
} else {
assertEq(stack, null);
}
// This frame should be the call to `checkRecursion`.
if (limit > n + 1) {
assertEq(stack.functionDisplayName, "checkRecursion");
assertEq(stack.asyncParent, null);
stack = stack.parent;
} else {
assertEq(stack, null);
}
// We should be at the top frame, which is the test script itself.
if (limit > n + 2) {
assertEq(stack.functionDisplayName, null);
assertEq(stack.asyncParent, null);
assertEq(stack.parent, null);
} else {
assertEq(stack, null);
}
}
// Check capturing with no limit, which should still apply a default limit.
checkRecursion(0, 0);
checkRecursion(1, 0);
checkRecursion(2, 0);
checkRecursion(defaultAsyncStackLimit + 10, 0);
// Limit of 1 frame.
checkRecursion(0, 1);
checkRecursion(1, 1);
checkRecursion(2, 1);
// Limit of 2 frames.
checkRecursion(0, 2);
checkRecursion(1, 2);
checkRecursion(2, 2);
// Limit of 3 frames.
checkRecursion(0, 3);
checkRecursion(1, 3);
checkRecursion(2, 3);
// Limit higher than the default limit.
checkRecursion(defaultAsyncStackLimit + 10, defaultAsyncStackLimit + 10);
checkRecursion(defaultAsyncStackLimit + 11, defaultAsyncStackLimit + 10);
checkRecursion(defaultAsyncStackLimit + 12, defaultAsyncStackLimit + 10);

View File

@ -0,0 +1,244 @@
// Test cases when a SavedFrame or one of its ancestors have a principal that is
// not subsumed by the caller's principal, when async parents are present.
function checkVisibleStack(stackFrame, expectedFrames) {
// We check toString separately from properties like asyncCause to check that
// it walks the physical SavedFrame chain consistently with the properties.
var stringFrames = stackFrame.toString().split("\n");
while (expectedFrames.length) {
let expectedFrame = expectedFrames.shift();
let stringFrame = stringFrames.shift();
// Check the frame properties.
assertEq(stackFrame.functionDisplayName, expectedFrame.name);
assertEq(stackFrame.asyncCause, expectedFrame.asyncCause);
// Check the stringified version.
let expectedStart =
(expectedFrame.asyncCause ? expectedFrame.asyncCause + "*" : "") +
expectedFrame.name;
assertEq(stringFrame.replace(/@.*$/, ""), expectedStart);
// If the next frame has an asyncCause, it should be an asyncParent.
if (expectedFrames.length && expectedFrames[0].asyncCause) {
assertEq(stackFrame.parent, null);
stackFrame = stackFrame.asyncParent;
} else {
assertEq(stackFrame.asyncParent, null);
stackFrame = stackFrame.parent;
}
}
}
var low = newGlobal({ principal: 0 });
var high = newGlobal({ principal: 0xfffff });
// Test with synchronous cross-compartment calls.
//
// With arrows representing child-to-parent links, and fat arrows representing
// child-to-async-parent links, create a SavedFrame stack like this:
//
// low.e -> high.d => high.c => high.b -> low.a
//
// This stack captured in function `e` would be seen in its complete version if
// accessed by `high`'s compartment, while in `low`'s compartment it would look
// like this:
//
// low.e => low.a
//
// The asyncCause seen on `low.a` above should not leak information about the
// real asyncCause on `high.c` and `high.d`.
//
// The stack captured in function `d` would be seen in its complete version if
// accessed by `high`'s compartment, while in `low`'s compartment it would look
// like this:
//
// low.a
// We'll move these functions into the right globals below before invoking them.
function a() {
b();
}
function b() {
callFunctionWithAsyncStack(c, saveStack(), "BtoC");
}
function c() {
callFunctionWithAsyncStack(d, saveStack(), "CtoD");
}
function d() {
let stackD = saveStack();
print("high.checkVisibleStack(stackD)");
checkVisibleStack(stackD, [
{ name: "d", asyncCause: null },
{ name: "c", asyncCause: "CtoD" },
{ name: "b", asyncCause: "BtoC" },
{ name: "a", asyncCause: null },
]);
let stackE = e(saveStack(0, low));
print("high.checkVisibleStack(stackE)");
checkVisibleStack(stackE, [
{ name: "e", asyncCause: null },
{ name: "d", asyncCause: null },
{ name: "c", asyncCause: "CtoD" },
{ name: "b", asyncCause: "BtoC" },
{ name: "a", asyncCause: null },
]);
}
function e(stackD) {
print("low.checkVisibleStack(stackD)");
checkVisibleStack(stackD, [
{ name: "a", asyncCause: "Async" },
]);
let stackE = saveStack();
print("low.checkVisibleStack(stackE)");
checkVisibleStack(stackE, [
{ name: "e", asyncCause: null },
{ name: "a", asyncCause: "Async" },
]);
return saveStack(0, high);
}
// Test with asynchronous cross-compartment calls and shared frames.
//
// With arrows representing child-to-parent links, and fat arrows representing
// child-to-async-parent links, create a SavedFrame stack like this:
//
// low.x => high.v => low.u
// low.y -> high.v => low.u
// low.z => high.w -> low.u
//
// This stack captured in functions `x`, `y`, and `z` would be seen in its
// complete version if accessed by `high`'s compartment, while in `low`'s
// compartment it would look like this:
//
// low.x => low.u
// low.y => low.u
// low.z => low.u
//
// The stack captured in function `v` would be seen in its complete version if
// accessed by `high`'s compartment, while in `low`'s compartment it would look
// like this:
//
// low.u
// We'll move these functions into the right globals below before invoking them.
function u() {
callFunctionWithAsyncStack(v, saveStack(), "UtoV");
w();
}
function v() {
let stackV = saveStack();
print("high.checkVisibleStack(stackV)");
checkVisibleStack(stackV, [
{ name: "v", asyncCause: null },
{ name: "u", asyncCause: "UtoV" },
]);
let xToCall = x.bind(undefined, saveStack(0, low));
let stackX = callFunctionWithAsyncStack(xToCall, saveStack(), "VtoX");
print("high.checkVisibleStack(stackX)");
checkVisibleStack(stackX, [
{ name: "x", asyncCause: null },
{ name: "v", asyncCause: "VtoX" },
{ name: "u", asyncCause: "UtoV" },
]);
let stackY = y();
print("high.checkVisibleStack(stackY)");
checkVisibleStack(stackY, [
{ name: "y", asyncCause: null },
{ name: "v", asyncCause: null },
{ name: "u", asyncCause: "UtoV" },
]);
}
function w() {
let stackZ = callFunctionWithAsyncStack(z, saveStack(), "WtoZ");
print("high.checkVisibleStack(stackZ)");
checkVisibleStack(stackZ, [
{ name: "z", asyncCause: null },
{ name: "w", asyncCause: "WtoZ" },
{ name: "u", asyncCause: null },
]);
}
function x(stackV) {
print("low.checkVisibleStack(stackV)");
checkVisibleStack(stackV, [
{ name: "u", asyncCause: "UtoV" },
]);
let stackX = saveStack();
print("low.checkVisibleStack(stackX)");
checkVisibleStack(stackX, [
{ name: "x", asyncCause: null },
{ name: "u", asyncCause: "UtoV" },
]);
return saveStack(0, high);
}
function y() {
let stackY = saveStack();
print("low.checkVisibleStack(stackY)");
checkVisibleStack(stackY, [
{ name: "y", asyncCause: null },
{ name: "u", asyncCause: "UtoV" },
]);
return saveStack(0, high);
}
function z() {
let stackZ = saveStack();
print("low.checkVisibleStack(stackZ)");
checkVisibleStack(stackZ, [
{ name: "z", asyncCause: null },
{ name: "u", asyncCause: "Async" },
]);
return saveStack(0, high);
}
// Split the functions in their respective globals.
low .eval(a.toSource());
high.eval(b.toSource());
high.eval(c.toSource());
high.eval(d.toSource());
low .eval(e.toSource());
low .b = high.b;
high.e = low .e;
low .eval(u.toSource());
high.eval(v.toSource());
high.eval(w.toSource());
low .eval(x.toSource());
low .eval(y.toSource());
low .eval(z.toSource());
low .v = high.v;
low .w = high.w;
high.x = low .x;
high.y = low .y;
high.z = low .z;
low .high = high;
high.low = low;
low .eval(checkVisibleStack.toSource());
high.eval(checkVisibleStack.toSource());
// Execute the tests.
low.a();
low.u();

View File

@ -0,0 +1,24 @@
// Test calling a function using a previously captured stack as an async stack.
function getAsyncStack() {
return saveStack();
}
// asyncCause may contain non-ASCII characters.
let testAsyncCause = "Tes" + String.fromCharCode(355) + "String";
callFunctionWithAsyncStack(function asyncCallback() {
let stack = saveStack();
assertEq(stack.functionDisplayName, "asyncCallback");
assertEq(stack.parent, null);
assertEq(stack.asyncCause, null);
assertEq(stack.asyncParent.functionDisplayName, "getAsyncStack");
assertEq(stack.asyncParent.asyncCause, testAsyncCause);
assertEq(stack.asyncParent.asyncParent, null);
assertEq(stack.asyncParent.parent.asyncCause, null);
assertEq(stack.asyncParent.parent.asyncParent, null);
assertEq(stack.asyncParent.parent.parent, null);
}, getAsyncStack(), testAsyncCause);

View File

@ -4318,6 +4318,28 @@ JS_RestoreFrameChain(JSContext *cx)
cx->restoreFrameChain();
}
JS::AutoSetAsyncStackForNewCalls::AutoSetAsyncStackForNewCalls(
JSContext *cx, HandleObject stack, HandleString asyncCause)
: cx(cx),
oldAsyncStack(cx, cx->runtime()->asyncStackForNewActivations),
oldAsyncCause(cx, cx->runtime()->asyncCauseForNewActivations)
{
CHECK_REQUEST(cx);
SavedFrame *asyncStack = &stack->as<SavedFrame>();
MOZ_ASSERT(!asyncCause->empty());
cx->runtime()->asyncStackForNewActivations = asyncStack;
cx->runtime()->asyncCauseForNewActivations = asyncCause;
}
JS::AutoSetAsyncStackForNewCalls::~AutoSetAsyncStackForNewCalls()
{
cx->runtime()->asyncCauseForNewActivations = oldAsyncCause;
cx->runtime()->asyncStackForNewActivations =
oldAsyncStack ? &oldAsyncStack->as<SavedFrame>() : nullptr;
}
/************************************************************************/
JS_PUBLIC_API(JSString *)
JS_NewStringCopyN(JSContext *cx, const char *s, size_t n)

View File

@ -3811,6 +3811,40 @@ JS_SaveFrameChain(JSContext *cx);
extern JS_PUBLIC_API(void)
JS_RestoreFrameChain(JSContext *cx);
namespace JS {
/*
* This class can be used to store a pointer to the youngest frame of a saved
* stack in the specified JSContext. This reference will be picked up by any new
* calls performed until the class is destroyed, with the specified asyncCause,
* that must not be empty.
*
* Any stack capture initiated during these new calls will go through the async
* stack instead of the current stack.
*
* Capturing the stack before a new call is performed will not be affected.
*
* The provided chain of SavedFrame objects can live in any compartment,
* although it will be copied to the compartment where the stack is captured.
*/
class MOZ_STACK_CLASS JS_PUBLIC_API(AutoSetAsyncStackForNewCalls)
{
JSContext *cx;
RootedObject oldAsyncStack;
RootedString oldAsyncCause;
public:
// The stack parameter cannot be null by design, because it would be
// ambiguous whether that would clear any scheduled async stack and make the
// normal stack reappear in the new call, or just keep the async stack
// already scheduled for the new call, if any.
AutoSetAsyncStackForNewCalls(JSContext *cx, HandleObject stack,
HandleString asyncCause);
~AutoSetAsyncStackForNewCalls();
};
}
/************************************************************************/
/*

View File

@ -123,6 +123,8 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
profilerSampleBufferGen_(0),
profilerSampleBufferLapCount_(1),
asmJSActivationStack_(nullptr),
asyncStackForNewActivations(nullptr),
asyncCauseForNewActivations(nullptr),
parentRuntime(parentRuntime),
interrupt_(false),
telemetryCallback(nullptr),

View File

@ -660,6 +660,22 @@ struct JSRuntime : public JS::shadow::Runtime,
js::AsmJSActivation * volatile asmJSActivationStack_;
public:
/*
* Youngest frame of a saved stack that will be picked up as an async stack
* by any new Activation, and is nullptr when no async stack should be used.
*
* The JS::AutoSetAsyncStackForNewCalls class can be used to set this.
*
* New activations will reset this to nullptr on construction after getting
* the current value, and will restore the previous value on destruction.
*/
js::SavedFrame *asyncStackForNewActivations;
/*
* Value of asyncCause to be attached to asyncStackForNewActivations.
*/
JSString *asyncCauseForNewActivations;
js::Activation *const *addressOfActivation() const {
return &activation_;
}

View File

@ -38,6 +38,11 @@ using mozilla::Maybe;
namespace js {
/**
* Maximum number of saved frames returned for an async stack.
*/
const unsigned ASYNC_STACK_MAX_FRAME_COUNT = 60;
struct SavedFrame::Lookup {
Lookup(JSAtom *source, uint32_t line, uint32_t column,
JSAtom *functionDisplayName, JSAtom *asyncCause, SavedFrame *parent,
@ -53,6 +58,18 @@ struct SavedFrame::Lookup {
MOZ_ASSERT(source);
}
explicit Lookup(SavedFrame &savedFrame)
: source(savedFrame.getSource()),
line(savedFrame.getLine()),
column(savedFrame.getColumn()),
functionDisplayName(savedFrame.getFunctionDisplayName()),
asyncCause(savedFrame.getAsyncCause()),
parent(savedFrame.getParent()),
principals(savedFrame.getPrincipals())
{
MOZ_ASSERT(source);
}
JSAtom *source;
uint32_t line;
uint32_t column;
@ -797,13 +814,7 @@ SavedStacks::sweep(JSRuntime *rt)
}
if (obj != temp || parentMoved) {
e.rekeyFront(SavedFrame::Lookup(frame->getSource(),
frame->getLine(),
frame->getColumn(),
frame->getFunctionDisplayName(),
frame->getAsyncCause(),
frame->getParent(),
frame->getPrincipals()),
e.rekeyFront(SavedFrame::Lookup(*frame),
ReadBarriered<SavedFrame *>(frame));
}
}
@ -864,9 +875,32 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram
// pointers on the first pass, and then we fill in the parent pointers as we
// return in the second pass.
Activation *asyncActivation = nullptr;
RootedSavedFrame asyncStack(cx, nullptr);
RootedString asyncCause(cx, nullptr);
// Accumulate the vector of Lookup objects in |stackChain|.
SavedFrame::AutoLookupVector stackChain(cx);
while (!iter.done()) {
Activation &activation = *iter.activation();
if (!asyncActivation) {
asyncStack = activation.asyncStack();
if (asyncStack) {
// While walking from the youngest to the oldest frame, we found
// an activation that has an async stack set. We will use the
// youngest frame of the async stack as the parent of the oldest
// frame of this activation. We still need to iterate over other
// frames in this activation before reaching the oldest frame.
asyncCause = activation.asyncCause();
asyncActivation = &activation;
}
} else if (asyncActivation != &activation) {
// We found an async stack in the previous activation, and we
// walked past the oldest frame of that activation, we're done.
break;
}
AutoLocationValueRooter location(cx);
{
@ -891,17 +925,73 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram
++iter;
if (maxFrameCount == 0) {
// If maxFrameCount is zero, then there's no limit on the number of
// frames.
// If maxFrameCount is zero there's no limit on the number of frames.
if (maxFrameCount == 0)
continue;
} else if (maxFrameCount == 1) {
// Since we were only asked to save one frame, do not continue
// walking the stack and saving frame state.
if (maxFrameCount == 1) {
// The frame we just saved was the last one we were asked to save.
// If we had an async stack, ensure we don't use any of its frames.
asyncStack.set(nullptr);
break;
} else {
maxFrameCount--;
}
maxFrameCount--;
}
// Limit the depth of the async stack, if any, and ensure that the
// SavedFrame instances we use are stored in the same compartment as the
// rest of the synchronous stack chain.
RootedSavedFrame parentFrame(cx, nullptr);
if (asyncStack && !adoptAsyncStack(cx, asyncStack, asyncCause, &parentFrame, maxFrameCount))
return false;
// Iterate through |stackChain| in reverse order and get or create the
// actual SavedFrame instances.
for (size_t i = stackChain->length(); i != 0; i--) {
SavedFrame::HandleLookup lookup = stackChain[i-1];
lookup->parent = parentFrame;
parentFrame.set(getOrCreateSavedFrame(cx, lookup));
if (!parentFrame)
return false;
}
frame.set(parentFrame);
return true;
}
bool
SavedStacks::adoptAsyncStack(JSContext *cx, HandleSavedFrame asyncStack,
HandleString asyncCause,
MutableHandleSavedFrame adoptedStack,
unsigned maxFrameCount)
{
RootedAtom asyncCauseAtom(cx, AtomizeString(cx, asyncCause));
if (!asyncCauseAtom)
return false;
// If maxFrameCount is zero, the caller asked for an unlimited number of
// stack frames, but async stacks are not limited by the available stack
// memory, so we need to set an arbitrary limit when collecting them. We
// still don't enforce an upper limit if the caller requested more frames.
if (maxFrameCount == 0)
maxFrameCount = ASYNC_STACK_MAX_FRAME_COUNT;
// Accumulate the vector of Lookup objects in |stackChain|.
SavedFrame::AutoLookupVector stackChain(cx);
SavedFrame *currentSavedFrame = asyncStack;
for (unsigned i = 0; i < maxFrameCount && currentSavedFrame; i++) {
// Use growByUninitialized and placement-new instead of just append.
// We'd ideally like to use an emplace method once Vector supports it.
if (!stackChain->growByUninitialized(1))
return false;
new (&stackChain->back()) SavedFrame::Lookup(*currentSavedFrame);
// Attach the asyncCause to the youngest frame.
if (i == 0)
stackChain->back().asyncCause = asyncCauseAtom;
currentSavedFrame = currentSavedFrame->getParent();
}
// Iterate through |stackChain| in reverse order and get or create the
@ -915,7 +1005,7 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram
return false;
}
frame.set(parentFrame);
adoptedStack.set(parentFrame);
return true;
}

View File

@ -63,8 +63,6 @@ class SavedFrame : public NativeObject {
HashPolicy,
SystemAllocPolicy> Set;
typedef RootedGeneric<Lookup*> AutoLookupRooter;
class AutoLookupVector;
class MOZ_STACK_CLASS HandleLookup {
@ -160,6 +158,10 @@ class SavedStacks {
bool insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFrame frame,
unsigned maxFrameCount = 0);
bool adoptAsyncStack(JSContext *cx, HandleSavedFrame asyncStack,
HandleString asyncCause,
MutableHandleSavedFrame adoptedStack,
unsigned maxFrameCount);
SavedFrame *getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup lookup);
SavedFrame *createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup);
void chooseSamplingProbability(JSContext* cx);

View File

@ -826,8 +826,12 @@ Activation::Activation(JSContext *cx, Kind kind)
prevProfiling_(prev_ ? prev_->mostRecentProfiling() : nullptr),
savedFrameChain_(0),
hideScriptedCallerCount_(0),
asyncStack_(cx, cx->runtime_->asyncStackForNewActivations),
asyncCause_(cx, cx->runtime_->asyncCauseForNewActivations),
kind_(kind)
{
cx->runtime_->asyncStackForNewActivations = nullptr;
cx->runtime_->asyncCauseForNewActivations = nullptr;
cx->runtime_->activation_ = this;
}
@ -837,6 +841,8 @@ Activation::~Activation()
MOZ_ASSERT(cx_->runtime_->activation_ == this);
MOZ_ASSERT(hideScriptedCallerCount_ == 0);
cx_->runtime_->activation_ = prev_;
cx_->runtime_->asyncCauseForNewActivations = asyncCause_;
cx_->runtime_->asyncStackForNewActivations = asyncStack_;
}
bool

View File

@ -35,6 +35,8 @@ class StaticBlockObject;
class ScopeCoordinate;
class SavedFrame;
// VM stack layout
//
// A JSRuntime's stack consists of a linked list of activations. Every activation
@ -1064,6 +1066,17 @@ class Activation
// data structures instead.
size_t hideScriptedCallerCount_;
// Youngest saved frame of an async stack that will be iterated during stack
// capture in place of the actual stack of previous activations. Note that
// the stack of this activation is captured entirely before this is used.
//
// Usually this is nullptr, meaning that normal stack capture will occur.
// When this is set, the stack of any previous activation is ignored.
Rooted<SavedFrame *> asyncStack_;
// Value of asyncCause to be attached to asyncStack_.
RootedString asyncCause_;
enum Kind { Interpreter, Jit, AsmJS };
Kind kind_;
@ -1136,6 +1149,14 @@ class Activation
return offsetof(Activation, prevProfiling_);
}
SavedFrame *asyncStack() {
return asyncStack_;
}
JSString *asyncCause() {
return asyncCause_;
}
private:
Activation(const Activation &other) = delete;
void operator=(const Activation &other) = delete;