diff --git a/source/cell.c b/source/cell.c index bcba91dc..faa974fb 100644 --- a/source/cell.c +++ b/source/cell.c @@ -574,7 +574,21 @@ int cell_init(int argc, char **argv) if (JS_IsException(result)) { JSValue exc = JS_GetException(ctx); const char *str = JS_ToCString(ctx, exc); - if (str) { printf("Exception: %s\n", str); JS_FreeCString(ctx, str); } + if (str) { printf("Error: %s\n", str); JS_FreeCString(ctx, str); } + 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 line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line")); + int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column")); + printf(" at %s (%s:%d:%d)\n", fn ? fn : "", file ? file : "", line, col); + } + cJSON_Delete(stack); + } + JS_FreeValue(ctx, exc); } else if (!JS_IsNull(result)) { const char *str = JS_ToCString(ctx, result); if (str) { printf("%s\n", str); JS_FreeCString(ctx, str); } @@ -716,6 +730,20 @@ int cell_init(int argc, char **argv) printf("Error: %s\n", err_str); JS_FreeCString(ctx, err_str); } + 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 line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line")); + int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column")); + printf(" at %s (%s:%d:%d)\n", fn ? fn : "", file ? file : "", line, col); + } + cJSON_Delete(stack); + } + JS_FreeValue(ctx, exc); exit_code = 1; } else if (!JS_IsNull(result)) { const char *str = JS_ToCString(ctx, result); diff --git a/source/quickjs.c b/source/quickjs.c index 294d2964..19cabe9e 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -390,6 +390,10 @@ struct JSRuntime { /* Interrupt handler for checking execution limits */ JSInterruptHandler *interrupt_handler; void *interrupt_opaque; + + /* Register VM frame tracking for stack traces */ + void *current_register_frame; /* JSFrameRegister* at exception time */ + uint32_t current_register_pc; /* PC at exception time */ }; struct JSClass { @@ -462,6 +466,8 @@ typedef struct JSFrameRegister { typedef uint32_t MachInstr32; +typedef struct { uint16_t line; uint16_t col; } MachLineEntry; + /* Encoding macros */ #define MACH_ABC(op, a, b, c) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(b)<<16) | ((uint32_t)(c)<<24)) #define MACH_ABx(op, a, bx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(bx)<<16)) @@ -633,6 +639,9 @@ typedef struct JSCodeRegister { /* Debug info */ JSValue name; /* function name (stone text) */ + MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */ + char *filename_cstr; /* plain C string for stack trace (js_malloc_rt) */ + char *name_cstr; /* plain C string for stack trace (js_malloc_rt) */ } JSCodeRegister; /* Pre-parsed MCODE for a single function (off-heap, never GC'd). @@ -652,6 +661,9 @@ typedef struct JSMCode { uint32_t func_count; /* Keep root cJSON alive (owns all the cJSON nodes instrs[] point into) */ cJSON *json_root; + MachLineEntry *line_table; /* [instr_count], parallel to instrs[] */ + const char *name; /* function name (points into cJSON tree) */ + const char *filename; /* source filename (points into cJSON tree) */ } JSMCode; /* Frame for closures - used by link-time relocation model where closures @@ -2067,6 +2079,7 @@ static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int a static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +cJSON *JS_GetStack(JSContext *ctx); JSValue JS_ThrowOutOfMemory (JSContext *ctx); static JSValue JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx); @@ -22899,6 +22912,23 @@ static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *ar return JS_NULL; } +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_GetObjectItem(fr, "function")); + const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file")); + int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line")); + int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column")); + printf(" at %s (%s:%d:%d)\n", fn ? fn : "", file ? file : "", line, col); + } + cJSON_Delete(stack); + } + return JS_NULL; +} + /* ---------------------------------------------------------------------------- * Bytecode dump function (always available, for debugging) * ---------------------------------------------------------------------------- @@ -26042,6 +26072,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) { /* I/O functions */ js_set_global_cfunc(ctx, "print", js_print, -1); /* variadic: length < 0 means no arg limit */ + js_set_global_cfunc(ctx, "stacktrace", js_stacktrace, 0); } } @@ -30770,6 +30801,8 @@ typedef struct MachCode { struct MachCode **functions; char *name; /* owned C string, or NULL */ + MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */ + char *filename; /* source filename (sys_malloc'd) */ } MachCode; /* ---- Compiler state ---- */ @@ -30816,6 +30849,12 @@ typedef struct MachCompState { /* Error tracking */ int has_error; + + /* Line tracking for debug info */ + int cur_line, cur_col; + MachLineEntry *line_info; /* growable, parallel to code[] */ + int line_capacity; + const char *filename; /* pointer into AST cJSON (not owned) */ } MachCompState; /* Forward declarations */ @@ -30824,12 +30863,25 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt); /* ---- Compiler helpers ---- */ +static void mach_set_pos(MachCompState *cs, cJSON *node) { + cJSON *r = cJSON_GetObjectItem(node, "from_row"); + cJSON *c = cJSON_GetObjectItem(node, "from_column"); + if (r) cs->cur_line = (int)r->valuedouble + 1; + if (c) cs->cur_col = (int)c->valuedouble + 1; +} + static void mach_emit(MachCompState *cs, MachInstr32 instr) { if (cs->code_count >= cs->code_capacity) { int new_cap = cs->code_capacity ? cs->code_capacity * 2 : 64; cs->code = sys_realloc(cs->code, new_cap * sizeof(MachInstr32)); cs->code_capacity = new_cap; } + if (cs->code_count >= cs->line_capacity) { + int new_cap = cs->line_capacity ? cs->line_capacity * 2 : 64; + cs->line_info = sys_realloc(cs->line_info, new_cap * sizeof(MachLineEntry)); + cs->line_capacity = new_cap; + } + cs->line_info[cs->code_count] = (MachLineEntry){cs->cur_line, cs->cur_col}; cs->code[cs->code_count++] = instr; } @@ -30998,6 +31050,7 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { return dest; } + mach_set_pos(cs, node); const char *kind = cJSON_GetStringValue(cJSON_GetObjectItem(node, "kind")); if (!kind) { if (dest < 0) dest = mach_reserve_reg(cs); @@ -31459,6 +31512,7 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { MachCompState child = {0}; child.parent = cs; child.scopes = cs->scopes; + child.filename = cs->filename; child.freereg = 1; /* slot 0 = this */ /* Read function_nr from AST node */ @@ -31516,6 +31570,8 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { fn_code->cpool = child.cpool; fn_code->func_count = child.func_count; fn_code->functions = child.functions; + fn_code->line_table = child.line_info; + fn_code->filename = cs->filename ? strdup(cs->filename) : NULL; cJSON *fname = cJSON_GetObjectItem(node, "name"); if (fname && cJSON_IsString(fname)) { @@ -31546,6 +31602,7 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { if (!stmt) return; + mach_set_pos(cs, stmt); const char *kind = cJSON_GetStringValue(cJSON_GetObjectItem(stmt, "kind")); if (!kind) return; @@ -31817,6 +31874,9 @@ static MachCode *mach_compile_program(MachCompState *cs, cJSON *ast) { cs->scopes = cJSON_GetObjectItem(ast, "scopes"); cs->function_nr = 0; + /* Extract filename for debug info */ + cs->filename = cJSON_GetStringValue(cJSON_GetObjectItem(ast, "filename")); + /* Scan scope record for declarations */ mach_scan_scope(cs); @@ -31861,6 +31921,8 @@ static MachCode *mach_compile_program(MachCompState *cs, cJSON *ast) { code->func_count = cs->func_count; code->functions = cs->functions; code->name = NULL; + code->line_table = cs->line_info; + code->filename = cs->filename ? strdup(cs->filename) : NULL; return code; } @@ -31898,6 +31960,8 @@ void JS_FreeMachCode(MachCode *mc) { JS_FreeMachCode(mc->functions[i]); sys_free(mc->functions); sys_free(mc->name); + sys_free(mc->line_table); + sys_free(mc->filename); sys_free(mc); } @@ -31928,6 +31992,12 @@ JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) { /* Intern function name */ code->name = mc->name ? js_key_new(ctx, mc->name) : JS_NULL; + /* Transfer debug info */ + code->line_table = mc->line_table; + mc->line_table = NULL; + code->filename_cstr = mc->filename ? js_strdup_rt(mc->filename) : NULL; + code->name_cstr = mc->name ? js_strdup_rt(mc->name) : NULL; + /* Link: resolve GETNAME to GETENV/GETINTRINSIC */ mach_link_code(ctx, code, env); @@ -31943,6 +32013,9 @@ static void js_free_code_register(JSCodeRegister *code) { js_free_code_register(code->functions[i]); } js_free_rt(code->functions); + js_free_rt(code->line_table); + js_free_rt(code->filename_cstr); + js_free_rt(code->name_cstr); js_free_rt(code); } @@ -31973,17 +32046,31 @@ static JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count) { /* Create a register-based function from JSCodeRegister */ static JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue env, JSValue outer_frame) { + /* Protect env and outer_frame from GC — js_mallocz can trigger + collection which moves heap objects, invalidating stack-local copies */ + JSGCRef env_ref, frame_ref; + JS_PushGCRef(ctx, &env_ref); + env_ref.val = env; + JS_PushGCRef(ctx, &frame_ref); + frame_ref.val = outer_frame; + JSFunction *fn = js_mallocz(ctx, sizeof(JSFunction)); - if (!fn) return JS_EXCEPTION; + if (!fn) { + JS_PopGCRef(ctx, &frame_ref); + JS_PopGCRef(ctx, &env_ref); + return JS_EXCEPTION; + } fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0); fn->kind = JS_FUNC_KIND_REGISTER; fn->length = code->arity; fn->name = code->name; fn->u.reg.code = code; - fn->u.reg.env_record = env; - fn->u.reg.outer_frame = outer_frame; + fn->u.reg.env_record = env_ref.val; + fn->u.reg.outer_frame = frame_ref.val; + JS_PopGCRef(ctx, &frame_ref); + JS_PopGCRef(ctx, &env_ref); return JS_MKPTR(fn); } @@ -32453,8 +32540,11 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val); if (fn->kind == JS_FUNC_KIND_C) { /* C function: call directly with args from consecutive slots */ + ctx->reg_current_frame = frame_ref.val; + ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; JSValue ret = js_call_c_function(ctx, func_val, JS_NULL, nargs, &frame->slots[base + 1]); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + ctx->reg_current_frame = JS_NULL; if (JS_IsException(ret)) { result = ret; goto done; } if (nresults > 0) frame->slots[base] = ret; } else if (fn->kind == JS_FUNC_KIND_REGISTER) { @@ -32572,6 +32662,10 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, } done: + if (JS_IsException(result)) { + ctx->reg_current_frame = frame_ref.val; + ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; + } JS_DeleteGCRef(ctx, &frame_ref); return result; } @@ -32616,6 +32710,10 @@ typedef struct MachGenState { /* Error tracking */ cJSON *errors; int has_error; + + /* Line tracking for debug info */ + int cur_line, cur_col; + const char *filename; } MachGenState; static int mach_gen_expr (MachGenState *s, cJSON *expr, int target); @@ -32740,17 +32838,30 @@ static void mach_gen_emit_label (MachGenState *s, const char *label) { cJSON_AddItemToArray (s->instructions, item); } +static void mach_gen_set_pos (MachGenState *s, cJSON *node) { + cJSON *r = cJSON_GetObjectItem (node, "from_row"); + cJSON *c = cJSON_GetObjectItem (node, "from_column"); + if (r) s->cur_line = (int)r->valuedouble + 1; + if (c) s->cur_col = (int)c->valuedouble + 1; +} + +static void mach_gen_add_instr (MachGenState *s, cJSON *instr) { + cJSON_AddItemToArray (instr, cJSON_CreateNumber (s->cur_line)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (s->cur_col)); + cJSON_AddItemToArray (s->instructions, instr); +} + static void mach_gen_emit_0 (MachGenState *s, const char *op) { cJSON *instr = cJSON_CreateArray (); cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_1 (MachGenState *s, const char *op, int a) { cJSON *instr = cJSON_CreateArray (); cJSON_AddItemToArray (instr, cJSON_CreateString (op)); cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_2 (MachGenState *s, const char *op, int a, int b) { @@ -32758,7 +32869,7 @@ static void mach_gen_emit_2 (MachGenState *s, const char *op, int a, int b) { cJSON_AddItemToArray (instr, cJSON_CreateString (op)); cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_3 (MachGenState *s, const char *op, int a, int b, int c) { @@ -32767,7 +32878,7 @@ static void mach_gen_emit_3 (MachGenState *s, const char *op, int a, int b, int cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); cJSON_AddItemToArray (instr, cJSON_CreateNumber (c)); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_const_num (MachGenState *s, int dest, double val) { @@ -32775,7 +32886,7 @@ static void mach_gen_emit_const_num (MachGenState *s, int dest, double val) { cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); cJSON_AddItemToArray (instr, cJSON_CreateNumber (val)); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_const_str (MachGenState *s, int dest, const char *val) { @@ -32783,7 +32894,7 @@ static void mach_gen_emit_const_str (MachGenState *s, int dest, const char *val) cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); cJSON_AddItemToArray (instr, cJSON_CreateString (val ? val : "")); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_const_bool (MachGenState *s, int dest, int val) { @@ -32798,7 +32909,7 @@ static void mach_gen_emit_jump (MachGenState *s, const char *label) { cJSON *instr = cJSON_CreateArray (); cJSON_AddItemToArray (instr, cJSON_CreateString ("jump")); cJSON_AddItemToArray (instr, cJSON_CreateString (label)); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_jump_cond (MachGenState *s, const char *op, int slot, const char *label) { @@ -32806,7 +32917,7 @@ static void mach_gen_emit_jump_cond (MachGenState *s, const char *op, int slot, cJSON_AddItemToArray (instr, cJSON_CreateString (op)); cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot)); cJSON_AddItemToArray (instr, cJSON_CreateString (label)); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_get_prop (MachGenState *s, int dest, int obj, const char *prop) { @@ -32815,7 +32926,7 @@ static void mach_gen_emit_get_prop (MachGenState *s, int dest, int obj, const ch cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_set_prop (MachGenState *s, int obj, const char *prop, int val) { @@ -32824,7 +32935,7 @@ static void mach_gen_emit_set_prop (MachGenState *s, int obj, const char *prop, cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); cJSON_AddItemToArray (instr, cJSON_CreateNumber (val)); cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); - cJSON_AddItemToArray (s->instructions, instr); + mach_gen_add_instr (s, instr); } static void mach_gen_emit_get_elem (MachGenState *s, int dest, int obj, int idx) { @@ -33096,6 +33207,7 @@ static cJSON *mach_gen_function (MachGenState *parent, cJSON *func_node); static int mach_gen_expr (MachGenState *s, cJSON *expr, int target) { if (!expr) return -1; + mach_gen_set_pos (s, expr); const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "kind")); if (!kind) return -1; @@ -33434,6 +33546,7 @@ static int mach_gen_expr (MachGenState *s, cJSON *expr, int target) { static void mach_gen_statement (MachGenState *s, cJSON *stmt) { if (!stmt) return; + mach_gen_set_pos (s, stmt); const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "kind")); if (!kind) return; @@ -33741,6 +33854,7 @@ static cJSON *mach_gen_function (MachGenState *parent, cJSON *func_node) { s.scopes = parent->scopes; s.errors = parent->errors; s.has_error = parent->has_error; + s.filename = parent->filename; cJSON *result = cJSON_CreateObject (); @@ -33750,6 +33864,9 @@ static cJSON *mach_gen_function (MachGenState *parent, cJSON *func_node) { else cJSON_AddStringToObject (result, "name", ""); + if (s.filename) + cJSON_AddStringToObject (result, "filename", s.filename); + cJSON *is_arrow = cJSON_GetObjectItem (func_node, "arrow"); s.is_arrow = is_arrow && cJSON_IsTrue (is_arrow); @@ -33860,6 +33977,9 @@ static cJSON *mach_gen_program (MachGenState *s, cJSON *ast) { cJSON *result = cJSON_CreateObject (); const char *filename = cJSON_GetStringValue (cJSON_GetObjectItem (ast, "filename")); cJSON_AddStringToObject (result, "name", filename ? filename : ""); + s->filename = filename; + if (filename) + cJSON_AddStringToObject (result, "filename", filename); s->data = cJSON_AddObjectToObject (result, "data"); s->functions = cJSON_AddArrayToObject (result, "functions"); @@ -34024,6 +34144,27 @@ static JSMCode *jsmcode_parse_one(cJSON *func_def) { } code->instr_count = idx; + /* Extract line table from trailing numbers in each instruction array */ + if (idx > 0) { + code->line_table = js_mallocz_rt(idx * sizeof(MachLineEntry)); + for (uint32_t i = 0; i < idx; i++) { + cJSON *instr = code->instrs[i]; + int n = cJSON_GetArraySize(instr); + if (n >= 2) { + cJSON *line_item = cJSON_GetArrayItem(instr, n - 2); + cJSON *col_item = cJSON_GetArrayItem(instr, n - 1); + if (cJSON_IsNumber(line_item) && cJSON_IsNumber(col_item)) { + code->line_table[i].line = (uint16_t)line_item->valuedouble; + code->line_table[i].col = (uint16_t)col_item->valuedouble; + } + } + } + } + + /* Extract name and filename from function definition */ + code->name = cJSON_GetStringValue(cJSON_GetObjectItem(func_def, "name")); + code->filename = cJSON_GetStringValue(cJSON_GetObjectItem(func_def, "filename")); + return code; } @@ -34063,6 +34204,7 @@ static void jsmcode_free(JSMCode *code) { /* Don't free functions[i]->functions — it's the shared pointer */ if (code->functions[i]->instrs) js_free_rt(code->functions[i]->instrs); if (code->functions[i]->labels) js_free_rt(code->functions[i]->labels); + if (code->functions[i]->line_table) js_free_rt(code->functions[i]->line_table); js_free_rt(code->functions[i]); } } @@ -34070,6 +34212,7 @@ static void jsmcode_free(JSMCode *code) { } if (code->instrs) js_free_rt(code->instrs); if (code->labels) js_free_rt(code->labels); + if (code->line_table) js_free_rt(code->line_table); if (code->json_root) cJSON_Delete(code->json_root); js_free_rt(code); } @@ -34693,8 +34836,11 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, if (JS_IsNull(new_frame->slots[i])) break; c_argv[c_argc++] = new_frame->slots[i]; } + ctx->reg_current_frame = frame_ref.val; + ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; JSValue c_result = js_call_c_function(ctx, new_frame->function, new_frame->slots[0], c_argc, c_argv); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + ctx->reg_current_frame = JS_NULL; if (JS_IsException(c_result)) { result = c_result; goto done; } frame->slots[ret_reg] = c_result; } else { @@ -34858,10 +35004,73 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, } done: + if (JS_IsException(result)) { + ctx->reg_current_frame = frame_ref.val; + ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; + } JS_DeleteGCRef(ctx, &frame_ref); return result; } +/* Public API: get stack trace as cJSON array */ +cJSON *JS_GetStack(JSContext *ctx) { + JSRuntime *rt = ctx->rt; + if (JS_IsNull(ctx->reg_current_frame)) return NULL; + JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); + uint32_t cur_pc = rt->current_register_pc; + + cJSON *arr = cJSON_CreateArray(); + int is_first = 1; + + while (frame) { + 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); + } + if (code->line_table && pc < code->instr_count) { + line = code->line_table[pc].line; + col = code->line_table[pc].col; + } + } else if (fn->kind == JS_FUNC_KIND_MCODE && fn->u.mcode.code) { + JSMCode *code = fn->u.mcode.code; + file = code->filename; + func_name = code->name; + if (!is_first) { + pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); + } + if (code->line_table && pc < code->instr_count) { + line = code->line_table[pc].line; + col = code->line_table[pc].col; + } + } + + cJSON *entry = cJSON_CreateObject(); + cJSON_AddStringToObject(entry, "function", func_name ? func_name : ""); + cJSON_AddStringToObject(entry, "file", file ? file : ""); + 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; +} + /* Public API: parse MCODE JSON and execute */ JSValue JS_CallMcode(JSContext *ctx, const char *mcode_json) { cJSON *root = cJSON_Parse(mcode_json); diff --git a/source/quickjs.h b/source/quickjs.h index b9ce74c3..b434b527 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -1260,6 +1260,11 @@ char *JS_Mcode (const char *ast_json); Returns result of execution, or JS_EXCEPTION on error. */ JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json); +/* Get stack trace as cJSON array of frame objects. + Returns NULL if no register VM frame is active. + Caller must call cJSON_Delete() on the result. */ +struct cJSON *JS_GetStack (JSContext *ctx); + /* Integrate a CellModule with an environment and execute. Returns callable function value, or JS_EXCEPTION on error. */ JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env);