Skip to content
New issue

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

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

Already on GitHub? # to your account

Record stack trace for non-object exceptions #805

Merged
merged 4 commits into from
Jan 24, 2025
Merged
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
@@ -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,
@@ -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)
8 changes: 5 additions & 3 deletions quickjs-libc.c
Original file line number Diff line number Diff line change
@@ -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);
}
}
87 changes: 53 additions & 34 deletions quickjs.c
Original file line number Diff line number Diff line change
@@ -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;
@@ -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);
@@ -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);
@@ -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]);
@@ -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)
{
@@ -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;
@@ -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)) {
@@ -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;
@@ -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)");
}
@@ -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);
@@ -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)
@@ -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;
@@ -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;
}
@@ -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);
@@ -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;

@@ -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;
}
Loading