MenTaLguY wrote:
How about only doing the capture and conversion of Java exceptions when
compiling/interpreting a begin...rescue block with a clause that traps
NativeExceptions?

You don't need to do the conversion all the time (and in fact the performance
hit from the conversion sounds very unappealing) if you're just unwinding
the Ruby stack, right?  Only at the point of a begin...rescue which could
potentially catch a NativeException do you ever really need to do a
conversion.

Attached is a patch that adds wrapping of RuntimeException in a NativeException to the interpreter. It does not yet do it for the compiler (exception handling is a bit of a mess in the compiler).

Example code ("npe" is a Kernel method I added that just throws NPE):

require 'java'

class A
  def foo
    npe
  end
end

class B
  def bar
    A.new.foo
  end
end

begin
  B.new.bar
rescue Exception
  puts 'caught it!'
end

Running interpreted:

~/NetBeansProjects/jruby ➔ jruby -X-C test2.rb
caught it!

Running compiled:

~/NetBeansProjects/jruby ➔ jruby test2.rb
Exception in thread "main" java.lang.NullPointerException
        at org.jruby.RubyKernel.npe(RubyKernel.java:1319)
        at org.jruby.RubyKernelInvoker$npe_s_method_0_0.call(Unknown Source)
at org.jruby.internal.runtime.methods.JavaMethod$JavaMethodZeroBlock.call(JavaMethod.java:161) at org.jruby.internal.runtime.methods.DynamicMethod.call(DynamicMethod.java:70) at org.jruby.runtime.CallSite$InlineCachingCallSite.cacheAndCall(CallSite.java:116)
        at 
org.jruby.runtime.CallSite$InlineCachingCallSite.call(CallSite.java:270)
...

And interpreted without the catch, but with a rescue block that doesn't rescue the exception (so like typical Ruby code where an NPE happens to go through a rescued method):

~/NetBeansProjects/jruby ➔ jruby -X-C -e "begin; npe; rescue SyntaxError; end" org/jruby/RubyKernel.java:1319:in `npe': java.lang.NullPointerException: null (NativeException)
        from org.jruby.RubyKernelInvoker$npe_s_method_0_0:-1:in `call'
        from org/jruby/internal/runtime/methods/JavaMethod.java:161:in `call'
        from org/jruby/internal/runtime/methods/DynamicMethod.java:70:in `call'
        from org/jruby/runtime/CallSite.java:116:in `cacheAndCall'
        from org/jruby/runtime/CallSite.java:270:in `call'
        from org/jruby/evaluator/ASTInterpreter.java:1833:in `vcallNode'
        from org/jruby/evaluator/ASTInterpreter.java:485:in `evalInternal'
        from org/jruby/evaluator/ASTInterpreter.java:1567:in `rescueNode'
         ... 7 levels...
        from org/jruby/Main.java:144:in `run'
        from org/jruby/Main.java:89:in `run'
        from org/jruby/Main.java:80:in `main'
Complete Java stackTrace
java.lang.NullPointerException
        at org.jruby.RubyKernel.npe(RubyKernel.java:1319)
        at org.jruby.RubyKernelInvoker$npe_s_method_0_0.call(Unknown Source)
at org.jruby.internal.runtime.methods.JavaMethod$JavaMethodZeroBlock.call(JavaMethod.java:161) at org.jruby.internal.runtime.methods.DynamicMethod.call(DynamicMethod.java:70) at org.jruby.runtime.CallSite$InlineCachingCallSite.cacheAndCall(CallSite.java:116)
        at 
org.jruby.runtime.CallSite$InlineCachingCallSite.call(CallSite.java:270)
        at 
org.jruby.evaluator.ASTInterpreter.vcallNode(ASTInterpreter.java:1833)
        at 
org.jruby.evaluator.ASTInterpreter.evalInternal(ASTInterpreter.java:485)
        at 
org.jruby.evaluator.ASTInterpreter.rescueNode(ASTInterpreter.java:1567)
        at 
org.jruby.evaluator.ASTInterpreter.evalInternal(ASTInterpreter.java:453)
        at org.jruby.evaluator.ASTInterpreter.rootNode(ASTInterpreter.java:1706)
        at 
org.jruby.evaluator.ASTInterpreter.evalInternal(ASTInterpreter.java:459)
        at org.jruby.evaluator.ASTInterpreter.eval(ASTInterpreter.java:169)
        at org.jruby.Ruby.runInterpreter(Ruby.java:529)
        at org.jruby.Ruby.runNormally(Ruby.java:435)
        at org.jruby.Ruby.runFromMain(Ruby.java:311)
        at org.jruby.Main.run(Main.java:144)
        at org.jruby.Main.run(Main.java:89)
        at org.jruby.Main.main(Main.java:80)

Looks a bit nicer, yeah? Has the actual trace of where the rescue happened at least.

- Charlie
diff --git a/src/org/jruby/RubyKernel.java b/src/org/jruby/RubyKernel.java
index 5c05d50..bf11e74 100644
--- a/src/org/jruby/RubyKernel.java
+++ b/src/org/jruby/RubyKernel.java
@@ -1313,4 +1313,9 @@ public class RubyKernel {
         block.yield(context, recv);
         return recv;
     }
+
+    @JRubyMethod(module = true)
+    public static IRubyObject npe(ThreadContext context, IRubyObject recv, 
Block block) {
+        throw new NullPointerException();
+    }
 }
