mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1083359
- Part 2 - Allow C++ code to provide an async stack when calling a JS function. r=jimb
This commit is contained in:
parent
861e0237ae
commit
b86edfa586
@ -115,8 +115,8 @@ AsmJSFrameIterator::computeLine(uint32_t *column) const
|
|||||||
static const unsigned PushedRetAddr = 0;
|
static const unsigned PushedRetAddr = 0;
|
||||||
static const unsigned PostStorePrePopFP = 0;
|
static const unsigned PostStorePrePopFP = 0;
|
||||||
# endif
|
# endif
|
||||||
static const unsigned PushedFP = 10;
|
static const unsigned PushedFP = 13;
|
||||||
static const unsigned StoredFP = 14;
|
static const unsigned StoredFP = 20;
|
||||||
#elif defined(JS_CODEGEN_X86)
|
#elif defined(JS_CODEGEN_X86)
|
||||||
# if defined(DEBUG)
|
# if defined(DEBUG)
|
||||||
static const unsigned PushedRetAddr = 0;
|
static const unsigned PushedRetAddr = 0;
|
||||||
|
@ -1007,6 +1007,37 @@ SaveStack(JSContext *cx, unsigned argc, jsval *vp)
|
|||||||
return true;
|
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
|
static bool
|
||||||
EnableTrackAllocations(JSContext *cx, unsigned argc, jsval *vp)
|
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"
|
" of frames. If 'compartment' is given, allocate the js::SavedFrame instances\n"
|
||||||
" with the given object's compartment."),
|
" 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,
|
JS_FN_HELP("enableTrackAllocations", EnableTrackAllocations, 0, 0,
|
||||||
"enableTrackAllocations()",
|
"enableTrackAllocations()",
|
||||||
" Start capturing the JS stack at every allocation. Note that this sets an\n"
|
" Start capturing the JS stack at every allocation. Note that this sets an\n"
|
||||||
|
@ -451,6 +451,14 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc,
|
|||||||
MarkPersistentRootedChains(trc);
|
MarkPersistentRootedChains(trc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rt->asyncStackForNewActivations)
|
||||||
|
MarkObjectRoot(trc, &rt->asyncStackForNewActivations,
|
||||||
|
"asyncStackForNewActivations");
|
||||||
|
|
||||||
|
if (rt->asyncCauseForNewActivations)
|
||||||
|
MarkStringRoot(trc, &rt->asyncCauseForNewActivations,
|
||||||
|
"asyncCauseForNewActivations");
|
||||||
|
|
||||||
if (rt->scriptAndCountsVector) {
|
if (rt->scriptAndCountsVector) {
|
||||||
ScriptAndCountsVector &vec = *rt->scriptAndCountsVector;
|
ScriptAndCountsVector &vec = *rt->scriptAndCountsVector;
|
||||||
for (size_t i = 0; i < vec.length(); i++)
|
for (size_t i = 0; i < vec.length(); i++)
|
||||||
|
84
js/src/jit-test/tests/saved-stacks/async-max-frame-count.js
Normal file
84
js/src/jit-test/tests/saved-stacks/async-max-frame-count.js
Normal 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);
|
244
js/src/jit-test/tests/saved-stacks/async-principals.js
Normal file
244
js/src/jit-test/tests/saved-stacks/async-principals.js
Normal 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();
|
24
js/src/jit-test/tests/saved-stacks/async.js
Normal file
24
js/src/jit-test/tests/saved-stacks/async.js
Normal 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);
|
@ -4318,6 +4318,28 @@ JS_RestoreFrameChain(JSContext *cx)
|
|||||||
cx->restoreFrameChain();
|
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_PUBLIC_API(JSString *)
|
||||||
JS_NewStringCopyN(JSContext *cx, const char *s, size_t n)
|
JS_NewStringCopyN(JSContext *cx, const char *s, size_t n)
|
||||||
|
@ -3811,6 +3811,40 @@ JS_SaveFrameChain(JSContext *cx);
|
|||||||
extern JS_PUBLIC_API(void)
|
extern JS_PUBLIC_API(void)
|
||||||
JS_RestoreFrameChain(JSContext *cx);
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -123,6 +123,8 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
|
|||||||
profilerSampleBufferGen_(0),
|
profilerSampleBufferGen_(0),
|
||||||
profilerSampleBufferLapCount_(1),
|
profilerSampleBufferLapCount_(1),
|
||||||
asmJSActivationStack_(nullptr),
|
asmJSActivationStack_(nullptr),
|
||||||
|
asyncStackForNewActivations(nullptr),
|
||||||
|
asyncCauseForNewActivations(nullptr),
|
||||||
parentRuntime(parentRuntime),
|
parentRuntime(parentRuntime),
|
||||||
interrupt_(false),
|
interrupt_(false),
|
||||||
telemetryCallback(nullptr),
|
telemetryCallback(nullptr),
|
||||||
|
@ -660,6 +660,22 @@ struct JSRuntime : public JS::shadow::Runtime,
|
|||||||
js::AsmJSActivation * volatile asmJSActivationStack_;
|
js::AsmJSActivation * volatile asmJSActivationStack_;
|
||||||
|
|
||||||
public:
|
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 {
|
js::Activation *const *addressOfActivation() const {
|
||||||
return &activation_;
|
return &activation_;
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,11 @@ using mozilla::Maybe;
|
|||||||
|
|
||||||
namespace js {
|
namespace js {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of saved frames returned for an async stack.
|
||||||
|
*/
|
||||||
|
const unsigned ASYNC_STACK_MAX_FRAME_COUNT = 60;
|
||||||
|
|
||||||
struct SavedFrame::Lookup {
|
struct SavedFrame::Lookup {
|
||||||
Lookup(JSAtom *source, uint32_t line, uint32_t column,
|
Lookup(JSAtom *source, uint32_t line, uint32_t column,
|
||||||
JSAtom *functionDisplayName, JSAtom *asyncCause, SavedFrame *parent,
|
JSAtom *functionDisplayName, JSAtom *asyncCause, SavedFrame *parent,
|
||||||
@ -53,6 +58,18 @@ struct SavedFrame::Lookup {
|
|||||||
MOZ_ASSERT(source);
|
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;
|
JSAtom *source;
|
||||||
uint32_t line;
|
uint32_t line;
|
||||||
uint32_t column;
|
uint32_t column;
|
||||||
@ -797,13 +814,7 @@ SavedStacks::sweep(JSRuntime *rt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (obj != temp || parentMoved) {
|
if (obj != temp || parentMoved) {
|
||||||
e.rekeyFront(SavedFrame::Lookup(frame->getSource(),
|
e.rekeyFront(SavedFrame::Lookup(*frame),
|
||||||
frame->getLine(),
|
|
||||||
frame->getColumn(),
|
|
||||||
frame->getFunctionDisplayName(),
|
|
||||||
frame->getAsyncCause(),
|
|
||||||
frame->getParent(),
|
|
||||||
frame->getPrincipals()),
|
|
||||||
ReadBarriered<SavedFrame *>(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
|
// pointers on the first pass, and then we fill in the parent pointers as we
|
||||||
// return in the second pass.
|
// return in the second pass.
|
||||||
|
|
||||||
|
Activation *asyncActivation = nullptr;
|
||||||
|
RootedSavedFrame asyncStack(cx, nullptr);
|
||||||
|
RootedString asyncCause(cx, nullptr);
|
||||||
|
|
||||||
// Accumulate the vector of Lookup objects in |stackChain|.
|
// Accumulate the vector of Lookup objects in |stackChain|.
|
||||||
SavedFrame::AutoLookupVector stackChain(cx);
|
SavedFrame::AutoLookupVector stackChain(cx);
|
||||||
while (!iter.done()) {
|
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);
|
AutoLocationValueRooter location(cx);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -891,17 +925,73 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram
|
|||||||
|
|
||||||
++iter;
|
++iter;
|
||||||
|
|
||||||
if (maxFrameCount == 0) {
|
// If maxFrameCount is zero there's no limit on the number of frames.
|
||||||
// If maxFrameCount is zero, then there's no limit on the number of
|
if (maxFrameCount == 0)
|
||||||
// frames.
|
|
||||||
continue;
|
continue;
|
||||||
} else if (maxFrameCount == 1) {
|
|
||||||
// Since we were only asked to save one frame, do not continue
|
if (maxFrameCount == 1) {
|
||||||
// walking the stack and saving frame state.
|
// 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;
|
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
|
// Iterate through |stackChain| in reverse order and get or create the
|
||||||
@ -915,7 +1005,7 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.set(parentFrame);
|
adoptedStack.set(parentFrame);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,8 +63,6 @@ class SavedFrame : public NativeObject {
|
|||||||
HashPolicy,
|
HashPolicy,
|
||||||
SystemAllocPolicy> Set;
|
SystemAllocPolicy> Set;
|
||||||
|
|
||||||
typedef RootedGeneric<Lookup*> AutoLookupRooter;
|
|
||||||
|
|
||||||
class AutoLookupVector;
|
class AutoLookupVector;
|
||||||
|
|
||||||
class MOZ_STACK_CLASS HandleLookup {
|
class MOZ_STACK_CLASS HandleLookup {
|
||||||
@ -160,6 +158,10 @@ class SavedStacks {
|
|||||||
|
|
||||||
bool insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFrame frame,
|
bool insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFrame frame,
|
||||||
unsigned maxFrameCount = 0);
|
unsigned maxFrameCount = 0);
|
||||||
|
bool adoptAsyncStack(JSContext *cx, HandleSavedFrame asyncStack,
|
||||||
|
HandleString asyncCause,
|
||||||
|
MutableHandleSavedFrame adoptedStack,
|
||||||
|
unsigned maxFrameCount);
|
||||||
SavedFrame *getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup lookup);
|
SavedFrame *getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup lookup);
|
||||||
SavedFrame *createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup);
|
SavedFrame *createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup);
|
||||||
void chooseSamplingProbability(JSContext* cx);
|
void chooseSamplingProbability(JSContext* cx);
|
||||||
|
@ -826,8 +826,12 @@ Activation::Activation(JSContext *cx, Kind kind)
|
|||||||
prevProfiling_(prev_ ? prev_->mostRecentProfiling() : nullptr),
|
prevProfiling_(prev_ ? prev_->mostRecentProfiling() : nullptr),
|
||||||
savedFrameChain_(0),
|
savedFrameChain_(0),
|
||||||
hideScriptedCallerCount_(0),
|
hideScriptedCallerCount_(0),
|
||||||
|
asyncStack_(cx, cx->runtime_->asyncStackForNewActivations),
|
||||||
|
asyncCause_(cx, cx->runtime_->asyncCauseForNewActivations),
|
||||||
kind_(kind)
|
kind_(kind)
|
||||||
{
|
{
|
||||||
|
cx->runtime_->asyncStackForNewActivations = nullptr;
|
||||||
|
cx->runtime_->asyncCauseForNewActivations = nullptr;
|
||||||
cx->runtime_->activation_ = this;
|
cx->runtime_->activation_ = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,6 +841,8 @@ Activation::~Activation()
|
|||||||
MOZ_ASSERT(cx_->runtime_->activation_ == this);
|
MOZ_ASSERT(cx_->runtime_->activation_ == this);
|
||||||
MOZ_ASSERT(hideScriptedCallerCount_ == 0);
|
MOZ_ASSERT(hideScriptedCallerCount_ == 0);
|
||||||
cx_->runtime_->activation_ = prev_;
|
cx_->runtime_->activation_ = prev_;
|
||||||
|
cx_->runtime_->asyncCauseForNewActivations = asyncCause_;
|
||||||
|
cx_->runtime_->asyncStackForNewActivations = asyncStack_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -35,6 +35,8 @@ class StaticBlockObject;
|
|||||||
|
|
||||||
class ScopeCoordinate;
|
class ScopeCoordinate;
|
||||||
|
|
||||||
|
class SavedFrame;
|
||||||
|
|
||||||
// VM stack layout
|
// VM stack layout
|
||||||
//
|
//
|
||||||
// A JSRuntime's stack consists of a linked list of activations. Every activation
|
// A JSRuntime's stack consists of a linked list of activations. Every activation
|
||||||
@ -1064,6 +1066,17 @@ class Activation
|
|||||||
// data structures instead.
|
// data structures instead.
|
||||||
size_t hideScriptedCallerCount_;
|
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 };
|
enum Kind { Interpreter, Jit, AsmJS };
|
||||||
Kind kind_;
|
Kind kind_;
|
||||||
|
|
||||||
@ -1136,6 +1149,14 @@ class Activation
|
|||||||
return offsetof(Activation, prevProfiling_);
|
return offsetof(Activation, prevProfiling_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SavedFrame *asyncStack() {
|
||||||
|
return asyncStack_;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSString *asyncCause() {
|
||||||
|
return asyncCause_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Activation(const Activation &other) = delete;
|
Activation(const Activation &other) = delete;
|
||||||
void operator=(const Activation &other) = delete;
|
void operator=(const Activation &other) = delete;
|
||||||
|
Loading…
Reference in New Issue
Block a user