I was working with exceptions bubbling up out of some C code, and having
trouble crafting an exception message that was descriptive enough to
identify the object(s) involved so I could easily find and inspect them.
The following is some ruby and C code (the ruby code is useful by
itself) for “augmented exceptions”. Maybe there’s better terminology,
but the idea is that you can tell the handler which object is causing
the problem, or if there is no handler then at least the stack dump
tells you a little more about the object. It falls back gracefully if
#inspect (or #to_s) fails for the object in question.
========================================================================
Include this module in an exception class to pass an object along
with the exception so that it can be used by the rescuer. To raise
an exception with an object, use this syntax:
raise YourExceptionClass, [message_string, object], …
To raise an exception normally, with just a message string:
raise YourExceptionClass, message_string, …
The object is used in two ways.
First, the message you provide when you raise the exception is
concatenated with a string describing the object so that the message
string on the receiving end of the exception says something helpful
about this object. To generate the string, we first use #inspect on
the object. If #inspect fails, we fall back to #to_s; if that fails,
we fall back to class name and #object_id.
Second, the object can be accessed from $! (or, equivalently, the
exception object captured using the “rescue => ex” syntax) using
the #object method.
The rationale for all of this is that sometimes you want to be able
to do “post-mortem” analysis on an object involved in an exception,
either in a rescue clause or by looking at the exception message.
module AugmentedException
attr_reader :object
def initialize(msg)
if defined?(msg.first)
msg, @object = *msg
s = ((@object.inspect rescue
@object.to_s) rescue
“id ##{@object.object_id} of class #{@object.class}”)
msg += " Object is: $!.object == #{s}"
end
super msg
end
end
Example:
class MyError < StandardError
include AugmentedException
end
class C
attr_reader :x
def initialize x
@x = x
end
def inspect
#raise # <-- try this!
super
end
def to_s
#raise # <-- try this!
“C with x=#{@x}”
end
end
If you are rescuing, then you can work with the object itself.
begin
raise MyError, [“foo bar”, C.new(3)]
rescue MyError => ex
p ex.message # “foo bar Object is: $!.object == #<C:0xb7cfb6d0 @x=3>”
puts ex # foo bar Object is: $!.object == #<C:0xb7cfb6d0 @x=3>
p ex.object # #<C:0xb7cfb6d0 @x=3>
end
puts
If all you have is the stack dump, you still get more info
than before.
raise MyError, [“zap”, C.new(4)]
c.rb:78: zap Object is: $!.object == #<C:0xb7da65d0 @x=4> (MyError)
END
/*
If you want to generate these exceptions from C code, you will find
that rb_raise() doesn’t quite work (it wants a string, not an
object).
But the following does work nicely. It’s almost a drop-in replacement
for rb_raise (and the implementation is based on MRI). You just add
an
argument referencing the object you wish to propagate to the rescuer.
You can still use format strings as with rb_raise.
*/
#include <stdarg.h>
void aug_ex_raise(VALUE exc, VALUE obj, const char *fmt, …)
{
va_list args;
char buf[BUFSIZ];
VALUE ex, ary;
va_start(args, fmt);
vsnprintf(buf, BUFSIZ, fmt, args);
va_end(args);
ary = rb_ary_new3(2, rb_str_new2(buf), obj);
ex = rb_funcall(exc, ID_new, 1, ary);
rb_exc_raise(ex);
}
// Sample use:
aug_ex_raise(exception_class, object, “foo: %s, %d”, “bar”, 13);