Bug 194398 - [compiler] Possible wrong bytecode generated for nested try-finally blocks: ret is part of more than one subroutine
Summary: [compiler] Possible wrong bytecode generated for nested try-finally blocks: r...
Status: VERIFIED INVALID
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.3   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: 3.4 M1   Edit
Assignee: Olivier Thomann CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-06-26 07:16 EDT by Nina Rinskaya CLA
Modified: 2007-12-20 06:53 EST (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Nina Rinskaya CLA 2007-06-26 07:16:27 EDT
Eclipse Compiler 3.2 and 3.3 seems to generate wrong bytecode for nested try-finally blocks (see the code for reproducing below). 

Apache Harmony throws VerifyError when running such class (java.lang.VerifyError: (class: Test, method: test()Z) A subroutine splits execution into several ret instructions) and BCEL 5.2 tool also reports verification issue (Instruction ' 53: ret[169](2) 1' is part of more than one subroutine (or of the top level and a subroutine)).

Please find more details below (see instruction '53: ret 1', it seems to return from 2 different subroutines depending on execution branch). 

It seems that Sun verifier doesn't check this specific constraint in this case, but Apache Harmony and BCEL tool strictly follow specification which says: 

"When executing the ret instruction, which implements a return from a subroutine, there must be only one possible subroutine from which the instruction can be returning." 

I might probably misunderstand what the specification says or how the bytecode below can be interpreted, so I would be very grateful for the explanations then. Thanks!

To reproduce:

1. Create Test.java:
-------------------------------------
public class Test {

    public boolean test() {
        try {
            Thread.sleep(10);
        } catch (Exception e) {
        } finally {
            try {
                try {
                    Thread.sleep(10);
                } finally {
                    Thread.sleep(10);
                }
            } catch (Exception e) {
            }
        }
        return true;
    }

    public static void main(String[] args) {
        new Test().test();
        System.out.println("SUCCESS");
    }

}
-------------------------------------

2. Compile Test.java (below) with Eclipse 3.2/3.3 compiler:

C:\jdk1.5.0_06\bin\java.exe -classpath c:\work\eut_eclipse\eclipse\plugins\org.eclipse.jdt.core_3.2.0.v_671.jar org.eclipse.jdt.internal.compiler.batch.Main 

Test.java

3. Output on Apache Harmony:

===============================
Apache Harmony Launcher : (c) Copyright 1991, 2006 The Apache Software Foundation or its licensors, as applicable.
java version "1.5.0"
pre-alpha : not complete or compatible
svn = r547521, (Jun 15 2007), Windows/ia32/msvc 1310, release build
http://harmony.apache.org
Uncaught exception in main:
java.lang.VerifyError: (class: Test, method: test()Z) A subroutine splits execution into several ret instructions
        at java.lang.ClassLoader.defineClass0(ClassLoader.java)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:438)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:70)
        at java.net.URLClassLoader.access$3(URLClassLoader.java:1)
        at java.net.URLClassLoader$URLHandler.createClass(URLClassLoader.java:261)
        at java.net.URLClassLoader$URLFileHandler.findClass(URLClassLoader.java:560)
        at java.net.URLClassLoader.findClassImpl(URLClassLoader.java:1194)
        at java.net.URLClassLoader$4.run(URLClassLoader.java:889)
        at java.net.URLClassLoader$4.run(URLClassLoader.java:1)
        at java.security.AccessController.doPrivilegedImpl(AccessController.java:171)
        at java.security.AccessController.doPrivileged(AccessController.java:64)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:891)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:575)
        at java.lang.ClassLoader$SystemClassLoader.loadClass(ClassLoader.java:963)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:319)
FAILED to invoke JVM.
================================

Note, that if '-noverify' option is specified the test passes ok and all "finally" blocks are executed correctly.


Output on Sun Java 1.5 (with or without -Xfuture):

java version "1.5.0_06"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)
Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode)

SUCCESS

4. The bytecode below corresponds to Eclipse compiler 3.2 compiled .class file:
-----------------
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
  Code:
   0: aload_0
   1: invokespecial #8; //Method java/lang/Object."<init>":()V
   4: return

