add stacktrace debugging
This commit is contained in:
162
source/quickjs.c
162
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user