Skip to content

Record stack trace for non-object exceptions #805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions gen/function_source.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

#include "quickjs-libc.h"

const uint32_t qjsc_function_source_size = 320;
const uint32_t qjsc_function_source_size = 324;

const uint8_t qjsc_function_source[320] = {
const uint8_t qjsc_function_source[324] = {
0x13, 0x05, 0x01, 0x30, 0x74, 0x65, 0x73, 0x74,
0x73, 0x2f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63,
Expand Down Expand Up @@ -42,9 +42,10 @@ const uint8_t qjsc_function_source[320] = {
0x00, 0xb0, 0xeb, 0x0b, 0x39, 0x95, 0x00, 0x00,
0x00, 0x63, 0x02, 0x00, 0xf0, 0x30, 0x69, 0x02,
0x00, 0x69, 0x01, 0x00, 0x06, 0x2f, 0xc0, 0x03,
0x01, 0x01, 0x14, 0x00, 0x1c, 0x0a, 0x2a, 0x26,
0x03, 0x39, 0x28, 0x00, 0x10, 0x08, 0x27, 0x11,
0x12, 0x67, 0x0d, 0x26, 0x03, 0x39, 0x28, 0x00,
0x01, 0x01, 0x18, 0x00, 0x1c, 0x0a, 0x2a, 0x26,
0x03, 0x20, 0x1c, 0x1b, 0x0c, 0x00, 0x10, 0x08,
0x27, 0x11, 0x12, 0x67, 0x0d, 0x26, 0x03, 0x20,
0x1c, 0x1b, 0x0c, 0x00,
};

static JSContext *JS_NewCustomContext(JSRuntime *rt)
Expand Down
8 changes: 5 additions & 3 deletions quickjs-libc.c
Original file line number Diff line number Diff line change
Expand Up @@ -4159,9 +4159,11 @@ static void js_std_dump_error1(JSContext *ctx, JSValue exception_val)
js_dump_obj(ctx, stderr, exception_val);
if (is_error) {
val = JS_GetPropertyStr(ctx, exception_val, "stack");
if (!JS_IsUndefined(val)) {
js_dump_obj(ctx, stderr, val);
}
} else {
js_std_cmd(/*ErrorBackTrace*/2, ctx, &val);
}
if (!JS_IsUndefined(val)) {
js_dump_obj(ctx, stderr, val);
JS_FreeValue(ctx, val);
}
}
Expand Down
87 changes: 53 additions & 34 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ struct JSContext {
JSValue promise_ctor;
JSValue native_error_proto[JS_NATIVE_ERROR_COUNT];
JSValue error_ctor;
JSValue error_back_trace;
JSValue error_prepare_stack;
int error_stack_trace_limit;
JSValue iterator_ctor;
Expand Down Expand Up @@ -2305,6 +2306,7 @@ JSContext *JS_NewContextRaw(JSRuntime *rt)
ctx->regexp_ctor = JS_NULL;
ctx->promise_ctor = JS_NULL;
ctx->error_ctor = JS_NULL;
ctx->error_back_trace = JS_UNDEFINED;
ctx->error_prepare_stack = JS_UNDEFINED;
ctx->error_stack_trace_limit = 10;
init_list_head(&ctx->loaded_modules);
Expand Down Expand Up @@ -2424,6 +2426,7 @@ static void JS_MarkContext(JSRuntime *rt, JSContext *ctx,
JS_MarkValue(rt, ctx->native_error_proto[i], mark_func);
}
JS_MarkValue(rt, ctx->error_ctor, mark_func);
JS_MarkValue(rt, ctx->error_back_trace, mark_func);
JS_MarkValue(rt, ctx->error_prepare_stack, mark_func);
for(i = 0; i < rt->class_count; i++) {
JS_MarkValue(rt, ctx->class_proto[i], mark_func);
Expand Down Expand Up @@ -2491,6 +2494,7 @@ void JS_FreeContext(JSContext *ctx)
JS_FreeValue(ctx, ctx->native_error_proto[i]);
}
JS_FreeValue(ctx, ctx->error_ctor);
JS_FreeValue(ctx, ctx->error_back_trace);
JS_FreeValue(ctx, ctx->error_prepare_stack);
for(i = 0; i < rt->class_count; i++) {
JS_FreeValue(ctx, ctx->class_proto[i]);
Expand Down Expand Up @@ -6616,14 +6620,28 @@ static const char *get_func_name(JSContext *ctx, JSValue func)
return JS_ToCString(ctx, val);
}

/* Note: it is important that no exception is returned by this function */
static bool can_add_backtrace(JSValue obj)
{
JSObject *p;
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
return false;
p = JS_VALUE_GET_OBJ(obj);
if (p->class_id != JS_CLASS_ERROR)
return false;
if (find_own_property1(p, JS_ATOM_stack))
return false;
return true;
}

#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0)
/* only taken into account if filename is provided */
#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1)
#define JS_BACKTRACE_FLAG_FILTER_FUNC (1 << 2)

/* if filename != NULL, an additional level is added with the filename
and line number information (used for parse error). */
static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_func,
static void build_backtrace(JSContext *ctx, JSValue error_val, JSValue filter_func,
const char *filename, int line_num, int col_num,
int backtrace_flags)
{
Expand All @@ -6634,7 +6652,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
const char *str1;
JSObject *p;
JSFunctionBytecode *b;
bool backtrace_barrier, has_prepare;
bool backtrace_barrier, has_prepare, has_filter_func;
JSRuntime *rt;
JSCallSiteData csd[64];
uint32_t i;
Expand All @@ -6646,6 +6664,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
stack_trace_limit = max_int(stack_trace_limit, 0);
rt = ctx->rt;
has_prepare = false;
has_filter_func = backtrace_flags & JS_BACKTRACE_FLAG_FILTER_FUNC;
i = 0;

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

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

if (b) {
if (b && sf->cur_pc) {
const char *atom_str;
int line_num1, col_num1;
uint32_t pc;

/* Bytecode functions must have cur_pc set in the stack frame. */
if (sf->cur_pc == NULL)
abort();

line_num1 = find_line_num(ctx, b,
sf->cur_pc - b->byte_code_buf - 1,
&col_num1);
pc = sf->cur_pc - b->byte_code_buf - 1;
line_num1 = find_line_num(ctx, b, pc, &col_num1);
atom_str = b->filename ? JS_AtomToCString(ctx, b->filename) : NULL;
dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : "<null>");
JS_FreeCString(ctx, atom_str);
if (line_num1 != -1)
dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1);
dbuf_putc(&dbuf, ')');
} else if (b) {
// FIXME(bnoordhuis) Missing `sf->cur_pc = pc` in bytecode
// handler in JS_CallInternal. Almost never user observable
// except with intercepting JS proxies that throw exceptions.
dbuf_printf(&dbuf, " (missing)");
} else {
dbuf_printf(&dbuf, " (native)");
}
Expand Down Expand Up @@ -6769,7 +6789,7 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
JS_FreeValue(ctx, csd[k].func_name);
}
JSValue args[] = {
error_obj,
error_val,
stack,
};
JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args);
Expand All @@ -6790,21 +6810,14 @@ static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_fu
}

