Skip to content

Commit 4c32c53

Browse files
authored
Record stack trace for non-object exceptions (#805)
Consider this script that throws an uncaught exception: function f() { throw "fail" } f() Running with `qjs --script t.js` used to print merely: fail But now prints: fail at f (t.js:2:11) at f (t.js:4:1) qjs has been updated but no public API yet because I still need to think through the corner cases. Includes a one-line fix for line:column reporting for throw statements. Refs: #799
1 parent acf63fa commit 4c32c53

File tree

3 files changed

+64
-42
lines changed

3 files changed

+64
-42
lines changed

gen/function_source.c

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
#include "quickjs-libc.h"
44

5-
const uint32_t qjsc_function_source_size = 320;
5+
const uint32_t qjsc_function_source_size = 324;
66

7-
const uint8_t qjsc_function_source[320] = {
7+
const uint8_t qjsc_function_source[324] = {
88
0x13, 0x05, 0x01, 0x30, 0x74, 0x65, 0x73, 0x74,
99
0x73, 0x2f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
1010
0x6f, 0x6e, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63,
@@ -42,9 +42,10 @@ const uint8_t qjsc_function_source[320] = {
4242
0x00, 0xb0, 0xeb, 0x0b, 0x39, 0x95, 0x00, 0x00,
4343
0x00, 0x63, 0x02, 0x00, 0xf0, 0x30, 0x69, 0x02,
4444
0x00, 0x69, 0x01, 0x00, 0x06, 0x2f, 0xc0, 0x03,
45-
0x01, 0x01, 0x14, 0x00, 0x1c, 0x0a, 0x2a, 0x26,
46-
0x03, 0x39, 0x28, 0x00, 0x10, 0x08, 0x27, 0x11,
47-
0x12, 0x67, 0x0d, 0x26, 0x03, 0x39, 0x28, 0x00,
45+
0x01, 0x01, 0x18, 0x00, 0x1c, 0x0a, 0x2a, 0x26,
46+
0x03, 0x20, 0x1c, 0x1b, 0x0c, 0x00, 0x10, 0x08,
47+
0x27, 0x11, 0x12, 0x67, 0x0d, 0x26, 0x03, 0x20,
48+
0x1c, 0x1b, 0x0c, 0x00,
4849
};
4950

5051
static JSContext *JS_NewCustomContext(JSRuntime *rt)

quickjs-libc.c

+5-3
Original file line numberDiff line numberDiff line change
@@ -4159,9 +4159,11 @@ static void js_std_dump_error1(JSContext *ctx, JSValue exception_val)
41594159
js_dump_obj(ctx, stderr, exception_val);
41604160
if (is_error) {
41614161
val = JS_GetPropertyStr(ctx, exception_val, "stack");
4162-
if (!JS_IsUndefined(val)) {
4163-
js_dump_obj(ctx, stderr, val);
4164-
}
4162+
} else {
4163+
js_std_cmd(/*ErrorBackTrace*/2, ctx, &val);
4164+
}
4165+
if (!JS_IsUndefined(val)) {
4166+
js_dump_obj(ctx, stderr, val);
41654167
JS_FreeValue(ctx, val);
41664168
}
41674169
}

quickjs.c

+53-34
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ struct JSContext {
394394
JSValue promise_ctor;
395395
JSValue native_error_proto[JS_NATIVE_ERROR_COUNT];
396396
JSValue error_ctor;
397+
JSValue error_back_trace;
397398
JSValue error_prepare_stack;
398399
int error_stack_trace_limit;
399400
JSValue iterator_ctor;
@@ -2305,6 +2306,7 @@ JSContext *JS_NewContextRaw(JSRuntime *rt)
23052306
ctx->regexp_ctor = JS_NULL;
23062307
ctx->promise_ctor = JS_NULL;
23072308
ctx->error_ctor = JS_NULL;
2309+
ctx->error_back_trace = JS_UNDEFINED;
23082310
ctx->error_prepare_stack = JS_UNDEFINED;
23092311
ctx->error_stack_trace_limit = 10;
23102312
init_list_head(&ctx->loaded_modules);
@@ -2424,6 +2426,7 @@ static void JS_MarkContext(JSRuntime *rt, JSContext *ctx,
24242426
JS_MarkValue(rt, ctx->native_error_proto[i], mark_func);
24252427
}
24262428
JS_MarkValue(rt, ctx->error_ctor, mark_func);
2429+
JS_MarkValue(rt, ctx->error_back_trace, mark_func);
24272430
JS_MarkValue(rt, ctx->error_prepare_stack, mark_func);
24282431
for(i = 0; i < rt->class_count; i++) {
24292432
JS_MarkValue(rt, ctx->class_proto[i], mark_func);
@@ -2491,6 +2494,7 @@ void JS_FreeContext(JSContext *ctx)
24912494
JS_FreeValue(ctx, ctx->native_error_proto[i]);
24922495
}
24932496
JS_FreeValue(ctx, ctx->error_ctor);
2497+
JS_FreeValue(ctx, ctx->error_back_trace);
24942498
JS_FreeValue(ctx, ctx->error_prepare_stack);
24952499
for(i = 0; i < rt->class_count; i++) {
24962500
JS_FreeValue(ctx, ctx->class_proto[i]);
@@ -6616,14 +6620,28 @@ static const char *get_func_name(JSContext *ctx, JSValue func)
66166620
return JS_ToCString(ctx, val);
66176621
}
66186622

6623+
/* Note: it is important that no exception is returned by this function */
6624+
static bool can_add_backtrace(JSValue obj)
6625+
{
6626+
JSObject *p;
6627+
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
6628+
return false;
6629+
p = JS_VALUE_GET_OBJ(obj);
6630+
if (p->class_id != JS_CLASS_ERROR)
6631+
return false;
6632+
if (find_own_property1(p, JS_ATOM_stack))
6633+
return false;
6634+
return true;
6635+
}
6636+
66196637
#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0)
66206638
/* only taken into account if filename is provided */
66216639
#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1)
66226640
#define JS_BACKTRACE_FLAG_FILTER_FUNC (1 << 2)
66236641

66246642
/* if filename != NULL, an additional level is added with the filename
66256643
and line number information (used for parse error). */
6626-
static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_func,
6644+
static void build_backtrace(JSContext *ctx, JSValue error_val, JSValue filter_func,
66276645
const char *filename, int line_num, int col_num,
66286646
int backtrace_flags)
66296647
{
@@ -6634,7 +6652,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
66346652
const char *str1;
66356653
JSObject *p;
66366654
JSFunctionBytecode *b;
6637-
bool backtrace_barrier, has_prepare;
6655+
bool backtrace_barrier, has_prepare, has_filter_func;
66386656
JSRuntime *rt;
66396657
JSCallSiteData csd[64];
66406658
uint32_t i;
@@ -6646,6 +6664,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
66466664
stack_trace_limit = max_int(stack_trace_limit, 0);
66476665
rt = ctx->rt;
66486666
has_prepare = false;
6667+
has_filter_func = backtrace_flags & JS_BACKTRACE_FLAG_FILTER_FUNC;
66496668
i = 0;
66506669

66516670
if (!rt->in_prepare_stack_trace && !JS_IsNull(ctx->error_ctor)) {
@@ -6681,7 +6700,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
66816700

66826701
/* Find the frame we want to start from. Note that when a filter is used the filter
66836702
function will be the first, but we also specify we want to skip the first one. */
6684-
if (backtrace_flags & JS_BACKTRACE_FLAG_FILTER_FUNC) {
6703+
if (has_filter_func) {
66856704
for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) {
66866705
if (js_same_value(ctx, sf->cur_func, filter_func)) {
66876706
sf_start = sf;
@@ -6717,23 +6736,24 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
67176736
dbuf_printf(&dbuf, " at %s", str1);
67186737
JS_FreeCString(ctx, func_name_str);
67196738

6720-
if (b) {
6739+
if (b && sf->cur_pc) {
67216740
const char *atom_str;
67226741
int line_num1, col_num1;
6742+
uint32_t pc;
67236743

6724-
/* Bytecode functions must have cur_pc set in the stack frame. */
6725-
if (sf->cur_pc == NULL)
6726-
abort();
6727-
6728-
line_num1 = find_line_num(ctx, b,
6729-
sf->cur_pc - b->byte_code_buf - 1,
6730-
&col_num1);
6744+
pc = sf->cur_pc - b->byte_code_buf - 1;
6745+
line_num1 = find_line_num(ctx, b, pc, &col_num1);
67316746
atom_str = b->filename ? JS_AtomToCString(ctx, b->filename) : NULL;
67326747
dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : "<null>");
67336748
JS_FreeCString(ctx, atom_str);
67346749
if (line_num1 != -1)
67356750
dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1);
67366751
dbuf_putc(&dbuf, ')');
6752+
} else if (b) {
6753+
// FIXME(bnoordhuis) Missing `sf->cur_pc = pc` in bytecode
6754+
// handler in JS_CallInternal. Almost never user observable
6755+
// except with intercepting JS proxies that throw exceptions.
6756+
dbuf_printf(&dbuf, " (missing)");
67376757
} else {
67386758
dbuf_printf(&dbuf, " (native)");
67396759
}
@@ -6769,7 +6789,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
67696789
JS_FreeValue(ctx, csd[k].func_name);
67706790
}
67716791
JSValue args[] = {
6772-
error_obj,
6792+
error_val,
67736793
stack,
67746794
};
67756795
JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args);
@@ -6790,21 +6810,14 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
67906810
}
67916811

67926812
rt->in_prepare_stack_trace = false;
6793-
JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, stack, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
6794-
}
6795-
6796-
/* Note: it is important that no exception is returned by this function */
6797-
static bool is_backtrace_needed(JSContext *ctx, JSValue obj)
6798-
{
6799-
JSObject *p;
6800-
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
6801-
return false;
6802-
p = JS_VALUE_GET_OBJ(obj);
6803-
if (p->class_id != JS_CLASS_ERROR)
6804-
return false;
6805-
if (find_own_property1(p, JS_ATOM_stack))
6806-
return false;
6807-
return true;
6813+
if (JS_IsUndefined(ctx->error_back_trace))
6814+
ctx->error_back_trace = js_dup(stack);
6815+
if (has_filter_func || can_add_backtrace(error_val)) {
6816+
JS_DefinePropertyValue(ctx, error_val, JS_ATOM_stack, stack,
6817+
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
6818+
} else {
6819+
JS_FreeValue(ctx, stack);
6820+
}
68086821
}
68096822

68106823
JSValue JS_NewError(JSContext *ctx)
@@ -17432,13 +17445,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
1743217445
}
1743317446
}
1743417447
exception:
17435-
if (is_backtrace_needed(ctx, rt->current_exception)) {
17436-
/* add the backtrace information now (it is not done
17437-
before if the exception happens in a bytecode
17438-
operation */
17439-
sf->cur_pc = pc;
17440-
build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, NULL, 0, 0, 0);
17441-
}
17448+
sf->cur_pc = pc;
17449+
build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, NULL, 0, 0, 0);
1744217450
if (!JS_IsUncatchableError(ctx, rt->current_exception)) {
1744317451
while (sp > stack_buf) {
1744417452
JSValue val = *--sp;
@@ -17453,6 +17461,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
1745317461
} else {
1745417462
*sp++ = rt->current_exception;
1745517463
rt->current_exception = JS_UNINITIALIZED;
17464+
JS_FreeValueRT(rt, ctx->error_back_trace);
17465+
ctx->error_back_trace = JS_UNDEFINED;
1745617466
pc = b->byte_code_buf + pos;
1745717467
goto restart;
1745817468
}
@@ -25570,6 +25580,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
2557025580
js_parse_error(s, "line terminator not allowed after throw");
2557125581
goto fail;
2557225582
}
25583+
emit_source_loc(s);
2557325584
if (js_parse_expr(s))
2557425585
goto fail;
2557525586
emit_op(s, OP_throw);
@@ -56168,7 +56179,9 @@ bool JS_DetectModule(const char *input, size_t input_len)
5616856179
}
5616956180

5617056181
uintptr_t js_std_cmd(int cmd, ...) {
56182+
JSContext *ctx;
5617156183
JSRuntime *rt;
56184+
JSValue *pv;
5617256185
uintptr_t rv;
5617356186
va_list ap;
5617456187

@@ -56183,6 +56196,12 @@ uintptr_t js_std_cmd(int cmd, ...) {
5618356196
rt = va_arg(ap, JSRuntime *);
5618456197
rt->libc_opaque = va_arg(ap, void *);
5618556198
break;
56199+
case 2: // ErrorBackTrace
56200+
ctx = va_arg(ap, JSContext *);
56201+
pv = va_arg(ap, JSValue *);
56202+
*pv = ctx->error_back_trace;
56203+
ctx->error_back_trace = JS_UNDEFINED;
56204+
break;
5618656205
default:
5618756206
rv = -1;
5618856207
}

0 commit comments

Comments
 (0)