Skip to content

Commit c351133

Browse files
committed
Implement Error.captureStackTrace
1 parent 0b0b794 commit c351133

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

docs/docs/diff.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,7 @@ of ES features present in NG:
6262

6363
Some non-standard but widely used APIs have also been added:
6464

65-
- V8's `Error.prepareStackTrace` and `Error.stackTraceLimit`
65+
- V8's [stack trace API](https://v8.dev/docs/stack-trace-api)
66+
- `Error.captureStackTrace`
67+
- `Error.prepareStackTrace`
68+
- `Error.stackTraceLimit`

quickjs.c

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6612,14 +6612,15 @@ static const char *get_func_name(JSContext *ctx, JSValue func)
66126612
#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0)
66136613
/* only taken into account if filename is provided */
66146614
#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1)
6615+
#define JS_BACKTRACE_FLAG_FILTER_FUNC (1 << 2)
66156616

66166617
/* if filename != NULL, an additional level is added with the filename
66176618
and line number information (used for parse error). */
6618-
static void build_backtrace(JSContext *ctx, JSValue error_obj,
6619+
static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_func,
66196620
const char *filename, int line_num, int col_num,
66206621
int backtrace_flags)
66216622
{
6622-
JSStackFrame *sf;
6623+
JSStackFrame *sf, *sf_start;
66236624
JSValue stack, prepare, saved_exception;
66246625
DynBuf dbuf;
66256626
const char *func_name_str;
@@ -6668,7 +6669,20 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj,
66686669
if (filename && (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL))
66696670
goto done;
66706671

6671-
for (sf = rt->current_stack_frame; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) {
6672+
sf_start = rt->current_stack_frame;
6673+
6674+
/* Find the frame we want to start from. Note that when a filter is used the filter
6675+
function will be the first, but we also specify we want to skip the first one. */
6676+
if (backtrace_flags & JS_BACKTRACE_FLAG_FILTER_FUNC) {
6677+
for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) {
6678+
if (js_same_value(ctx, sf->cur_func, filter_func)) {
6679+
sf_start = sf;
6680+
break;
6681+
}
6682+
}
6683+
}
6684+
6685+
for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) {
66726686
if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) {
66736687
backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL;
66746688
continue;
@@ -6811,7 +6825,7 @@ static JSValue JS_MakeError(JSContext *ctx, JSErrorEnum error_num,
68116825
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
68126826
}
68136827
if (add_backtrace)
6814-
build_backtrace(ctx, obj, NULL, 0, 0, 0);
6828+
build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0);
68156829
return obj;
68166830
}
68176831

@@ -17380,7 +17394,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
1738017394
before if the exception happens in a bytecode
1738117395
operation */
1738217396
sf->cur_pc = pc;
17383-
build_backtrace(ctx, rt->current_exception, NULL, 0, 0, 0);
17397+
build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, NULL, 0, 0, 0);
1738417398
}
1738517399
if (!JS_IsUncatchableError(ctx, rt->current_exception)) {
1738617400
while (sp > stack_buf) {
@@ -18968,7 +18982,7 @@ int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const
1896818982
backtrace_flags = 0;
1896918983
if (s->cur_func && s->cur_func->backtrace_barrier)
1897018984
backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL;
18971-
build_backtrace(ctx, ctx->rt->current_exception, s->filename,
18985+
build_backtrace(ctx, ctx->rt->current_exception, JS_UNDEFINED, s->filename,
1897218986
s->line_num, s->col_num, backtrace_flags);
1897318987
return -1;
1897418988
}
@@ -23470,7 +23484,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
2347023484
backtrace_flags = 0;
2347123485
if (s->cur_func && s->cur_func->backtrace_barrier)
2347223486
backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL;
23473-
build_backtrace(s->ctx, s->ctx->rt->current_exception,
23487+
build_backtrace(s->ctx, s->ctx->rt->current_exception, JS_UNDEFINED,
2347423488
s->filename,
2347523489
s->token.line_num,
2347623490
s->token.col_num,
@@ -37909,7 +37923,7 @@ static JSValue js_error_constructor(JSContext *ctx, JSValue new_target,
3790937923
}
3791037924

3791137925
/* skip the Error() function in the backtrace */
37912-
build_backtrace(ctx, obj, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL);
37926+
build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL);
3791337927
return obj;
3791437928
exception:
3791537929
JS_FreeValue(ctx, obj);
@@ -37999,8 +38013,19 @@ static JSValue js_error_set_prepareStackTrace(JSContext *ctx, JSValue this_val,
3799938013
return JS_UNDEFINED;
3800038014
}
3800138015

38016+
static JSValue js_error_capture_stack_trace(JSContext *ctx, JSValue this_val,
38017+
int argc, JSValue *argv)
38018+
{
38019+
JSValue v = argv[0];
38020+
if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT)
38021+
return JS_ThrowTypeErrorNotAnObject(ctx);
38022+
build_backtrace(ctx, v, argv[1], NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL|JS_BACKTRACE_FLAG_FILTER_FUNC);
38023+
return JS_UNDEFINED;
38024+
}
38025+
3800238026
static const JSCFunctionListEntry js_error_funcs[] = {
3800338027
JS_CFUNC_DEF("isError", 1, js_error_isError ),
38028+
JS_CFUNC_DEF("captureStackTrace", 2, js_error_capture_stack_trace),
3800438029
JS_CGETSET_DEF("stackTraceLimit", js_error_get_stackTraceLimit, js_error_set_stackTraceLimit ),
3800538030
JS_CGETSET_DEF("prepareStackTrace", js_error_get_prepareStackTrace, js_error_set_prepareStackTrace ),
3800638031
};

tests/test_builtin.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,37 @@ function test_exception_stack_size_limit()
8181
assert(!f.isNative());
8282
}
8383

84+
function test_exception_capture_stack_trace()
85+
{
86+
var o = {};
87+
88+
assertThrows(TypeError, (function() {
89+
Error.captureStackTrace();
90+
}));
91+
92+
Error.captureStackTrace(o);
93+
94+
assert(typeof o.stack === 'string');
95+
assert(o.stack.includes('test_exception_capture_stack_trace'));
96+
}
97+
98+
function test_exception_capture_stack_trace_filter()
99+
{
100+
var o = {};
101+
const fun1 = () => { fun2(); };
102+
const fun2 = () => { fun3(); };
103+
const fun3 = () => { log_stack(); };
104+
function log_stack() {
105+
Error.captureStackTrace(o, fun3);
106+
}
107+
fun1();
108+
109+
Error.captureStackTrace(o);
110+
111+
assert(!o.stack.includes('fun3'));
112+
assert(!o.stack.includes('log_stack'));
113+
}
114+
84115
function my_func(a, b)
85116
{
86117
return a + b;
@@ -1051,4 +1082,6 @@ test_exception_source_pos();
10511082
test_function_source_pos();
10521083
test_exception_prepare_stack();
10531084
test_exception_stack_size_limit();
1085+
test_exception_capture_stack_trace();
1086+
test_exception_capture_stack_trace_filter();
10541087
test_cur_pc();

0 commit comments

Comments
 (0)