Make rb_debug_inspector_backtrace_locations return a raw backtrace

Previously, a user of the debug inspector API was supposed to use
`rb_debug_inspector_backtrace_locations` to get an array of
`Thread::Backtrace::Location`, and then used its index to get more
information from `rb_debug_inspector _frame_binding_get(index)`, etc.

However, `rb_debug_inspector_backtrace_locations` returned an array of
backtraces excluding rescue/ensure frames. On the other hand,
`rb_debug_inspector_frame_binding_get(index)` interprets index with
rescue/ensure frames. This led to inconsistency of the index, and it was
very difficult to correctly use the debug inspector API.

This is a minimal fix for the issue by making
`rb_debug_inspector_backtrace_locations` return a raw backtrace
including rescue/ensure frames.
This commit is contained in:
Yusuke Endoh 2025-06-04 16:44:28 +09:00
parent 8d14d6ea2d
commit caa6ba1a46
Notes: git 2025-06-04 10:53:32 +00:00

View File

@ -1493,9 +1493,8 @@ RUBY_SYMBOL_EXPORT_END
struct rb_debug_inspector_struct {
rb_execution_context_t *ec;
rb_control_frame_t *cfp;
VALUE backtrace;
VALUE contexts; /* [[klass, binding, iseq, cfp], ...] */
long backtrace_size;
VALUE raw_backtrace;
};
enum {
@ -1504,18 +1503,22 @@ enum {
CALLER_BINDING_BINDING,
CALLER_BINDING_ISEQ,
CALLER_BINDING_CFP,
CALLER_BINDING_LOC,
CALLER_BINDING_DEPTH,
};
struct collect_caller_bindings_data {
VALUE ary;
const rb_execution_context_t *ec;
VALUE btobj;
rb_backtrace_t *bt;
};
static void
collect_caller_bindings_init(void *arg, size_t size)
collect_caller_bindings_init(void *arg, size_t num_frames)
{
/* */
struct collect_caller_bindings_data *data = (struct collect_caller_bindings_data *)arg;
data->btobj = backtrace_alloc_capa(num_frames, &data->bt);
}
static VALUE
@ -1553,6 +1556,14 @@ collect_caller_bindings_iseq(void *arg, const rb_control_frame_t *cfp)
rb_ary_store(frame, CALLER_BINDING_BINDING, GC_GUARDED_PTR(cfp)); /* create later */
rb_ary_store(frame, CALLER_BINDING_ISEQ, cfp->iseq ? (VALUE)cfp->iseq : Qnil);
rb_ary_store(frame, CALLER_BINDING_CFP, GC_GUARDED_PTR(cfp));
rb_backtrace_location_t *loc = &data->bt->backtrace[data->bt->backtrace_size++];
RB_OBJ_WRITE(data->btobj, &loc->cme, rb_vm_frame_method_entry(cfp));
RB_OBJ_WRITE(data->btobj, &loc->iseq, cfp->iseq);
loc->pc = cfp->pc;
VALUE vloc = location_create(loc, (void *)data->btobj);
rb_ary_store(frame, CALLER_BINDING_LOC, vloc);
rb_ary_store(frame, CALLER_BINDING_DEPTH, INT2FIX(frame_depth(data->ec, cfp)));
rb_ary_push(data->ary, frame);
@ -1569,6 +1580,14 @@ collect_caller_bindings_cfunc(void *arg, const rb_control_frame_t *cfp, ID mid)
rb_ary_store(frame, CALLER_BINDING_BINDING, Qnil); /* not available */
rb_ary_store(frame, CALLER_BINDING_ISEQ, Qnil); /* not available */
rb_ary_store(frame, CALLER_BINDING_CFP, GC_GUARDED_PTR(cfp));
rb_backtrace_location_t *loc = &data->bt->backtrace[data->bt->backtrace_size++];
RB_OBJ_WRITE(data->btobj, &loc->cme, rb_vm_frame_method_entry(cfp));
loc->iseq = NULL;
loc->pc = NULL;
VALUE vloc = location_create(loc, (void *)data->btobj);
rb_ary_store(frame, CALLER_BINDING_LOC, vloc);
rb_ary_store(frame, CALLER_BINDING_DEPTH, INT2FIX(frame_depth(data->ec, cfp)));
rb_ary_push(data->ary, frame);
@ -1617,15 +1636,19 @@ rb_debug_inspector_open(rb_debug_inspector_func_t func, void *data)
rb_execution_context_t *ec = GET_EC();
enum ruby_tag_type state;
volatile VALUE MAYBE_UNUSED(result);
int i;
/* escape all env to heap */
rb_vm_stack_to_heap(ec);
dbg_context.ec = ec;
dbg_context.cfp = dbg_context.ec->cfp;
dbg_context.backtrace = rb_ec_backtrace_location_ary(ec, RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES, FALSE);
dbg_context.backtrace_size = RARRAY_LEN(dbg_context.backtrace);
dbg_context.contexts = collect_caller_bindings(ec);
dbg_context.raw_backtrace = rb_ary_new();
for (i=0; i<RARRAY_LEN(dbg_context.contexts); i++) {
VALUE frame = rb_ary_entry(dbg_context.contexts, i);
rb_ary_push(dbg_context.raw_backtrace, rb_ary_entry(frame, CALLER_BINDING_LOC));
}
EC_PUSH_TAG(ec);
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
@ -1645,7 +1668,7 @@ rb_debug_inspector_open(rb_debug_inspector_func_t func, void *data)
static VALUE
frame_get(const rb_debug_inspector_t *dc, long index)
{
if (index < 0 || index >= dc->backtrace_size) {
if (index < 0 || index >= RARRAY_LEN(dc->contexts)) {
rb_raise(rb_eArgError, "no such frame");
}
return rb_ary_entry(dc->contexts, index);
@ -1698,7 +1721,7 @@ rb_debug_inspector_current_depth(void)
VALUE
rb_debug_inspector_backtrace_locations(const rb_debug_inspector_t *dc)
{
return dc->backtrace;
return dc->raw_backtrace;
}
static int