From 4c929c5b6bb7719973a82d9a14c0b8efc1496b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Tue, 19 Dec 2023 16:01:47 +0100 Subject: [PATCH] Implement Error.stackTraceLimit We default to 10 with a max cap of 64. Ref: https://v8.dev/docs/stack-trace-api --- quickjs.c | 40 +++++++++++++++++++++++++++++++++++----- tests/test_builtin.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/quickjs.c b/quickjs.c index 0c0791d..9b1e192 100644 --- a/quickjs.c +++ b/quickjs.c @@ -382,6 +382,7 @@ struct JSContext { JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; JSValue error_ctor; JSValue error_prepare_stack; + int error_stack_trace_limit; JSValue iterator_proto; JSValue async_iterator_proto; JSValue array_proto_values; @@ -2101,6 +2102,7 @@ JSContext *JS_NewContextRaw(JSRuntime *rt) ctx->promise_ctor = JS_NULL; ctx->error_ctor = JS_NULL; ctx->error_prepare_stack = JS_UNDEFINED; + ctx->error_stack_trace_limit = 10; init_list_head(&ctx->loaded_modules); JS_AddIntrinsicBasicObjects(ctx); @@ -6385,9 +6387,6 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, const char *filename, int line_num, int col_num, int backtrace_flags) { - // TODO(saghul) Make this configurable with Error.stackTraceLimit - static const int stack_limit = 10; - JSStackFrame *sf; JSValue stack, prepare, saved_exception; DynBuf dbuf; @@ -6397,9 +6396,13 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSFunctionBytecode *b; BOOL backtrace_barrier, has_prepare; JSRuntime *rt; - JSCallSiteData csd[stack_limit]; + JSCallSiteData csd[64]; uint32_t i; + int stack_trace_limit; + stack_trace_limit = ctx->error_stack_trace_limit; + stack_trace_limit = min_int(stack_trace_limit, countof(csd)); + stack_trace_limit = max_int(stack_trace_limit, 0); rt = ctx->rt; has_prepare = FALSE; i = 0; @@ -6413,10 +6416,14 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, if (has_prepare) { saved_exception = rt->current_exception; rt->current_exception = JS_NULL; + if (stack_trace_limit == 0) + goto done; if (filename) js_new_callsite_data2(ctx, &csd[i++], filename, line_num, col_num); } else { js_dbuf_init(ctx, &dbuf); + if (stack_trace_limit == 0) + goto done; if (filename) { i++; dbuf_printf(&dbuf, " at %s", filename); @@ -6429,7 +6436,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, if (filename && (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL)) goto done; - for (sf = rt->current_stack_frame; sf != NULL && i < stack_limit; sf = sf->prev_frame) { + for (sf = rt->current_stack_frame; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; continue; @@ -36178,6 +36185,28 @@ static const JSCFunctionListEntry js_error_proto_funcs[] = { JS_PROP_STRING_DEF("message", "", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; +static JSValue js_error_get_stackTraceLimit(JSContext *ctx, JSValue this_val) +{ + JSValue val, ret; + + val = JS_ToObject(ctx, this_val); + if (JS_IsException(val)) + return val; + JS_FreeValue(ctx, val); + return js_int32(ctx->error_stack_trace_limit); +} + +static JSValue js_error_set_stackTraceLimit(JSContext *ctx, JSValue this_val, JSValue value) +{ + if (JS_IsUndefined(this_val) || JS_IsNull(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + int limit; + if (JS_ToInt32(ctx, &limit, value) < 0) + return JS_EXCEPTION; + ctx->error_stack_trace_limit = limit; + return JS_UNDEFINED; +} + static JSValue js_error_get_prepareStackTrace(JSContext *ctx, JSValue this_val) { JSValue val, ret; @@ -36199,6 +36228,7 @@ static JSValue js_error_set_prepareStackTrace(JSContext *ctx, JSValue this_val, } static const JSCFunctionListEntry js_error_funcs[] = { + JS_CGETSET_DEF("stackTraceLimit", js_error_get_stackTraceLimit, js_error_set_stackTraceLimit ), JS_CGETSET_DEF("prepareStackTrace", js_error_get_prepareStackTrace, js_error_set_prepareStackTrace ), }; diff --git a/tests/test_builtin.js b/tests/test_builtin.js index 9ed1c2e..a2a657e 100644 --- a/tests/test_builtin.js +++ b/tests/test_builtin.js @@ -51,6 +51,35 @@ function test_exception_prepare_stack() Error.prepareStackTrace = undefined; } +// Keep this at the top; it tests source positions. +function test_exception_stack_size_limit() +{ + var e; + + Error.stackTraceLimit = 1; + Error.prepareStackTrace = (_, frames) => { + // Just return the array to check. + return frames; + }; + + try { + throw new Error(""); // line 66, column 19 + } catch(_e) { + e = _e; + } + + assert(e.stack.length === 1); + const f = e.stack[0]; + assert(f.getFunctionName() === 'test_exception_stack_size_limit'); + assert(f.getFileName() === 'tests/test_builtin.js'); + assert(f.getLineNumber() === 66); + assert(f.getColumnNumber() === 19); + assert(!f.isNative()); + + Error.stackTraceLimit = 10; + Error.prepareStackTrace = undefined; +} + function assert(actual, expected, message) { if (arguments.length == 1) expected = true; @@ -829,3 +858,4 @@ test_proxy_is_array(); test_exception_source_pos(); test_function_source_pos(); test_exception_prepare_stack(); +test_exception_stack_size_limit();