Skip to content

Commit a651bbe

Browse files
committed
Record stack trace for non-object exceptions
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. Refs: quickjs-ng#799
1 parent f106b7c commit a651bbe

File tree

2 files changed

+54
-35
lines changed

2 files changed

+54
-35
lines changed

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

+49-32
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
{
@@ -6717,23 +6735,24 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
67176735
dbuf_printf(&dbuf, " at %s", str1);
67186736
JS_FreeCString(ctx, func_name_str);
67196737

6720-
if (b) {
6738+
if (b && sf->cur_pc) {
67216739
const char *atom_str;
67226740
int line_num1, col_num1;
6741+
uint32_t pc;
67236742

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);
6743+
pc = sf->cur_pc - b->byte_code_buf - 1;
6744+
line_num1 = find_line_num(ctx, b, pc, &col_num1);
67316745
atom_str = b->filename ? JS_AtomToCString(ctx, b->filename) : NULL;
67326746
dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : "<null>");
67336747
JS_FreeCString(ctx, atom_str);
67346748
if (line_num1 != -1)
67356749
dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1);
67366750
dbuf_putc(&dbuf, ')');
6751+
} else if (b) {
6752+
// FIXME(bnoordhuis) Missing `sf->cur_pc = pc` in bytecode
6753+
// handler in JS_CallInternal. Almost never user observable
6754+
// except with intercepting JS proxies that throw exceptions.
6755+
dbuf_printf(&dbuf, " (missing)");
67376756
} else {
67386757
dbuf_printf(&dbuf, " (native)");
67396758
}
@@ -6769,7 +6788,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
67696788
JS_FreeValue(ctx, csd[k].func_name);
67706789
}
67716790
JSValue args[] = {
6772-
error_obj,
6791+
error_val,
67736792
stack,
67746793
};
67756794
JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args);
@@ -6790,21 +6809,14 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
67906809
}
67916810

67926811
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;
6812+
if (JS_IsUndefined(ctx->error_back_trace))
6813+
ctx->error_back_trace = js_dup(stack);
6814+
if (can_add_backtrace(error_val)) {
6815+
JS_DefinePropertyValue(ctx, error_val, JS_ATOM_stack, stack,
6816+
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
6817+
} else {
6818+
JS_FreeValue(ctx, stack);
6819+
}
68086820
}
68096821

68106822
JSValue JS_NewError(JSContext *ctx)
@@ -17432,13 +17444,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
1743217444
}
1743317445
}
1743417446
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-
}
17447+
sf->cur_pc = pc;
17448+
build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, NULL, 0, 0, 0);
1744217449
if (!JS_IsUncatchableError(ctx, rt->current_exception)) {
1744317450
while (sp > stack_buf) {
1744417451
JSValue val = *--sp;
@@ -17453,6 +17460,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
1745317460
} else {
1745417461
*sp++ = rt->current_exception;
1745517462
rt->current_exception = JS_UNINITIALIZED;
17463+
JS_FreeValueRT(rt, ctx->error_back_trace);
17464+
ctx->error_back_trace = JS_UNDEFINED;
1745617465
pc = b->byte_code_buf + pos;
1745717466
goto restart;
1745817467
}
@@ -56163,7 +56172,9 @@ bool JS_DetectModule(const char *input, size_t input_len)
5616356172
}
5616456173

5616556174
uintptr_t js_std_cmd(int cmd, ...) {
56175+
JSContext *ctx;
5616656176
JSRuntime *rt;
56177+
JSValue *pv;
5616756178
uintptr_t rv;
5616856179
va_list ap;
5616956180

@@ -56178,6 +56189,12 @@ uintptr_t js_std_cmd(int cmd, ...) {
5617856189
rt = va_arg(ap, JSRuntime *);
5617956190
rt->libc_opaque = va_arg(ap, void *);
5618056191
break;
56192+
case 2: // ErrorBackTrace
56193+
ctx = va_arg(ap, JSContext *);
56194+
pv = va_arg(ap, JSValue *);
56195+
*pv = ctx->error_back_trace;
56196+
ctx->error_back_trace = JS_UNDEFINED;
56197+
break;
5618156198
default:
5618256199
rv = -1;
5618356200
}

0 commit comments

Comments
 (0)