Skip to content

Commit

Permalink
Exception#set_backtrace accept arrays of Backtrace::Location
Browse files Browse the repository at this point in the history
[Feature #13557]

Setting the backtrace with an array of strings is lossy. The resulting
exception will return nil on `#backtrace_locations`.

By accepting an array of `Backtrace::Location` instance, we can rebuild
a `Backtrace` instance and have a fully functioning Exception.

Co-Authored-By: Étienne Barrié <etienne.barrie@gmail.com>
  • Loading branch information
byroot and etiennebarrie committed Mar 14, 2024
1 parent 5326337 commit 315bde5
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 11 deletions.
12 changes: 10 additions & 2 deletions error.c
Expand Up @@ -1839,7 +1839,7 @@ static VALUE
rb_check_backtrace(VALUE bt)
{
long i;
static const char err[] = "backtrace must be Array of String";
static const char err[] = "backtrace must be an Array of String or an Array of Thread::Backtrace::Location";

if (!NIL_P(bt)) {
if (RB_TYPE_P(bt, T_STRING)) return rb_ary_new3(1, bt);
Expand Down Expand Up @@ -1870,7 +1870,15 @@ rb_check_backtrace(VALUE bt)
static VALUE
exc_set_backtrace(VALUE exc, VALUE bt)
{
return rb_ivar_set(exc, id_bt, rb_check_backtrace(bt));
VALUE btobj = rb_location_ary_to_backtrace(bt);
if (RTEST(btobj)) {
rb_ivar_set(exc, id_bt, btobj);
rb_ivar_set(exc, id_bt_locations, btobj);
return bt;
}
else {
return rb_ivar_set(exc, id_bt, rb_check_backtrace(bt));
}
}

VALUE
Expand Down
1 change: 1 addition & 0 deletions internal/vm.h
Expand Up @@ -114,6 +114,7 @@ void rb_backtrace_print_as_bugreport(FILE*);
int rb_backtrace_p(VALUE obj);
VALUE rb_backtrace_to_str_ary(VALUE obj);
VALUE rb_backtrace_to_location_ary(VALUE obj);
VALUE rb_location_ary_to_backtrace(VALUE ary);
void rb_backtrace_each(VALUE (*iter)(VALUE recv, VALUE str), VALUE output);
int rb_frame_info_p(VALUE obj);
int rb_get_node_id_from_frame_info(VALUE obj);
Expand Down
28 changes: 28 additions & 0 deletions spec/ruby/core/exception/set_backtrace_spec.rb
Expand Up @@ -11,9 +11,37 @@
it "allows the user to set the backtrace from a rescued exception" do
bt = ExceptionSpecs::Backtrace.backtrace
err = RuntimeError.new
err.backtrace.should == nil
err.backtrace_locations.should == nil

err.set_backtrace bt

err.backtrace.should == bt
err.backtrace_locations.should == nil
end

ruby_version_is "3.4" do
it "allows the user to set backtrace locations from a rescued exception" do
bt_locations = ExceptionSpecs::Backtrace.backtrace_locations
err = RuntimeError.new
err.backtrace.should == nil
err.backtrace_locations.should == nil

err.set_backtrace bt_locations

err.backtrace_locations.size.should == bt_locations.size
err.backtrace_locations.each_with_index do |loc, index|
other_loc = bt_locations[index]

loc.path.should == other_loc.path
loc.label.should == other_loc.label
loc.base_label.should == other_loc.base_label
loc.lineno.should == other_loc.lineno
loc.absolute_path.should == other_loc.absolute_path
loc.to_s.should == other_loc.to_s
end
err.backtrace.size.should == err.backtrace_locations.size
end
end

it "accepts an empty Array" do
Expand Down
8 changes: 8 additions & 0 deletions spec/ruby/shared/kernel/raise.rb
Expand Up @@ -146,4 +146,12 @@ def initialize
@object.raise(ArgumentError, "message", caller)
end.should raise_error(ArgumentError, "message")
end

ruby_version_is "3.4" do
it "allows Exception, message, and backtrace_locations parameters" do
-> do
@object.raise(ArgumentError, "message", caller_locations)
end.should raise_error(ArgumentError, "message")
end
end
end
18 changes: 12 additions & 6 deletions test/ruby/test_exception.rb
Expand Up @@ -416,7 +416,7 @@ def test_errat

assert_in_out_err([], "$@ = 1", [], /\$! not set \(ArgumentError\)$/)

assert_in_out_err([], <<-INPUT, [], /backtrace must be Array of String \(TypeError\)$/)
assert_in_out_err([], <<-INPUT, [], /backtrace must be an Array of String or an Array of Thread::Backtrace::Location \(TypeError\)$/)
begin
raise
rescue
Expand Down Expand Up @@ -508,6 +508,16 @@ def test_set_backtrace

assert_raise(TypeError) { e.set_backtrace(1) }
assert_raise(TypeError) { e.set_backtrace([1]) }

error = assert_raise(TypeError) do
e.set_backtrace(caller_locations(1, 1) + ["foo"])
end
assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location"

error = assert_raise(TypeError) do
e.set_backtrace(["foo"] + caller_locations(1, 1))
end
assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location"
end

def test_exit_success_p
Expand Down Expand Up @@ -1421,11 +1431,7 @@ def test_circular_cause_handle
end

def test_marshal_circular_cause
begin
raise RuntimeError, "err", [], cause: Exception.new
rescue => e
end
dump = Marshal.dump(e).sub(/o:\x0EException\x08;.0;.0;.0/, "@\x05")
dump = "\x04\bo:\x11RuntimeError\b:\tmesgI\"\berr\x06:\x06ET:\abt[\x00:\ncause@\x05"
assert_raise_with_message(ArgumentError, /circular cause/, ->{dump.inspect}) do
e = Marshal.load(dump)
assert_same(e, e.cause)
Expand Down
48 changes: 45 additions & 3 deletions vm_backtrace.c
Expand Up @@ -531,6 +531,16 @@ backtrace_alloc(VALUE klass)
return obj;
}

static VALUE
backtrace_alloc_capa(long num_frames, rb_backtrace_t **backtrace)
{
size_t memsize = offsetof(rb_backtrace_t, backtrace) + num_frames * sizeof(rb_backtrace_location_t);
VALUE btobj = rb_data_typed_object_zalloc(rb_cBacktrace, memsize, &backtrace_data_type);
TypedData_Get_Struct(btobj, rb_backtrace_t, &backtrace_data_type, *backtrace);
return btobj;
}


static long
backtrace_size(const rb_execution_context_t *ec)
{
Expand Down Expand Up @@ -617,9 +627,7 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram
}
}

size_t memsize = offsetof(rb_backtrace_t, backtrace) + num_frames * sizeof(rb_backtrace_location_t);
btobj = rb_data_typed_object_zalloc(rb_cBacktrace, memsize, &backtrace_data_type);
TypedData_Get_Struct(btobj, rb_backtrace_t, &backtrace_data_type, bt);
btobj = backtrace_alloc_capa(num_frames, &bt);

bt->backtrace_size = 0;
if (num_frames == 0) {
Expand Down Expand Up @@ -783,6 +791,40 @@ rb_backtrace_to_location_ary(VALUE self)
return bt->locary;
}

VALUE
rb_location_ary_to_backtrace(VALUE ary)
{
if (!RB_TYPE_P(ary, T_ARRAY) || !rb_frame_info_p(RARRAY_AREF(ary, 0))) {
return Qfalse;
}

rb_backtrace_t *new_backtrace;
long num_frames = RARRAY_LEN(ary);
VALUE btobj = backtrace_alloc_capa(num_frames, &new_backtrace);

for (long index = 0; index < RARRAY_LEN(ary); index++) {
VALUE locobj = RARRAY_AREF(ary, index);

if (!rb_frame_info_p(locobj)) {
return Qfalse;
}

struct valued_frame_info *src_vloc;
TypedData_Get_Struct(locobj, struct valued_frame_info, &location_data_type, src_vloc);

rb_backtrace_location_t *dst_location = &new_backtrace->backtrace[index];
RB_OBJ_WRITE(btobj, &dst_location->cme, src_vloc->loc->cme);
RB_OBJ_WRITE(btobj, &dst_location->iseq, src_vloc->loc->iseq);
dst_location->pc = src_vloc->loc->pc;

new_backtrace->backtrace_size++;

RB_GC_GUARD(locobj);
}

return btobj;
}

static VALUE
backtrace_dump_data(VALUE self)
{
Expand Down

0 comments on commit 315bde5

Please sign in to comment.