Skip to content

Commit 4c929c5

Browse files
committed
Implement Error.stackTraceLimit
We default to 10 with a max cap of 64. Ref: https://v8.dev/docs/stack-trace-api
1 parent 555d837 commit 4c929c5

File tree

2 files changed

+65
-5
lines changed

2 files changed

+65
-5
lines changed

Diff for: quickjs.c

+35-5
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ struct JSContext {
382382
JSValue native_error_proto[JS_NATIVE_ERROR_COUNT];
383383
JSValue error_ctor;
384384
JSValue error_prepare_stack;
385+
int error_stack_trace_limit;
385386
JSValue iterator_proto;
386387
JSValue async_iterator_proto;
387388
JSValue array_proto_values;
@@ -2101,6 +2102,7 @@ JSContext *JS_NewContextRaw(JSRuntime *rt)
21012102
ctx->promise_ctor = JS_NULL;
21022103
ctx->error_ctor = JS_NULL;
21032104
ctx->error_prepare_stack = JS_UNDEFINED;
2105+
ctx->error_stack_trace_limit = 10;
21042106
init_list_head(&ctx->loaded_modules);
21052107

21062108
JS_AddIntrinsicBasicObjects(ctx);
@@ -6385,9 +6387,6 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj,
63856387
const char *filename, int line_num, int col_num,
63866388
int backtrace_flags)
63876389
{
6388-
// TODO(saghul) Make this configurable with Error.stackTraceLimit
6389-
static const int stack_limit = 10;
6390-
63916390
JSStackFrame *sf;
63926391
JSValue stack, prepare, saved_exception;
63936392
DynBuf dbuf;
@@ -6397,9 +6396,13 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj,
63976396
JSFunctionBytecode *b;
63986397
BOOL backtrace_barrier, has_prepare;
63996398
JSRuntime *rt;
6400-
JSCallSiteData csd[stack_limit];
6399+
JSCallSiteData csd[64];
64016400
uint32_t i;
6401+
int stack_trace_limit;
64026402

6403+
stack_trace_limit = ctx->error_stack_trace_limit;
6404+
stack_trace_limit = min_int(stack_trace_limit, countof(csd));
6405+
stack_trace_limit = max_int(stack_trace_limit, 0);
64036406
rt = ctx->rt;
64046407
has_prepare = FALSE;
64056408
i = 0;
@@ -6413,10 +6416,14 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj,
64136416
if (has_prepare) {
64146417
saved_exception = rt->current_exception;
64156418
rt->current_exception = JS_NULL;
6419+
if (stack_trace_limit == 0)
6420+
goto done;
64166421
if (filename)
64176422
js_new_callsite_data2(ctx, &csd[i++], filename, line_num, col_num);
64186423
} else {
64196424
js_dbuf_init(ctx, &dbuf);
6425+
if (stack_trace_limit == 0)
6426+
goto done;
64206427
if (filename) {
64216428
i++;
64226429
dbuf_printf(&dbuf, " at %s", filename);
@@ -6429,7 +6436,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj,
64296436
if (filename && (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL))
64306437
goto done;
64316438

6432-
for (sf = rt->current_stack_frame; sf != NULL && i < stack_limit; sf = sf->prev_frame) {
6439+
for (sf = rt->current_stack_frame; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) {
64336440
if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) {
64346441
backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL;
64356442
continue;
@@ -36178,6 +36185,28 @@ static const JSCFunctionListEntry js_error_proto_funcs[] = {
3617836185
JS_PROP_STRING_DEF("message", "", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
3617936186
};
3618036187

36188+
static JSValue js_error_get_stackTraceLimit(JSContext *ctx, JSValue this_val)
36189+
{
36190+
JSValue val, ret;
36191+
36192+
val = JS_ToObject(ctx, this_val);
36193+
if (JS_IsException(val))
36194+
return val;
36195+
JS_FreeValue(ctx, val);
36196+
return js_int32(ctx->error_stack_trace_limit);
36197+
}
36198+
36199+
static JSValue js_error_set_stackTraceLimit(JSContext *ctx, JSValue this_val, JSValue value)
36200+
{
36201+
if (JS_IsUndefined(this_val) || JS_IsNull(this_val))
36202+
return JS_ThrowTypeErrorNotAnObject(ctx);
36203+
int limit;
36204+
if (JS_ToInt32(ctx, &limit, value) < 0)
36205+
return JS_EXCEPTION;
36206+
ctx->error_stack_trace_limit = limit;
36207+
return JS_UNDEFINED;
36208+
}
36209+
3618136210
static JSValue js_error_get_prepareStackTrace(JSContext *ctx, JSValue this_val)
3618236211
{
3618336212
JSValue val, ret;
@@ -36199,6 +36228,7 @@ static JSValue js_error_set_prepareStackTrace(JSContext *ctx, JSValue this_val,
3619936228
}
3620036229

3620136230
static const JSCFunctionListEntry js_error_funcs[] = {
36231+
JS_CGETSET_DEF("stackTraceLimit", js_error_get_stackTraceLimit, js_error_set_stackTraceLimit ),
3620236232
JS_CGETSET_DEF("prepareStackTrace", js_error_get_prepareStackTrace, js_error_set_prepareStackTrace ),
3620336233
};
3620436234

Diff for: tests/test_builtin.js

+30
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,35 @@ function test_exception_prepare_stack()
5151
Error.prepareStackTrace = undefined;
5252
}
5353

54+
// Keep this at the top; it tests source positions.
55+
function test_exception_stack_size_limit()
56+
{
57+
var e;
58+
59+
Error.stackTraceLimit = 1;
60+
Error.prepareStackTrace = (_, frames) => {
61+
// Just return the array to check.
62+
return frames;
63+
};
64+
65+
try {
66+
throw new Error(""); // line 66, column 19
67+
} catch(_e) {
68+
e = _e;
69+
}
70+
71+
assert(e.stack.length === 1);
72+
const f = e.stack[0];
73+
assert(f.getFunctionName() === 'test_exception_stack_size_limit');
74+
assert(f.getFileName() === 'tests/test_builtin.js');
75+
assert(f.getLineNumber() === 66);
76+
assert(f.getColumnNumber() === 19);
77+
assert(!f.isNative());
78+
79+
Error.stackTraceLimit = 10;
80+
Error.prepareStackTrace = undefined;
81+
}
82+
5483
function assert(actual, expected, message) {
5584
if (arguments.length == 1)
5685
expected = true;
@@ -829,3 +858,4 @@ test_proxy_is_array();
829858
test_exception_source_pos();
830859
test_function_source_pos();
831860
test_exception_prepare_stack();
861+
test_exception_stack_size_limit();

0 commit comments

Comments
 (0)