Method: Exception#set_backtrace

Defined in:
error.c

#set_backtrace(value) ⇒ Object

Sets the backtrace value for self; returns the given value.

The value might be:

  • an array of Thread::Backtrace::Location;

  • an array of String instances;

  • a single String instance; or

  • nil.

Using array of Thread::Backtrace::Location is the most consistent option: it sets both #backtrace and #backtrace_locations. It should be preferred when possible. The suitable array of locations can be obtained from Kernel#caller_locations, copied from another error, or just set to the adjusted result of the current error’s #backtrace_locations:

require 'json'

def parse_payload(text)
  JSON.parse(text)  # test.rb, line 4
rescue JSON::ParserError => ex
  ex.set_backtrace(ex.backtrace_locations[2...])
  raise
end

parse_payload('{"wrong: "json"')
# test.rb:4:in 'Object#parse_payload': unexpected token at '{"wrong: "json"' (JSON::ParserError)
#
# An error points to the body of parse_payload method,
# hiding the parts of the backtrace related to the internals
# of the "json" library

# The error has both #backtace and #backtrace_locations set
# consistently:
begin
  parse_payload('{"wrong: "json"')
rescue => ex
  p ex.backtrace
  # ["test.rb:4:in 'Object#parse_payload'", "test.rb:20:in '<main>'"]
  p ex.backtrace_locations
  # ["test.rb:4:in 'Object#parse_payload'", "test.rb:20:in '<main>'"]
end

When the desired stack of locations is not available and should be constructed from scratch, an array of strings or a singular string can be used. In this case, only #backtrace is affected:

def parse_payload(text)
  JSON.parse(text)
rescue JSON::ParserError => ex
  ex.set_backtrace(["dsl.rb:34", "framework.rb:1"])
  # The error have the new value in #backtrace:
  p ex.backtrace
  # ["dsl.rb:34", "framework.rb:1"]

  # but the original one in #backtrace_locations
  p ex.backtrace_locations
  # [".../json/common.rb:221:in 'JSON::Ext::Parser.parse'", ...]
end

parse_payload('{"wrong: "json"')

Calling #set_backtrace with nil clears up #backtrace but doesn’t affect #backtrace_locations:

def parse_payload(text)
  JSON.parse(text)
rescue JSON::ParserError => ex
  ex.set_backtrace(nil)
  p ex.backtrace
  # nil
  p ex.backtrace_locations
  # [".../json/common.rb:221:in 'JSON::Ext::Parser.parse'", ...]
end

parse_payload('{"wrong: "json"')

On reraising of such an exception, both #backtrace and #backtrace_locations is set to the place of reraising:

def parse_payload(text)
  JSON.parse(text)
rescue JSON::ParserError => ex
  ex.set_backtrace(nil)
  raise # test.rb, line 7
end

begin
  parse_payload('{"wrong: "json"')
rescue => ex
  p ex.backtrace
  # ["test.rb:7:in 'Object#parse_payload'", "test.rb:11:in '<main>'"]
  p ex.backtrace_locations
  # ["test.rb:7:in 'Object#parse_payload'", "test.rb:11:in '<main>'"]
end

See Backtraces.



2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
# File 'error.c', line 2108

static VALUE
exc_set_backtrace(VALUE exc, VALUE 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));
    }
}