Bug 1886820 - Add experimental support for Error.captureStackTrace r=jandem
Differential Revision: https://phabricator.services.mozilla.com/D230327
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "js/NativeStackLimits.h"
|
||||
#include "js/Principals.h" // JSPrincipals, JS_HoldPrincipals, JS_DropPrincipals
|
||||
#include "js/TypeDecls.h" // JSContext, Handle*, MutableHandle*
|
||||
#include "js/RootingAPI.h"
|
||||
|
||||
/**
|
||||
* Set the size of the native stack that should not be exceed. To disable
|
||||
@@ -142,7 +142,8 @@ using StackCapture = mozilla::Variant<AllFrames, MaxFrames, FirstSubsumedFrame>;
|
||||
*/
|
||||
extern JS_PUBLIC_API bool CaptureCurrentStack(
|
||||
JSContext* cx, MutableHandleObject stackp,
|
||||
StackCapture&& capture = StackCapture(AllFrames()));
|
||||
StackCapture&& capture = StackCapture(AllFrames()),
|
||||
HandleObject startAfter = nullptr);
|
||||
|
||||
/**
|
||||
* Returns true if capturing stack trace data to associate with an asynchronous
|
||||
|
||||
41
js/src/jit-test/tests/errors/capture-stack-jit.js
Normal file
41
js/src/jit-test/tests/errors/capture-stack-jit.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// |jit-test| --setpref=experimental.error_capture_stack_trace; --no-threads; --fast-warmup;
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
function caller(f) {
|
||||
return f();
|
||||
}
|
||||
|
||||
|
||||
function fill() {
|
||||
let x = {}
|
||||
Error.captureStackTrace(x, caller);
|
||||
return x;
|
||||
}
|
||||
|
||||
let iter = 1500
|
||||
for (let i = 0; i < iter; i++) {
|
||||
// Make sure caller is an IonFrame.
|
||||
caller(fill);
|
||||
}
|
||||
|
||||
|
||||
function test_jit_elision() {
|
||||
let x = caller(fill);
|
||||
let { stack } = x;
|
||||
print(stack);
|
||||
assertEq(stack.includes("caller"), false);
|
||||
assertEq(stack.includes("fill"), false);
|
||||
}
|
||||
|
||||
function test_jit_elision2() {
|
||||
({ stack } = caller(() => {
|
||||
let x = caller(fill);
|
||||
return x;
|
||||
}));
|
||||
assertEq(stack.includes("caller"), true); // Only elide the first caller!
|
||||
assertEq(stack.includes("fill"), false);
|
||||
}
|
||||
|
||||
test_jit_elision();
|
||||
test_jit_elision2();
|
||||
|
||||
344
js/src/jit-test/tests/errors/capture-stack.js
Normal file
344
js/src/jit-test/tests/errors/capture-stack.js
Normal file
@@ -0,0 +1,344 @@
|
||||
// |jit-test| --setpref=experimental.error_capture_stack_trace;
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
assertEq('captureStackTrace' in Error, true);
|
||||
assertEq(Error.captureStackTrace.length, 2);
|
||||
|
||||
let x = Error.captureStackTrace({});
|
||||
assertEq(x, undefined);
|
||||
|
||||
assertThrowsInstanceOf(() => Error.captureStackTrace(), TypeError);
|
||||
assertThrowsInstanceOf(() => Error.captureStackTrace(2), TypeError);
|
||||
|
||||
Error.captureStackTrace({}, 2);
|
||||
Error.captureStackTrace({}, null);
|
||||
Error.captureStackTrace({}, {});
|
||||
|
||||
function caller(f) {
|
||||
return f();
|
||||
}
|
||||
|
||||
function fill() {
|
||||
let x = {}
|
||||
Error.captureStackTrace(x, caller);
|
||||
return x;
|
||||
}
|
||||
|
||||
function test_elision() {
|
||||
let x = caller(fill);
|
||||
let { stack } = x;
|
||||
assertEq(stack.includes("caller"), false);
|
||||
assertEq(stack.includes("fill"), false);
|
||||
|
||||
|
||||
({ stack } = caller(() => caller(fill)))
|
||||
print(stack);
|
||||
assertEq(stack.includes("caller"), true); // Only elide the first caller!
|
||||
assertEq(stack.includes("fill"), false);
|
||||
}
|
||||
|
||||
test_elision();
|
||||
|
||||
function nestedLambda(f) {
|
||||
(() => {
|
||||
(() => {
|
||||
(() => {
|
||||
(() => {
|
||||
f();
|
||||
})();
|
||||
})();
|
||||
})();
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
// If we never see a matching frame when requesting a truncated
|
||||
// stack we should return the empty string
|
||||
function test_no_match() {
|
||||
let obj = {};
|
||||
// test_elision chosen arbitrarily as a function object which
|
||||
// doesn't exist in the call stack here.
|
||||
let capture = () => Error.captureStackTrace(obj, test_elision);
|
||||
nestedLambda(capture);
|
||||
assertEq(obj.stack, "");
|
||||
}
|
||||
test_no_match()
|
||||
|
||||
function count_frames(str) {
|
||||
return str.split("\n").length
|
||||
}
|
||||
|
||||
function test_nofilter() {
|
||||
let obj = {};
|
||||
let capture = () => Error.captureStackTrace(obj);
|
||||
nestedLambda(capture);
|
||||
assertEq(count_frames(obj.stack), 9);
|
||||
}
|
||||
test_nofilter();
|
||||
|
||||
function test_in_eval() {
|
||||
let obj = eval(`
|
||||
let obj = {};
|
||||
let capture = () => Error.captureStackTrace(obj);
|
||||
nestedLambda(capture);
|
||||
obj
|
||||
`)
|
||||
|
||||
// Same as above, with an eval frame added!
|
||||
assertEq(count_frames(obj.stack), 10);
|
||||
}
|
||||
test_in_eval();
|
||||
|
||||
//
|
||||
// [[ErrorData]]
|
||||
//
|
||||
const stackGetter = Object.getOwnPropertyDescriptor(Error.prototype, 'stack').get;
|
||||
const getStack = function (obj) {
|
||||
return stackGetter.call(obj);
|
||||
};
|
||||
|
||||
function test_uncensored() {
|
||||
let err = undefined;
|
||||
function create_err() {
|
||||
err = new Error;
|
||||
Error.captureStackTrace(err, test_uncensored);
|
||||
}
|
||||
|
||||
nestedLambda(create_err);
|
||||
|
||||
// Calling Error.captureStackTrace doesn't mess with the internal
|
||||
// [[ErrorData]] slot
|
||||
assertEq(count_frames(err.stack), 2);
|
||||
assertEq(count_frames(getStack(err)), 9)
|
||||
}
|
||||
test_uncensored()
|
||||
|
||||
// In general, the stacks a non-caller version of Error.captureStackStrace
|
||||
// should match what Error gives you
|
||||
function compare_stacks() {
|
||||
function censor_column(str) {
|
||||
return str.replace(/:(\d+):\d+\n/g, ":$1:censored\n")
|
||||
}
|
||||
|
||||
let obj = {};
|
||||
let err = (Error.captureStackTrace(obj), new Error)
|
||||
assertEq(censor_column(err.stack), censor_column(obj.stack));
|
||||
}
|
||||
compare_stacks();
|
||||
nestedLambda(compare_stacks)
|
||||
|
||||
// New global
|
||||
|
||||
function test_in_global(global) {
|
||||
global.evaluate(caller.toString());
|
||||
global.evaluate(fill.toString());
|
||||
global.evaluate(test_elision.toString());
|
||||
global.evaluate("test_elision()");
|
||||
|
||||
global.evaluate(nestedLambda.toString())
|
||||
global.evaluate(test_no_match.toString());
|
||||
global.evaluate("test_no_match()");
|
||||
|
||||
|
||||
global.evaluate(compare_stacks.toString());
|
||||
global.evaluate(`
|
||||
compare_stacks();
|
||||
nestedLambda(compare_stacks)
|
||||
`)
|
||||
}
|
||||
|
||||
let global = newGlobal();
|
||||
test_in_global(global);
|
||||
|
||||
let global2 = newGlobal({ principal: 0 });
|
||||
test_in_global(global2)
|
||||
|
||||
let global3 = newGlobal({ principal: 0xfffff });
|
||||
test_in_global(global3)
|
||||
|
||||
// What if the caller is a proxy?
|
||||
const caller_proxy = new Proxy(caller, {
|
||||
apply: function (target, thisArg, arguments) {
|
||||
return target(...arguments);
|
||||
}
|
||||
});
|
||||
|
||||
function fill_proxy() {
|
||||
let x = {}
|
||||
Error.captureStackTrace(x, caller_proxy);
|
||||
return x;
|
||||
}
|
||||
|
||||
// Proxies don't count for elision.
|
||||
function test_proxy_elision() {
|
||||
let x = caller_proxy(fill_proxy);
|
||||
let { stack } = x;
|
||||
assertEq(stack.includes("caller"), true);
|
||||
assertEq(stack.includes("fill_proxy"), true);
|
||||
}
|
||||
test_proxy_elision();
|
||||
|
||||
const trivial_proxy = new Proxy(caller, {});
|
||||
function fill_trivial() {
|
||||
let x = {}
|
||||
Error.captureStackTrace(x, trivial_proxy);
|
||||
return x;
|
||||
}
|
||||
|
||||
// Elision doesn't work even on forwarding proxy
|
||||
function test_trivial_elision() {
|
||||
let x = caller(fill_trivial);
|
||||
let { stack } = x;
|
||||
assertEq(stack.includes("caller"), true);
|
||||
assertEq(stack.includes("fill"), true);
|
||||
}
|
||||
test_trivial_elision();
|
||||
|
||||
// Elision happens through bind
|
||||
function test_bind_elision() {
|
||||
let b = caller.bind(undefined, fill);
|
||||
let { stack } = b();
|
||||
assertEq(stack.includes("caller"), false);
|
||||
assertEq(stack.includes("fill"), false);
|
||||
}
|
||||
test_bind_elision();
|
||||
|
||||
// Cross Realm testing
|
||||
|
||||
let nr = newGlobal({ newCompartment: true })
|
||||
nr.eval(`globalThis.x = {}`);
|
||||
Error.captureStackTrace(nr.x);
|
||||
|
||||
// Test strict definition
|
||||
function test_strict_definition() {
|
||||
"use strict";
|
||||
assertThrowsInstanceOf(() => Error.captureStackTrace(Object.freeze({ stack: null })), TypeError);
|
||||
}
|
||||
test_strict_definition();
|
||||
|
||||
function test_property_descriptor() {
|
||||
let o = {};
|
||||
Error.captureStackTrace(o);
|
||||
let desc = Object.getOwnPropertyDescriptor(o, "stack");
|
||||
assertEq(desc.configurable, true)
|
||||
assertEq(desc.writable, true)
|
||||
assertEq(desc.enumerable, false)
|
||||
}
|
||||
test_property_descriptor();
|
||||
|
||||
function test_delete() {
|
||||
let o = {};
|
||||
Error.captureStackTrace(o);
|
||||
delete o.stack
|
||||
assertEq("stack" in o, false)
|
||||
}
|
||||
test_delete();
|
||||
|
||||
// Principal testing: This is basic/shell-principals.js extended to support
|
||||
// and compare Error.captureStackTrace.
|
||||
//
|
||||
// Reminder:
|
||||
// > In the shell, a principal is simply a 32-bit mask: P subsumes Q if the
|
||||
// > set bits in P are a superset of those in Q. Thus, the principal 0 is
|
||||
// > subsumed by everything, and the principal ~0 subsumes everything.
|
||||
|
||||
// Given a string of letters |expected|, say "abc", assert that the stack
|
||||
// contains calls to a series of functions named by the next letter from
|
||||
// the string, say a, b, and then c. Younger frames appear earlier in
|
||||
// |expected| than older frames.
|
||||
let count = 0;
|
||||
function check(expected, stack) {
|
||||
print("check(" + JSON.stringify(expected) + ") against:\n" + stack);
|
||||
count++;
|
||||
|
||||
// Extract only the function names from the stack trace. Omit the frames
|
||||
// for the top-level evaluation, if it is present.
|
||||
var split = stack.split(/(.)?@.*\n/).slice(0, -1);
|
||||
if (split[split.length - 1] === undefined)
|
||||
split = split.slice(0, -2);
|
||||
|
||||
print(JSON.stringify(split));
|
||||
// Check the function names against the expected sequence.
|
||||
assertEq(split.length, expected.length * 2);
|
||||
for (var i = 0; i < expected.length; i++)
|
||||
assertEq(split[i * 2 + 1], expected[i]);
|
||||
}
|
||||
|
||||
var low = newGlobal({ principal: 0 });
|
||||
var mid = newGlobal({ principal: 0xffff });
|
||||
var high = newGlobal({ principal: 0xfffff });
|
||||
|
||||
eval('function a() { let o = {}; Error.captureStackTrace(o); check("a", o.stack); b(); }');
|
||||
low.eval('function b() { let o = {}; Error.captureStackTrace(o); check("b", o.stack); c(); }');
|
||||
mid.eval('function c() { let o = {}; Error.captureStackTrace(o); check("cba", o.stack); d(); }');
|
||||
high.eval('function d() { let o = {}; Error.captureStackTrace(o); check("dcba", o.stack); e(); }');
|
||||
|
||||
// Globals created with no explicit principals get 0xffff.
|
||||
eval('function e() { let o = {}; Error.captureStackTrace(o); check("ecba", o.stack); f(); }');
|
||||
|
||||
low.eval('function f() { let o = {}; Error.captureStackTrace(o); check("fb", o.stack); g(); }');
|
||||
mid.eval('function g() { let o = {}; Error.captureStackTrace(o); check("gfecba", o.stack); h(); }');
|
||||
high.eval('function h() { let o = {}; Error.captureStackTrace(o); check("hgfedcba", o.stack); }');
|
||||
|
||||
// Make everyone's functions visible to each other, as needed.
|
||||
b = low.b;
|
||||
low.c = mid.c;
|
||||
mid.d = high.d;
|
||||
high.e = e;
|
||||
f = low.f;
|
||||
low.g = mid.g;
|
||||
mid.h = high.h;
|
||||
|
||||
low.check = mid.check = high.check = check;
|
||||
|
||||
// Kick the whole process off.
|
||||
a();
|
||||
|
||||
assertEq(count, 8);
|
||||
|
||||
// Ensure filtering is based on caller realm not on target object.
|
||||
low.eval("low_target = {}");
|
||||
mid.eval("mid_target = {}");
|
||||
high.eval("high_target = {}");
|
||||
|
||||
high.low_target = mid.low_target = low.low_target;
|
||||
high.mid_target = low.mid_target = mid.mid_target;
|
||||
mid.high_target = low.high_target = high.high_target;
|
||||
|
||||
high.low_cst = mid.low_cst = low.low_cst = low.Error.captureStackTrace;
|
||||
high.mid_cst = low.mid_cst = mid.mid_cst = mid.Error.captureStackTrace;
|
||||
mid.high_cst = low.high_cst = high.high_cst = high.Error.captureStackTrace;
|
||||
|
||||
for (let g of [low, mid, high]) {
|
||||
assertEq("low_target" in g, true);
|
||||
assertEq("mid_target" in g, true);
|
||||
assertEq("high_target" in g, true);
|
||||
|
||||
assertEq("low_cst" in g, true);
|
||||
assertEq("mid_cst" in g, true);
|
||||
assertEq("high_cst" in g, true);
|
||||
|
||||
// install caller function z -- single letter name for
|
||||
// check compat.
|
||||
g.eval("function z(f) { f() }")
|
||||
}
|
||||
|
||||
low.eval("function q() { Error.captureStackTrace(low_target); }")
|
||||
|
||||
|
||||
high.q = low.q;
|
||||
|
||||
// Caller function z is from high, but using low Error.captureStackTrace, so
|
||||
// z should be elided.
|
||||
high.eval("z(q)");
|
||||
check("q", low.low_target.stack);
|
||||
|
||||
low.eval("function r() { high_cst(low_target) }")
|
||||
high.r = low.r;
|
||||
|
||||
// Can see everything here using high cst and low target.
|
||||
high.eval("function t() { z(r) }");
|
||||
high.t();
|
||||
check("rzt", low.low_target.stack);
|
||||
|
||||
|
||||
@@ -4931,14 +4931,16 @@ JS::FirstSubsumedFrame::FirstSubsumedFrame(
|
||||
|
||||
JS_PUBLIC_API bool JS::CaptureCurrentStack(
|
||||
JSContext* cx, JS::MutableHandleObject stackp,
|
||||
JS::StackCapture&& capture /* = JS::StackCapture(JS::AllFrames()) */) {
|
||||
JS::StackCapture&& capture /* = JS::StackCapture(JS::AllFrames()) */,
|
||||
JS::HandleObject startAt /* = nullptr*/) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
MOZ_RELEASE_ASSERT(cx->realm());
|
||||
|
||||
Realm* realm = cx->realm();
|
||||
Rooted<SavedFrame*> frame(cx);
|
||||
if (!realm->savedStacks().saveCurrentStack(cx, &frame, std::move(capture))) {
|
||||
if (!realm->savedStacks().saveCurrentStack(cx, &frame, std::move(capture),
|
||||
startAt)) {
|
||||
return false;
|
||||
}
|
||||
stackp.set(frame.get());
|
||||
|
||||
@@ -221,10 +221,6 @@ struct SuppressErrorsGuard {
|
||||
~SuppressErrorsGuard() { JS::SetWarningReporter(cx, prevReporter); }
|
||||
};
|
||||
|
||||
// Cut off the stack if it gets too deep (most commonly for infinite recursion
|
||||
// errors).
|
||||
static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7;
|
||||
|
||||
bool js::CaptureStack(JSContext* cx, MutableHandleObject stack) {
|
||||
return CaptureCurrentStack(
|
||||
cx, stack, JS::StackCapture(JS::MaxFrames(MAX_REPORTED_STACK_DEPTH)));
|
||||
|
||||
@@ -36,6 +36,10 @@ UniquePtr<JSErrorNotes::Note> CopyErrorNote(JSContext* cx,
|
||||
|
||||
UniquePtr<JSErrorReport> CopyErrorReport(JSContext* cx, JSErrorReport* report);
|
||||
|
||||
// Cut off the stack if it gets too deep (most commonly for infinite recursion
|
||||
// errors).
|
||||
static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7;
|
||||
|
||||
bool CaptureStack(JSContext* cx, MutableHandleObject stack);
|
||||
|
||||
JSString* ComputeStackString(JSContext* cx);
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
MACRO_(callee, "callee") \
|
||||
MACRO_(caller, "caller") \
|
||||
MACRO_(callFunction, "callFunction") \
|
||||
MACRO_(captureStackTrace, "captureStackTrace") \
|
||||
MACRO_(cancel, "cancel") \
|
||||
MACRO_(case_, "case") \
|
||||
MACRO_(caseFirst, "caseFirst") \
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include "vm/JSContext-inl.h"
|
||||
#include "vm/JSObject-inl.h"
|
||||
#include "vm/ObjectOperations-inl.h"
|
||||
#include "vm/Realm-inl.h"
|
||||
#include "vm/SavedStacks-inl.h"
|
||||
#include "vm/Shape-inl.h"
|
||||
|
||||
@@ -96,11 +97,13 @@ static const JSFunctionSpec error_methods[] = {
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
static bool exn_isError(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool exn_captureStackTrace(JSContext* cx, unsigned argc, Value* vp);
|
||||
#endif
|
||||
|
||||
static const JSFunctionSpec error_static_methods[] = {
|
||||
#ifdef NIGHTLY_BUILD
|
||||
JS_FN("isError", exn_isError, 1, 0),
|
||||
JS_FN("captureStackTrace", exn_captureStackTrace, 2, 0),
|
||||
#endif
|
||||
JS_FS_END,
|
||||
};
|
||||
@@ -969,4 +972,85 @@ static bool exn_isError(JSContext* cx, unsigned argc, Value* vp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The below is the "documentation" from https://v8.dev/docs/stack-trace-api
|
||||
//
|
||||
// ## Stack trace collection for custom exceptions
|
||||
//
|
||||
// The stack trace mechanism used for built-in errors is implemented using a
|
||||
// general stack trace collection API that is also available to user scripts.
|
||||
// The function
|
||||
//
|
||||
// Error.captureStackTrace(error, constructorOpt)
|
||||
//
|
||||
// adds a stack property to the given error object that yields the stack trace
|
||||
// at the time captureStackTrace was called. Stack traces collected through
|
||||
// Error.captureStackTrace are immediately collected, formatted, and attached
|
||||
// to the given error object.
|
||||
//
|
||||
// The optional constructorOpt parameter allows you to pass in a function
|
||||
// value. When collecting the stack trace all frames above the topmost call to
|
||||
// this function, including that call, are left out of the stack trace. This
|
||||
// can be useful to hide implementation details that won’t be useful to the
|
||||
// user. The usual way of defining a custom error that captures a stack trace
|
||||
// would be:
|
||||
//
|
||||
// function MyError() {
|
||||
// Error.captureStackTrace(this, MyError);
|
||||
// // Any other initialization goes here.
|
||||
// }
|
||||
//
|
||||
// Passing in MyError as a second argument means that the constructor call to
|
||||
// MyError won’t show up in the stack trace.
|
||||
|
||||
static bool exn_captureStackTrace(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
const char* callerName = "Error.captureStackTrace";
|
||||
|
||||
if (!args.requireAtLeast(cx, callerName, 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<JSObject*> obj(cx,
|
||||
RequireObjectArg(cx, "`target`", callerName, args[0]));
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<JSObject*> caller(cx, nullptr);
|
||||
if (args.length() > 1 && args[1].isObject() &&
|
||||
args[1].toObject().isCallable()) {
|
||||
caller = CheckedUnwrapStatic(&args[1].toObject());
|
||||
if (!caller) {
|
||||
ReportAccessDenied(cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RootedObject stack(cx);
|
||||
if (!CaptureCurrentStack(
|
||||
cx, &stack, JS::StackCapture(JS::MaxFrames(MAX_REPORTED_STACK_DEPTH)),
|
||||
caller)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedString stackString(cx);
|
||||
|
||||
// Do frame filtering based on the current realm, to filter out any
|
||||
// chrome frames which could exist on the stack.
|
||||
JSPrincipals* principals = cx->realm()->principals();
|
||||
if (!BuildStackString(cx, principals, stack, &stackString)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// V8 installs a non-enumerable, configurable getter-setter on the object.
|
||||
// JSC installs a non-enumerable, configurable, writable value on the
|
||||
// object. We are following JSC here, not V8.
|
||||
RootedValue string(cx, StringValue(stackString));
|
||||
if (!DefineDataProperty(cx, obj, cx->names().stack, string, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2345,6 +2345,12 @@ JS_PUBLIC_API bool js::ShouldIgnorePropertyDefinition(JSContext* cx,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (key == JSProto_Function &&
|
||||
!JS::Prefs::experimental_error_capture_stack_trace() &&
|
||||
id == NameToId(cx->names().captureStackTrace)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == JSProto_JSON &&
|
||||
!JS::Prefs::experimental_json_parse_with_source() &&
|
||||
(id == NameToId(cx->names().isRawJSON) ||
|
||||
|
||||
@@ -1324,7 +1324,8 @@ bool SavedFrame::toStringMethod(JSContext* cx, unsigned argc, Value* vp) {
|
||||
|
||||
bool SavedStacks::saveCurrentStack(
|
||||
JSContext* cx, MutableHandle<SavedFrame*> frame,
|
||||
JS::StackCapture&& capture /* = JS::StackCapture(JS::AllFrames()) */) {
|
||||
JS::StackCapture&& capture /* = JS::StackCapture(JS::AllFrames()) */,
|
||||
HandleObject startAt /* nullptr */) {
|
||||
MOZ_RELEASE_ASSERT(cx->realm());
|
||||
MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this);
|
||||
|
||||
@@ -1335,7 +1336,7 @@ bool SavedStacks::saveCurrentStack(
|
||||
}
|
||||
|
||||
AutoGeckoProfilerEntry labelFrame(cx, "js::SavedStacks::saveCurrentStack");
|
||||
return insertFrames(cx, frame, std::move(capture));
|
||||
return insertFrames(cx, frame, std::move(capture), startAt);
|
||||
}
|
||||
|
||||
bool SavedStacks::copyAsyncStack(JSContext* cx, HandleObject asyncStack,
|
||||
@@ -1411,7 +1412,10 @@ static inline bool captureIsSatisfied(JSContext* cx, JSPrincipals* principals,
|
||||
}
|
||||
|
||||
bool SavedStacks::insertFrames(JSContext* cx, MutableHandle<SavedFrame*> frame,
|
||||
JS::StackCapture&& capture) {
|
||||
JS::StackCapture&& capture,
|
||||
HandleObject startAtObj) {
|
||||
MOZ_ASSERT_IF(startAtObj, startAtObj->isCallable());
|
||||
|
||||
// In order to look up a cached SavedFrame object, we need to have its parent
|
||||
// SavedFrame, which means we need to walk the stack from oldest frame to
|
||||
// youngest. However, FrameIter walks the stack from youngest frame to
|
||||
@@ -1465,6 +1469,11 @@ bool SavedStacks::insertFrames(JSContext* cx, MutableHandle<SavedFrame*> frame,
|
||||
// targets and ensure that we don't stop before they have all been reached.
|
||||
Vector<AbstractFramePtr, 4, TempAllocPolicy> unreachedEvalTargets(cx);
|
||||
|
||||
Rooted<JSFunction*> startAt(cx, startAtObj && startAtObj->is<JSFunction>()
|
||||
? &startAtObj->as<JSFunction>()
|
||||
: nullptr);
|
||||
bool seenStartAt = !startAt;
|
||||
|
||||
while (!iter.done()) {
|
||||
Activation& activation = *iter.activation();
|
||||
Maybe<LiveSavedFrameCache::FramePtr> framePtr =
|
||||
@@ -1541,19 +1550,29 @@ bool SavedStacks::insertFrames(JSContext* cx, MutableHandle<SavedFrame*> frame,
|
||||
auto principals = iter.realm()->principals();
|
||||
MOZ_ASSERT_IF(framePtr && !iter.isWasm(), iter.pc());
|
||||
|
||||
if (!stackChain.emplaceBack(location.source(), location.sourceId(),
|
||||
location.line(), location.column(), displayAtom,
|
||||
nullptr, // asyncCause
|
||||
nullptr, // parent (not known yet)
|
||||
principals, iter.mutedErrors(), framePtr,
|
||||
iter.pc(), &activation)) {
|
||||
return false;
|
||||
// If we haven't yet seen the start, then don't add anything to the stack
|
||||
// chain.
|
||||
if (seenStartAt) {
|
||||
if (!stackChain.emplaceBack(location.source(), location.sourceId(),
|
||||
location.line(), location.column(),
|
||||
displayAtom,
|
||||
nullptr, // asyncCause
|
||||
nullptr, // parent (not known yet)
|
||||
principals, iter.mutedErrors(), framePtr,
|
||||
iter.pc(), &activation)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (captureIsSatisfied(cx, principals, location.source(), capture)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!seenStartAt && iter.isFunctionFrame() &&
|
||||
iter.matchCallee(cx, startAt)) {
|
||||
seenStartAt = true;
|
||||
}
|
||||
|
||||
++iter;
|
||||
framePtr = LiveSavedFrameCache::FramePtr::create(iter);
|
||||
|
||||
|
||||
@@ -167,7 +167,8 @@ class SavedStacks {
|
||||
|
||||
[[nodiscard]] bool saveCurrentStack(
|
||||
JSContext* cx, MutableHandle<SavedFrame*> frame,
|
||||
JS::StackCapture&& capture = JS::StackCapture(JS::AllFrames()));
|
||||
JS::StackCapture&& capture = JS::StackCapture(JS::AllFrames()),
|
||||
HandleObject startAt = nullptr);
|
||||
[[nodiscard]] bool copyAsyncStack(
|
||||
JSContext* cx, HandleObject asyncStack, HandleString asyncCause,
|
||||
MutableHandle<SavedFrame*> adoptedStack,
|
||||
@@ -218,7 +219,8 @@ class SavedStacks {
|
||||
|
||||
[[nodiscard]] bool insertFrames(JSContext* cx,
|
||||
MutableHandle<SavedFrame*> frame,
|
||||
JS::StackCapture&& capture);
|
||||
JS::StackCapture&& capture,
|
||||
HandleObject startAt);
|
||||
[[nodiscard]] bool adoptAsyncStack(
|
||||
JSContext* cx, MutableHandle<SavedFrame*> asyncStack,
|
||||
Handle<JSAtom*> asyncCause, const mozilla::Maybe<size_t>& maxFrameCount);
|
||||
|
||||
Reference in New Issue
Block a user