зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1083359
- Part 2 - Allow C++ code to provide an async stack when calling a JS function. r=jimb
This commit is contained in:
Родитель
14addff448
Коммит
54445b17d2
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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++)
|
||||
|
|
|
@ -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);
|
|
@ -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();
|
|
@ -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();
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
/*
|
||||
|
|
|
@ -123,6 +123,8 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
|
|||
profilerSampleBufferGen_(0),
|
||||
profilerSampleBufferLapCount_(1),
|
||||
asmJSActivationStack_(nullptr),
|
||||
asyncStackForNewActivations(nullptr),
|
||||
asyncCauseForNewActivations(nullptr),
|
||||
parentRuntime(parentRuntime),
|
||||
interrupt_(false),
|
||||
telemetryCallback(nullptr),
|
||||
|
|
|
@ -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_;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче