Re-raising an exception with the original backtrace

Is there a way to re-raise an exception without replacing its backtrace
with the current one?

The reason I want to do this is that I am catching an exception and
passing it to a handler. The handler needs to make a decision and then,
possibly, re-raise the same exception, preferrably with the same
information as it originally contained.

But if I do the following, the exception takes on the backtrace of the
handler, which has lost some information. I know I can add the original
backtrace to the exception’s message, but that’s not the same thing…

$ cat exception.rb

class MyError < StandardError; end

def foo
raise MyError
end

def main
foo
rescue Exception => e
handler(e)
end

def handler(e)
ok = false
if ok
return true
else
puts “backtrace:”, e.backtrace, "—"
raise e
end
end

main

$ ruby exception.rb
backtrace:
exception.rb:4:in foo' exception.rb:8:inmain’
exception.rb:23

···

exception.rb:19:in handler': MyError (MyError) from exception.rb:10:inmain’
from exception.rb:23

Note that the backtrace of e knows about foo, but the re-raised
exception does not.

Hi –

···

On Sat, 29 Nov 2003, Joel VanderWerf wrote:

Is there a way to re-raise an exception without replacing its backtrace
with the current one?

The reason I want to do this is that I am catching an exception and
passing it to a handler. The handler needs to make a decision and then,
possibly, re-raise the same exception, preferrably with the same
information as it originally contained.

But if I do the following, the exception takes on the backtrace of the
handler, which has lost some information. I know I can add the original
backtrace to the exception’s message, but that’s not the same thing…

$ cat exception.rb

class MyError < StandardError; end

def foo
raise MyError
end

def main
foo
rescue Exception => e
handler(e)
end

def handler(e)
ok = false
if ok
return true
else
puts “backtrace:”, e.backtrace, “—”
raise e

I think if you just do ‘raise’ there, instead of ‘raise e’, it will
propagate the original exception and backtrace.

David


David A. Black
dblack@wobblini.net

David A. Black wrote:

Hi –

Is there a way to re-raise an exception without replacing its backtrace
with the current one?

The reason I want to do this is that I am catching an exception and
passing it to a handler. The handler needs to make a decision and then,
possibly, re-raise the same exception, preferrably with the same
information as it originally contained.

But if I do the following, the exception takes on the backtrace of the
handler, which has lost some information. I know I can add the original
backtrace to the exception’s message, but that’s not the same thing…

$ cat exception.rb

class MyError < StandardError; end

def foo
raise MyError
end

def main
foo
rescue Exception => e
handler(e)
end

def handler(e)
ok = false
if ok
return true
else
puts “backtrace:”, e.backtrace, “—”
raise e

I think if you just do ‘raise’ there, instead of ‘raise e’, it will
propagate the original exception and backtrace.

Ooh, that’s nice. I thought ‘raise’ would just re-raise the last
exception, but it’s actually sensitive to the dynamic context. The
example below would fail in the former case, but since it succeeds, I
guess raise looks back down the stack and re-raises the first exception
it finds there?

$ cat exception.rb

class MyError < StandardError; end

def foo
raise MyError
end

def main
foo
rescue Exception => e
handler(e)
end

class OtherError < StandardError; end
def do_something_that_might_use_exceptions
raise OtherError
rescue Exception => e
p e
false
end

def handler(e)
ok = do_something_that_might_use_exceptions
if ok
return true
else
puts “backtrace:”, e.backtrace, “—”
raise
end
end

main

$ ruby exception.rb
#<OtherError: OtherError>
backtrace:
exception.rb:4:in foo' exception.rb:8:in main’
exception.rb:31

···

On Sat, 29 Nov 2003, Joel VanderWerf wrote:


exception.rb:4:in foo': MyError (MyError) from exception.rb:8:in main’
from exception.rb:31

Hi,

Ooh, that’s nice. I thought ‘raise’ would just re-raise the last
exception, but it’s actually sensitive to the dynamic context. The
example below would fail in the former case, but since it succeeds, I
guess raise looks back down the stack and re-raises the first exception
it finds there?

But I found that outer $! was preserved only in rescue clauses,
in 1.6.8 and 1.8.1.

$ cat exception.rb

class MyError < StandardError; end

def foo
raise MyError
end

def main
foo
rescue Exception => e
handler(e)
end

class OtherError < StandardError; end
def do_something_that_might_use_exceptions(retrying = false)
raise OtherError
rescue Exception => e
retry if (retrying ^= true)
p e
false
end

def handler(e)
ok = do_something_that_might_use_exceptions
if ok
return true
else
puts “backtrace:”, e.backtrace, “—”
raise
end
end

main

$ ruby -v exception.rb
ruby 1.8.1 (2003-11-28) [i686-linux]
#<OtherError: OtherError>
backtrace:
exception.rb:4:in foo' exception.rb:8:in main’
exception.rb:32

···

At Sat, 29 Nov 2003 08:53:40 +0900, Joel VanderWerf wrote:

exception.rb:28:in handler': unhandled exception from exception.rb:10:in main’
from exception.rb:32

In other words, only changing $! in rescue clauses has no
effect for outside.

I suspect that rescue blocks should preserve $! value when no
exception raised and $! should not be cleared at retry.

Index: eval.c

RCS file: /cvs/ruby/src/ruby/eval.c,v
retrieving revision 1.598
diff -u -2 -p -r1.598 eval.c
— eval.c 27 Nov 2003 19:15:29 -0000 1.598
+++ eval.c 29 Nov 2003 09:48:56 -0000
@@ -2857,4 +2857,5 @@ rb_eval(self, n)
PUSH_TAG(PROT_NONE);
if ((state = EXEC_TAG()) == 0) {

  •     retry_entry:
      result = rb_eval(self, node->nd_head);
      }
    

@@ -2865,9 +2866,8 @@ rb_eval(self, n)
else if (state == TAG_RETRY) {
rescuing = state = 0;

  •       e_info = ruby_errinfo = Qnil;
    
  •       result = rb_eval(self, node->nd_head);
    
  •       ruby_errinfo = e_info;
    
  •       goto retry_entry;
      }
      else if (state != TAG_RAISE) {
    
  •       ruby_errinfo = e_info;
          result = prot_tag->retval;
      }
    

@@ -2883,5 +2883,4 @@ rb_eval(self, n)
rescuing = 1;
result = rb_eval(self, resq->nd_body);

  •   	ruby_errinfo = e_info;
      	break;
          }
    

@@ -2893,4 +2892,5 @@ rb_eval(self, n)
}
POP_TAG();

  •   if (state != TAG_RAISE) ruby_errinfo = e_info;
      if (state) {
      if (state == TAG_NEXT) prot_tag->retval = result;
    


Nobu Nakada