log stack traces
This commit is contained in:
189
source/runtime.c
189
source/runtime.c
@@ -3088,32 +3088,31 @@ static const char *const native_error_name[JS_NATIVE_ERROR_COUNT] = {
|
||||
"AggregateError",
|
||||
};
|
||||
|
||||
/* Print backtrace to stderr. Walks the register VM frame chain via
|
||||
ctx->reg_current_frame, then falls back to the C stack frame chain. */
|
||||
void print_backtrace (JSContext *ctx, const char *filename, int line_num, int col_num) {
|
||||
/* Print backtrace to stderr from a JS array of {fn, file, line, col} objects. */
|
||||
void print_backtrace (JSContext *ctx, JSValue stack, const char *filename, int line_num, int col_num) {
|
||||
if (filename) {
|
||||
fprintf (stderr, " at %s", filename);
|
||||
if (line_num != -1) fprintf (stderr, ":%d:%d", line_num, col_num);
|
||||
fputc ('\n', stderr);
|
||||
}
|
||||
|
||||
/* Walk register VM frame chain (mach / mcode) */
|
||||
if (!JS_IsNull (ctx->reg_current_frame)) {
|
||||
cJSON *stack = JS_GetStack (ctx);
|
||||
if (stack) {
|
||||
int n = cJSON_GetArraySize (stack);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON *fr = cJSON_GetArrayItem (stack, i);
|
||||
const char *fn = cJSON_GetStringValue (cJSON_GetObjectItem (fr, "function"));
|
||||
const char *file = cJSON_GetStringValue (cJSON_GetObjectItem (fr, "file"));
|
||||
int ln = (int)cJSON_GetNumberValue (cJSON_GetObjectItem (fr, "line"));
|
||||
int cn = (int)cJSON_GetNumberValue (cJSON_GetObjectItem (fr, "column"));
|
||||
fprintf (stderr, " at %s (%s:%d:%d)\n",
|
||||
fn ? fn : "<anonymous>", file ? file : "<unknown>", ln, cn);
|
||||
}
|
||||
cJSON_Delete (stack);
|
||||
}
|
||||
return;
|
||||
int64_t n = 0;
|
||||
JS_GetLength(ctx, stack, &n);
|
||||
for (int i = 0; i < (int)n; i++) {
|
||||
JSValue fr = JS_GetPropertyNumber(ctx, stack, i);
|
||||
JSValue fn_val = JS_GetPropertyStr(ctx, fr, "fn");
|
||||
JSValue file_val = JS_GetPropertyStr(ctx, fr, "file");
|
||||
const char *fn = JS_ToCString(ctx, fn_val);
|
||||
const char *file = JS_ToCString(ctx, file_val);
|
||||
int32_t ln = 0, cn = 0;
|
||||
JSValue line_v = JS_GetPropertyStr(ctx, fr, "line");
|
||||
JSValue col_v = JS_GetPropertyStr(ctx, fr, "col");
|
||||
JS_ToInt32(ctx, &ln, line_v);
|
||||
JS_ToInt32(ctx, &cn, col_v);
|
||||
fprintf (stderr, " at %s (%s:%d:%d)\n",
|
||||
fn ? fn : "<anonymous>", file ? file : "<unknown>", ln, cn);
|
||||
if (fn) JS_FreeCString(ctx, fn);
|
||||
if (file) JS_FreeCString(ctx, file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3124,7 +3123,9 @@ JSValue JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char *fmt,
|
||||
vsnprintf (buf, sizeof (buf), fmt, ap);
|
||||
fprintf (stderr, "%s: %s\n", native_error_name[error_num], buf);
|
||||
if (add_backtrace) {
|
||||
print_backtrace (ctx, NULL, 0, 0);
|
||||
JSValue stack = JS_GetStack(ctx);
|
||||
ctx->reg_current_frame = JS_NULL;
|
||||
print_backtrace (ctx, stack, NULL, 0, 0);
|
||||
}
|
||||
return JS_Throw (ctx, JS_TRUE);
|
||||
}
|
||||
@@ -8127,70 +8128,44 @@ static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *ar
|
||||
}
|
||||
|
||||
static JSValue js_stacktrace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||
cJSON *stack = JS_GetStack(ctx);
|
||||
if (stack) {
|
||||
int n = cJSON_GetArraySize(stack);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON *fr = cJSON_GetArrayItem(stack, i);
|
||||
const char *fn = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "function"));
|
||||
const char *file = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "file"));
|
||||
int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "line"));
|
||||
int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "column"));
|
||||
printf(" at %s (%s:%d:%d)\n", fn ? fn : "<anonymous>", file ? file : "<unknown>", line, col);
|
||||
}
|
||||
cJSON_Delete(stack);
|
||||
(void)this_val; (void)argc; (void)argv;
|
||||
JSValue stack = JS_GetStack(ctx);
|
||||
int64_t n = 0;
|
||||
JS_GetLength(ctx, stack, &n);
|
||||
for (int i = 0; i < (int)n; i++) {
|
||||
JSValue fr = JS_GetPropertyNumber(ctx, stack, i);
|
||||
JSValue fn_val = JS_GetPropertyStr(ctx, fr, "fn");
|
||||
JSValue file_val = JS_GetPropertyStr(ctx, fr, "file");
|
||||
const char *fn = JS_ToCString(ctx, fn_val);
|
||||
const char *file = JS_ToCString(ctx, file_val);
|
||||
int32_t line = 0, col = 0;
|
||||
JS_ToInt32(ctx, &line, JS_GetPropertyStr(ctx, fr, "line"));
|
||||
JS_ToInt32(ctx, &col, JS_GetPropertyStr(ctx, fr, "col"));
|
||||
printf(" at %s (%s:%d:%d)\n", fn ? fn : "<anonymous>", file ? file : "<unknown>", line, col);
|
||||
if (fn) JS_FreeCString(ctx, fn);
|
||||
if (file) JS_FreeCString(ctx, file);
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue js_caller_info (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||
(void)this_val;
|
||||
int depth = 0;
|
||||
if (argc > 0) JS_ToInt32(ctx, &depth, argv[0]);
|
||||
|
||||
/* Save frame pointer — JS_GetStack clears it */
|
||||
JSValue saved_frame = ctx->reg_current_frame;
|
||||
uint32_t saved_pc = ctx->current_register_pc;
|
||||
JSValue stack = JS_GetStack(ctx);
|
||||
|
||||
cJSON *stack = JS_GetStack(ctx);
|
||||
int64_t n = 0;
|
||||
JS_GetLength(ctx, stack, &n);
|
||||
|
||||
/* Restore so other callers still see the frame */
|
||||
ctx->reg_current_frame = saved_frame;
|
||||
ctx->current_register_pc = saved_pc;
|
||||
/* depth 0 = immediate caller of caller_info, which is frame index 1
|
||||
(frame 0 is caller_info itself) */
|
||||
int idx = depth + 1;
|
||||
if (idx >= (int)n) idx = (int)n - 1;
|
||||
if (idx < 0) idx = 0;
|
||||
|
||||
const char *fn_str = "<anonymous>";
|
||||
const char *file_str = "<unknown>";
|
||||
int line = 0, col = 0;
|
||||
|
||||
if (stack) {
|
||||
int n = cJSON_GetArraySize(stack);
|
||||
/* depth 0 = immediate caller of caller_info, which is frame index 1
|
||||
(frame 0 is caller_info itself) */
|
||||
int idx = depth + 1;
|
||||
if (idx >= n) idx = n - 1;
|
||||
if (idx < 0) idx = 0;
|
||||
|
||||
cJSON *fr = cJSON_GetArrayItem(stack, idx);
|
||||
const char *v;
|
||||
v = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "function"));
|
||||
if (v) fn_str = v;
|
||||
v = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "file"));
|
||||
if (v) file_str = v;
|
||||
line = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "line"));
|
||||
col = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "column"));
|
||||
}
|
||||
|
||||
JSGCRef obj;
|
||||
JS_PushGCRef(ctx, &obj);
|
||||
obj.val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, obj.val, "file", JS_NewString(ctx, file_str));
|
||||
JS_SetPropertyStr(ctx, obj.val, "line", JS_NewInt32(ctx, line));
|
||||
JS_SetPropertyStr(ctx, obj.val, "column", JS_NewInt32(ctx, col));
|
||||
JS_SetPropertyStr(ctx, obj.val, "function", JS_NewString(ctx, fn_str));
|
||||
JSValue result = obj.val;
|
||||
JS_PopGCRef(ctx, &obj);
|
||||
|
||||
if (stack) cJSON_Delete(stack);
|
||||
return result;
|
||||
if (n > 0) return JS_GetPropertyNumber(ctx, stack, idx);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -12771,49 +12746,61 @@ JSValue js_core_math_cycles_use (JSContext *ctx) {
|
||||
JS_PopGCRef (ctx, &obj_ref);
|
||||
return result;
|
||||
}
|
||||
/* Public API: get stack trace as cJSON array */
|
||||
cJSON *JS_GetStack(JSContext *ctx) {
|
||||
if (JS_IsNull(ctx->reg_current_frame)) return NULL;
|
||||
/* Public API: get stack trace as JS array of {fn, file, line, col} objects.
|
||||
Does NOT clear reg_current_frame — caller is responsible if needed.
|
||||
Two-phase approach for GC safety: Phase 1 collects raw C data on the C stack
|
||||
(pointers into stable JSCodeRegister fields), Phase 2 allocates JS objects. */
|
||||
JSValue JS_GetStack(JSContext *ctx) {
|
||||
if (JS_IsNull(ctx->reg_current_frame)) return JS_NewArray(ctx);
|
||||
|
||||
/* Phase 1: collect raw frame data (no JS allocations) */
|
||||
struct { const char *fn; const char *file; uint16_t line, col; } frames[64];
|
||||
int count = 0;
|
||||
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);
|
||||
uint32_t cur_pc = ctx->current_register_pc;
|
||||
|
||||
cJSON *arr = cJSON_CreateArray();
|
||||
int is_first = 1;
|
||||
|
||||
while (frame) {
|
||||
while (frame && count < 64) {
|
||||
if (!JS_IsFunction(frame->function)) break;
|
||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
|
||||
const char *func_name = NULL;
|
||||
const char *file = NULL;
|
||||
uint16_t line = 0, col = 0;
|
||||
uint32_t pc = is_first ? cur_pc : 0;
|
||||
|
||||
if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) {
|
||||
JSCodeRegister *code = fn->u.reg.code;
|
||||
file = code->filename_cstr;
|
||||
func_name = code->name_cstr;
|
||||
if (!is_first) {
|
||||
pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
|
||||
}
|
||||
uint32_t pc = is_first ? cur_pc : (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
|
||||
frames[count].fn = code->name_cstr;
|
||||
frames[count].file = code->filename_cstr;
|
||||
frames[count].line = 0;
|
||||
frames[count].col = 0;
|
||||
if (code->line_table && pc < code->instr_count) {
|
||||
line = code->line_table[pc].line;
|
||||
col = code->line_table[pc].col;
|
||||
frames[count].line = code->line_table[pc].line;
|
||||
frames[count].col = code->line_table[pc].col;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
cJSON *entry = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(entry, "function", func_name ? func_name : "<anonymous>");
|
||||
cJSON_AddStringToObject(entry, "file", file ? file : "<unknown>");
|
||||
cJSON_AddNumberToObject(entry, "line", line);
|
||||
cJSON_AddNumberToObject(entry, "column", col);
|
||||
cJSON_AddItemToArray(arr, entry);
|
||||
|
||||
if (JS_IsNull(frame->caller)) break;
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||
is_first = 0;
|
||||
}
|
||||
|
||||
ctx->reg_current_frame = JS_NULL;
|
||||
return arr;
|
||||
/* Phase 2: create JS objects (frame data is on C stack, safe across GC) */
|
||||
JSGCRef arr_ref;
|
||||
JS_PushGCRef(ctx, &arr_ref);
|
||||
arr_ref.val = JS_NewArray(ctx);
|
||||
for (int i = 0; i < count; i++) {
|
||||
JSGCRef item_ref;
|
||||
JS_PushGCRef(ctx, &item_ref);
|
||||
item_ref.val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "fn",
|
||||
JS_NewString(ctx, frames[i].fn ? frames[i].fn : "<anonymous>"));
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "file",
|
||||
JS_NewString(ctx, frames[i].file ? frames[i].file : "<unknown>"));
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "line", JS_NewInt32(ctx, frames[i].line));
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "col", JS_NewInt32(ctx, frames[i].col));
|
||||
JS_SetPropertyNumber(ctx, arr_ref.val, i, item_ref.val);
|
||||
JS_PopGCRef(ctx, &item_ref);
|
||||
}
|
||||
JSValue result = arr_ref.val;
|
||||
JS_PopGCRef(ctx, &arr_ref);
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user