On Wed, Nov 25, 2009 at 6:31 AM, Jochen Theodorou <
blac...@gmx.org> wrote:
> I thought we should maybe some time talk about the stack traces java
> gives. A typical problem for Groovy is, that you a huge trace, which is
> of no interest to the user. What is commonly done is to catch the
> exception somewhere, edit the array to remove the unwanted elements and
> throw it again. This problem applies not only to Groovy, but also to
> almost any framework. Now in languages such as Groovy it is very common
> to generate helper function in bytecode. But usually I want those to not
> to show up. Also my runtime should not really show up to a normal user.
> Even Java has several helper methods, that then show up in the trace.
>
> Wouldn't it be possible to give an method an attribute so it will no add
> that stack frame to the exception? Is there a problem for the VM to do that?
Hello Jochen!
Yes, blasted stack traces...they are a pain to cope with for sure.
JRuby and Jython are probably the only two widely-used JVM languages
that present *nice* stack traces, but we pay a price for it. In JRuby,
we have to maintain a separate frame stack that we push and pop on
call and return. We do this to pass data across calls outside the call
stack (like some "out"-style implicit variables for regexp matching,
etc), but also to maintain only the pertinent information for Ruby
backtraces. So if you have this code:
~/projects/jruby ➔ cat trace.rb
def foo
bar
end
def bar
baz
end
def baz
raise
end
foo
You get this lovely trace:
~/projects/jruby ➔ jruby trace.rb
trace.rb:10:in `baz': unhandled exception
from trace.rb:6:in `bar'
from trace.rb:2:in `foo'
from trace.rb:13
Where the full-on Java trace would be considerably larger (at the end
of this email, because it's so large). We obviously pay a cost for
maintaining these artificial stack frames (2-3x more overhead per call
compared to an empty ruby method with no artificial framing), but I
believe the usability benefits of nice, clean traces outweighs the
cost...for now.
We have implemented a few other options for generating backtraces. The
most promising is to structure our generated method handles and
bytecode-compiled bodies to have method names we can mine out of the
normal Java trace.
You'll note in the Java trace below that method handles and Ruby
method names are specifically structured to be tokenizable into
appropriate pieces for the backtrace, as in "method__2$RUBY$baz". When
an exception needs to be raised from Ruby code, we can pull the Java
trace, look for any elements with RUBY in them, and then pull out only
the relevant information.
This actually works pretty well for compiled code, and produces
nearly-identical traces. Here's an example of a trace generated
entirely from Java backtrace element parsing rather than from our
artificial frames:
~/projects/jruby ➔ jruby --fast trace.rb
trace.rb:10:in `baz': unhandled exception
from trace.rb:6:in `bar'
from trace.rb:2:in `foo'
Where it breaks down, however, is in handling interpreter backtraces.
Since interpretation will always be a part of Ruby applications (eval
is definitely used), we need to have an option that also includes
interpreted frames. So we also have a backtrace mode that looks for
trace elements known to be interpreter entry-points, and the backtrace
builder then pops off frames as it sees them.
Obviously both of these techniques are a bit cumbersome, and they're
both definitely experimental.
I am also interested in ways to improve this situation, and I know
Clojure and Scala users/devs would like it two (both have large and
unfriendly backtraces, similar to Groovy).
- Charlie