diff --git a/src/org/jruby/evaluator/ASTInterpreter.java 
b/src/org/jruby/evaluator/ASTInterpreter.java
index aa44be0..2258f55 100644
--- a/src/org/jruby/evaluator/ASTInterpreter.java
+++ b/src/org/jruby/evaluator/ASTInterpreter.java
@@ -1582,43 +1582,53 @@ public class ASTInterpreter {
                 // get fixed at the same time we address bug #1296484.
                 runtime.getGlobalVariables().set("$!", raisedException);
 
-                RescueBodyNode rescueNode = iVisited.getRescueNode();
+                RescueBodyNode rescueNode = 
getExceptionHandler(iVisited.getRescueNode(), raisedException, runtime, 
context, self, aBlock);
 
-                while (rescueNode != null) {
-                    Node  exceptionNodes = rescueNode.getExceptionNodes();
-                    ListNode exceptionNodesList;
-                    
-                    if (exceptionNodes instanceof SplatNode) {                 
   
-                        exceptionNodesList = (ListNode) 
evalInternal(runtime,context, exceptionNodes, self, aBlock);
-                    } else {
-                        exceptionNodesList = (ListNode) exceptionNodes;
+                if (rescueNode != null) {
+                    try {
+                        return evalInternal(runtime,context, rescueNode, self, 
aBlock);
+                    } catch (JumpException.RetryJump rj) {
+                        // should be handled in the finally block below
+                        //state.runtime.getGlobalVariables().set("$!", 
state.runtime.getNil());
+                        //state.threadContext.setRaisedException(null);
+                        continue RescuedBlock;
+                    } catch (RaiseException je) {
+                        anotherExceptionRaised = true;
+                        throw je;
                     }
+                } else {
+                    // no takers; bubble up
+                    throw raiseJump;
+                }
+            } catch (JumpException je) {
+                // allow JumpExceptions to propagate
+                throw je;
+            } catch (RuntimeException re) {
+                RaiseException raiseJump = 
RaiseException.createNativeRaiseException(runtime, re);
+                RubyException raisedException = raiseJump.getException();
+                // TODO: Rubicon TestKernel dies without this line.  A cursory 
glance implies we
+                // falsely set $! to nil and this sets it back to something 
valid.  This should 
+                // get fixed at the same time we address bug #1296484.
+                runtime.getGlobalVariables().set("$!", raisedException);
 
-                    IRubyObject[] exceptions;
-                    if (exceptionNodesList == null) {
-                        exceptions = new IRubyObject[] 
{runtime.fastGetClass("StandardError")};
-                    } else {
-                        exceptions = setupArgs(runtime, context, 
exceptionNodes, self, aBlock);
-                    }
-                    if (RuntimeHelpers.isExceptionHandled(raisedException, 
exceptions, runtime, context, self).isTrue()) {
-                        try {
-                            return evalInternal(runtime,context, rescueNode, 
self, aBlock);
-                        } catch (JumpException.RetryJump rj) {
-                            // should be handled in the finally block below
-                            //state.runtime.getGlobalVariables().set("$!", 
state.runtime.getNil());
-                            //state.threadContext.setRaisedException(null);
-                            continue RescuedBlock;
-                        } catch (RaiseException je) {
-                            anotherExceptionRaised = true;
-                            throw je;
-                        }
+                RescueBodyNode rescueNode = 
getExceptionHandler(iVisited.getRescueNode(), raisedException, runtime, 
context, self, aBlock);
+
+                if (rescueNode != null) {
+                    try {
+                        return evalInternal(runtime,context, rescueNode, self, 
aBlock);
+                    } catch (JumpException.RetryJump rj) {
+                        // should be handled in the finally block below
+                        //state.runtime.getGlobalVariables().set("$!", 
state.runtime.getNil());
+                        //state.threadContext.setRaisedException(null);
+                        continue RescuedBlock;
+                    } catch (RaiseException je) {
+                        anotherExceptionRaised = true;
+                        throw je;
                     }
-                    
-                    rescueNode = rescueNode.getOptRescueNode();
+                } else {
+                    // no takers; bubble up
+                    throw raiseJump;
                 }
-
-                // no takers; bubble up
-                throw raiseJump;
             } finally {
                 // clear exception when handled or retried
                 if (!anotherExceptionRaised)
@@ -1626,6 +1636,37 @@ public class ASTInterpreter {
             }
         }
     }
+    
+    private static RescueBodyNode getExceptionHandler(
+            RescueBodyNode rescueNode, RubyException raisedException,
+            Ruby runtime, ThreadContext context, IRubyObject self,
+            Block aBlock) {
+        
+        while (rescueNode != null) {
+            Node  exceptionNodes = rescueNode.getExceptionNodes();
+            ListNode exceptionNodesList;
+
+            if (exceptionNodes instanceof SplatNode) {                    
+                exceptionNodesList = (ListNode) evalInternal(runtime,context, 
exceptionNodes, self, aBlock);
+            } else {
+                exceptionNodesList = (ListNode) exceptionNodes;
+            }
+
+            IRubyObject[] exceptions;
+            if (exceptionNodesList == null) {
+                exceptions = new IRubyObject[] 
{runtime.fastGetClass("StandardError")};
+            } else {
+                exceptions = setupArgs(runtime, context, exceptionNodes, self, 
aBlock);
+            }
+            if (RuntimeHelpers.isExceptionHandled(raisedException, exceptions, 
runtime, context, self).isTrue()) {
+                return rescueNode;
+            }
+
+            rescueNode = rescueNode.getOptRescueNode();
+        }
+
+        return null;
+    }
 
     private static IRubyObject retryNode(ThreadContext context) {
         context.pollThreadEvents();

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email

Reply via email to