diff --git a/source/quickjs.c b/source/quickjs.c index 0ee4d6e5..5651b12b 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -16438,10 +16438,60 @@ typedef struct StackSizeState { int *pc_stack; int pc_stack_len; int pc_stack_size; + /* predecessor tracking for debugging */ + int *pred_pc; /* predecessor PC that first set stack at pos */ + int *pred_stack; /* stack height at predecessor */ + uint8_t *is_op_start; /* bitmap: 1 if pos is start of instruction */ } StackSizeState; +static void print_backtrace_chain (StackSizeState *s, int pos, int max_depth) { + printf (" backtrace from pc=%d:\n", pos); + int depth = 0; + while (pos >= 0 && depth < max_depth) { + uint8_t op = s->bc_buf[pos]; +#ifdef DUMP_BYTECODE + const JSOpCode *oi = &short_opcode_info (op); + printf (" [%d] pc=%d op=%s stack=%d\n", + depth, pos, oi->name, s->stack_level_tab[pos]); +#else + printf (" [%d] pc=%d op=%d stack=%d\n", + depth, pos, op, s->stack_level_tab[pos]); +#endif + pos = s->pred_pc[pos]; + depth++; + } +} + +static void dump_instructions_around (StackSizeState *s, int pos, int window) { + printf (" bytecode around pc=%d:\n", pos); + /* Find start position (scan backwards for opcode boundaries) */ + int start = pos; + for (int i = 0; i < window && start > 0; ) { + start--; + if (s->is_op_start[start]) i++; + } + /* Dump instructions */ + int p = start; + while (p < s->bc_len && p < pos + window * 5) { + if (!s->is_op_start[p]) { p++; continue; } + uint8_t op = s->bc_buf[p]; + const JSOpCode *oi = &short_opcode_info (op); + char marker = (p == pos) ? '>' : ' '; +#ifdef DUMP_BYTECODE + printf (" %c %5d: %-20s size=%d stack_in=%d\n", + marker, p, oi->name, oi->size, + s->stack_level_tab[p] != 0xffff ? (int)s->stack_level_tab[p] : -1); +#else + printf (" %c %5d: op=%-3d size=%d stack_in=%d\n", + marker, p, op, oi->size, + s->stack_level_tab[p] != 0xffff ? (int)s->stack_level_tab[p] : -1); +#endif + p += oi->size; + } +} + /* 'op' is only used for error indication */ -static __exception int ss_check (JSContext *ctx, StackSizeState *s, int pos, int op, int stack_len, int catch_pos) { +static __exception int ss_check (JSContext *ctx, StackSizeState *s, int pos, int op, int stack_len, int catch_pos, int from_pos) { if ((unsigned)pos >= s->bc_len) { JS_ThrowInternalError (ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); return -1; @@ -16457,9 +16507,72 @@ static __exception int ss_check (JSContext *ctx, StackSizeState *s, int pos, int /* already explored: check that the stack size is consistent */ if (s->stack_level_tab[pos] != stack_len) { uint8_t op_at_pos = s->bc_buf[pos]; - JS_ThrowInternalError (ctx, "inconsistent stack size: %d %d (pc=%d, op=%d)", s->stack_level_tab[pos], stack_len, pos, op_at_pos); +#ifdef DUMP_BYTECODE + const JSOpCode *oi = &short_opcode_info (op_at_pos); +#endif + + /* Check if pos is a valid opcode boundary */ + int boundary_valid = s->is_op_start[pos]; + + printf ("=== STACK ANALYSIS ERROR ===\n"); + printf ("Inconsistent stack at pc=%d:\n", pos); + printf (" expected: %d (first path)\n", s->stack_level_tab[pos]); + printf (" got: %d (conflicting path)\n", stack_len); +#ifdef DUMP_BYTECODE + printf (" opcode at pos: %s (%d)\n", oi->name, op_at_pos); +#else + printf (" opcode at pos: %d\n", op_at_pos); +#endif + printf (" valid opcode boundary: %s\n", boundary_valid ? "YES" : "NO"); + + printf ("\nFirst path that reached pc=%d:\n", pos); + print_backtrace_chain (s, pos, 20); + + printf ("\nConflicting path (from pc=%d):\n", from_pos); + print_backtrace_chain (s, from_pos, 20); + + printf ("\nBytecode window:\n"); + dump_instructions_around (s, pos, 10); + +#ifdef DUMP_BYTECODE + JS_ThrowInternalError (ctx, + "inconsistent stack size: %d vs %d (pc=%d, op=%s, boundary=%s)", + s->stack_level_tab[pos], stack_len, pos, oi->name, + boundary_valid ? "valid" : "INVALID"); +#else + JS_ThrowInternalError (ctx, + "inconsistent stack size: %d vs %d (pc=%d, op=%d, boundary=%s)", + s->stack_level_tab[pos], stack_len, pos, op_at_pos, + boundary_valid ? "valid" : "INVALID"); +#endif return -1; } else if (s->catch_pos_tab[pos] != catch_pos) { + uint8_t op_at_pos = s->bc_buf[pos]; +#ifdef DUMP_BYTECODE + const JSOpCode *oi = &short_opcode_info (op_at_pos); +#endif + int boundary_valid = s->is_op_start[pos]; + + printf ("=== CATCH POSITION ERROR ===\n"); + printf ("Inconsistent catch position at pc=%d:\n", pos); + printf (" expected: %d (first path)\n", s->catch_pos_tab[pos]); + printf (" got: %d (conflicting path)\n", catch_pos); +#ifdef DUMP_BYTECODE + printf (" opcode at pos: %s (%d)\n", oi->name, op_at_pos); +#else + printf (" opcode at pos: %d\n", op_at_pos); +#endif + printf (" valid opcode boundary: %s\n", boundary_valid ? "YES" : "NO"); + + printf ("\nFirst path that reached pc=%d:\n", pos); + print_backtrace_chain (s, pos, 20); + + printf ("\nConflicting path (from pc=%d):\n", from_pos); + print_backtrace_chain (s, from_pos, 20); + + printf ("\nBytecode window:\n"); + dump_instructions_around (s, pos, 10); + JS_ThrowInternalError (ctx, "inconsistent catch position: %d %d (pc=%d)", s->catch_pos_tab[pos], catch_pos, pos); return -1; } else { @@ -16470,6 +16583,9 @@ static __exception int ss_check (JSContext *ctx, StackSizeState *s, int pos, int /* mark as explored and store the stack size */ s->stack_level_tab[pos] = stack_len; s->catch_pos_tab[pos] = catch_pos; + s->pred_pc[pos] = from_pos; + s->pred_stack[pos] = stack_len; + s->is_op_start[pos] = 1; /* queue the new PC to explore */ if (pjs_resize_array ((void **)&s->pc_stack, sizeof (s->pc_stack[0]), &s->pc_stack_size, s->pc_stack_len + 1)) @@ -16498,12 +16614,24 @@ static __exception int compute_stack_size (JSContext *ctx, JSFunctionDef *fd, in s->catch_pos_tab = pjs_malloc (sizeof (s->catch_pos_tab[0]) * s->bc_len); if (!s->catch_pos_tab) goto fail; + /* allocate predecessor tracking arrays */ + s->pred_pc = pjs_malloc (sizeof (s->pred_pc[0]) * s->bc_len); + if (!s->pred_pc) goto fail; + for (i = 0; i < s->bc_len; i++) s->pred_pc[i] = -1; + + s->pred_stack = pjs_malloc (sizeof (s->pred_stack[0]) * s->bc_len); + if (!s->pred_stack) goto fail; + + s->is_op_start = pjs_malloc (sizeof (s->is_op_start[0]) * s->bc_len); + if (!s->is_op_start) goto fail; + memset (s->is_op_start, 0, s->bc_len); + s->stack_len_max = 0; s->pc_stack_len = 0; s->pc_stack_size = 0; /* breadth-first graph exploration */ - if (ss_check (ctx, s, 0, OP_invalid, 0, -1)) goto fail; + if (ss_check (ctx, s, 0, OP_invalid, 0, -1, -1)) goto fail; while (s->pc_stack_len > 0) { pos = s->pc_stack[--s->pc_stack_len]; @@ -16511,11 +16639,15 @@ static __exception int compute_stack_size (JSContext *ctx, JSFunctionDef *fd, in catch_pos = s->catch_pos_tab[pos]; op = bc_buf[pos]; if (op == 0 || op >= OP_COUNT) { - const char *fn = "?"; - if (!JS_IsNull(fd->func_name)) { - fn = JS_ToCString(ctx, fd->func_name); + char fn_buf[64] = "?"; + if (!JS_IsNull (fd->func_name)) { + const char *fn = JS_ToCString (ctx, fd->func_name); + if (fn) { + snprintf (fn_buf, sizeof (fn_buf), "%s", fn); + JS_FreeCString (ctx, fn); + } } - JS_ThrowInternalError (ctx, "invalid opcode in '%s' (op=%d, pc=%d, bc_len=%d)", fn, op, pos, s->bc_len); + JS_ThrowInternalError (ctx, "invalid opcode in '%s' (op=%d, pc=%d, bc_len=%d)", fn_buf, op, pos, s->bc_len); goto fail; } oi = &short_opcode_info (op); @@ -16571,24 +16703,24 @@ static __exception int compute_stack_size (JSContext *ctx, JSFunctionDef *fd, in case OP_if_true8: case OP_if_false8: diff = (int8_t)bc_buf[pos + 1]; - if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos, pos)) goto fail; break; #endif case OP_if_true: case OP_if_false: diff = get_u32 (bc_buf + pos + 1); - if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos, pos)) goto fail; break; case OP_gosub: diff = get_u32 (bc_buf + pos + 1); - if (ss_check (ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos)) + if (ss_check (ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos, pos)) goto fail; break; case OP_catch: diff = get_u32 (bc_buf + pos + 1); - if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos, pos)) goto fail; catch_pos = pos; break; @@ -16622,18 +16754,24 @@ static __exception int compute_stack_size (JSContext *ctx, JSFunctionDef *fd, in default: break; } - if (ss_check (ctx, s, pos_next, op, stack_len, catch_pos)) goto fail; + if (ss_check (ctx, s, pos_next, op, stack_len, catch_pos, pos)) goto fail; done_insn:; } pjs_free (s->pc_stack); pjs_free (s->catch_pos_tab); pjs_free (s->stack_level_tab); + pjs_free (s->pred_pc); + pjs_free (s->pred_stack); + pjs_free (s->is_op_start); *pstack_size = s->stack_len_max; return 0; fail: pjs_free (s->pc_stack); pjs_free (s->catch_pos_tab); pjs_free (s->stack_level_tab); + pjs_free (s->pred_pc); + pjs_free (s->pred_stack); + pjs_free (s->is_op_start); *pstack_size = 0; return -1; }