public boolean test();
  Code:
   0: ldc2_w #13; //long 10l
   3: invokestatic #15; //Method java/lang/Thread.sleep:(J)V
   6: goto 55
   9: pop
   10: goto 55
   13: astore_2
   14: jsr 19
   17: aload_2
   18: athrow
   19: astore_1
   20: ldc2_w #13; //long 10l
   23: invokestatic #15; //Method java/lang/Thread.sleep:(J)V
   26: goto 46
   29: astore 4
   31: jsr 37
   34: aload 4
   36: athrow
   37: astore_3
   38: ldc2_w #13; //long 10l
   41: invokestatic #15; //Method java/lang/Thread.sleep:(J)V
   44: ret 3
   46: jsr 37
   49: goto 53
   52: pop
   53: ret 1
   55: jsr 19
   58: iconst_1
   59: ireturn
  Exception table:
   from to target type
     0 9 9 Class java/lang/Exception

     0 13 13 any
    55 58 13 any
    20 29 29 any
    46 49 29 any
    20 52 52 Class java/lang/Exception


public static void main(java.lang.String[]);
  Code:
   0: new #1; //class Test
   3: dup
   4: invokespecial #25; //Method "<init>":()V
   7: invokevirtual #26; //Method test:()Z
   10: pop
   11: getstatic #28; //Field java/lang/System.out:Ljava/io/PrintStream;
   14: ldc #34; //String SUCCESS
   16: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   19: return

}

5. BCEL 5.2 tool (http://jakarta.apache.org/bcel) reports:

$java -cp C:\work\harmony-jdk-r540603\jre\lib\boot\bcel-5.2\bcel-5.2.jar;. org.apache.bcel.verifier.Verifier Test.class
....
Pass 3b, method number 1 ['public boolean test()']:
VERIFIED_REJECTED
Constraint violated in method 'public boolean test()':
Instruction ' 53: ret[169](2) 1' is part of more than one subroutine (or of the top level and a subroutine).
....
Comment 1 Alexei Fedotov CLA 2007-07-05 08:45:02 EDT
Here is a trackback URL:
http://issues.apache.org/jira/browse/HARMONY-3862
Comment 2 Olivier Thomann CLA 2007-07-05 14:02:14 EDT
A workaround is to use the option to inline jsrs.
Comment 3 Olivier Thomann CLA 2007-07-05 15:25:38 EDT
Same bug is found in old 1.4 javac version (1.4.0_04).
Latest versions of javac 1.4.2 are inlining the inner finally blocks. So they never end up with a shared ret bytecodes.
Comment 4 Olivier Thomann CLA 2007-07-05 15:45:11 EDT
Using the option -XDjsrlimit=0 with latest jdk1.4.2 produces a .class file that leads to the same error.
Comment 5 Philipe Mulet CLA 2007-07-09 07:07:50 EDT
I think the problem comes from the exception handlers.
In theory, the line [20 52 52 Class java/lang/Exception] means that it is possible to escape from nested finally back into outer one; which must cause grief to bytecode verifier.

Need to check code generation, which is supposed to properly exit/reenter code blocks as appropriate (and this would indicate the bug if not doing it properly).
I believe it should generate a sub interval for the nested finally block.
Comment 6 Philipe Mulet CLA 2007-07-09 07:57:19 EDT
Actually, the exception handler is correct, to ensure the proper control transfer if an exception occurs at pc 41.

Could it be that the spec is being too strict ? And the reason why Sun's verifier doesn't enforce the extra constraint ?
Comment 7 Olivier Thomann CLA 2007-07-09 12:17:47 EDT
It seems that it is legal to return to a higher level in the subroutines call chain.
From the JVMS (2nd edition):
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#9308

"Each instance of type returnAddress can be returned to at most once. If a ret instruction returns to a point in the subroutine call chain above the ret instruction corresponding to a given instance of type returnAddress, then that instance can never be used as a return address."

This would mean that as long as the ret instruction is executed only once, this is fined. It would be a verify error if the ret 3 could be executed after the ret 1 has been executed.

So I would close this one as WONTFIX since the code generation is actually fine and it seems that the Harmony bytecode verifier is too strict.

Philippe, any comment on this ?
Comment 8 Philipe Mulet CLA 2007-07-09 12:25:29 EDT
Makes sense, otherwise, I don't see how we could generate proper bytecode meeting runtime expectation (other than by inlining some subroutines, but this would feel a bit of a hack).

I'd like to hear back from Harmony verifier...
Comment 9 Olivier Thomann CLA 2007-07-09 12:33:32 EDT
Then we should close as INVALID.
I am waiting from an update coming from the Harmony team.
Comment 10 Olivier Thomann CLA 2007-07-10 10:05:24 EDT
Closing as INVALID.
Comment 11 Alexei Fedotov CLA 2007-07-10 17:06:01 EDT
Thanks, Philippe and Olivier, for an excellent citation. It seems the following is true: a subroutine may conditionally branch to the code which is part of an upper level subroutine.
Comment 12 Frederic Fusier CLA 2007-12-20 06:53:43 EST
Verified by reporter