rt->in_prepare_stack_trace = false;
JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, stack, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
}

/* Note: it is important that no exception is returned by this function */
static bool is_backtrace_needed(JSContext *ctx, JSValue obj)
{
JSObject *p;
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
return false;
p = JS_VALUE_GET_OBJ(obj);
if (p->class_id != JS_CLASS_ERROR)
return false;
if (find_own_property1(p, JS_ATOM_stack))
return false;
return true;
if (JS_IsUndefined(ctx->error_back_trace))
ctx->error_back_trace = js_dup(stack);
if (has_filter_func || can_add_backtrace(error_val)) {
JS_DefinePropertyValue(ctx, error_val, JS_ATOM_stack, stack,
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
} else {
JS_FreeValue(ctx, stack);
}
}

JSValue JS_NewError(JSContext *ctx)
Expand Down Expand Up @@ -17432,13 +17445,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
}
}
exception:
if (is_backtrace_needed(ctx, rt->current_exception)) {
/* add the backtrace information now (it is not done
before if the exception happens in a bytecode
operation */
sf->cur_pc = pc;
build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, NULL, 0, 0, 0);
}
sf->cur_pc = pc;
build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, NULL, 0, 0, 0);
if (!JS_IsUncatchableError(ctx, rt->current_exception)) {
while (sp > stack_buf) {
JSValue val = *--sp;
Expand All @@ -17453,6 +17461,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
} else {
*sp++ = rt->current_exception;
rt->current_exception = JS_UNINITIALIZED;
JS_FreeValueRT(rt, ctx->error_back_trace);
ctx->error_back_trace = JS_UNDEFINED;
pc = b->byte_code_buf + pos;
goto restart;
}
Expand Down Expand Up @@ -25570,6 +25580,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
js_parse_error(s, "line terminator not allowed after throw");
goto fail;
}
emit_source_loc(s);
if (js_parse_expr(s))
goto fail;
emit_op(s, OP_throw);
Expand Down Expand Up @@ -56168,7 +56179,9 @@ bool JS_DetectModule(const char *input, size_t input_len)
}

uintptr_t js_std_cmd(int cmd, ...) {
JSContext *ctx;
JSRuntime *rt;
JSValue *pv;
uintptr_t rv;
va_list ap;

Expand All @@ -56183,6 +56196,12 @@ uintptr_t js_std_cmd(int cmd, ...) {
rt = va_arg(ap, JSRuntime *);
rt->libc_opaque = va_arg(ap, void *);
break;
case 2: // ErrorBackTrace
ctx = va_arg(ap, JSContext *);
pv = va_arg(ap, JSValue *);
*pv = ctx->error_back_trace;
ctx->error_back_trace = JS_UNDEFINED;
break;
default:
rv = -1;
}
Expand Down
Loading