add stacktrace debugging

This commit is contained in:
2026-02-04 16:04:56 -06:00
parent bc9d8a338a
commit 75ddb1fb4f

View File

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