Details
-
Type:
Bug
-
Status:
Resolved
-
Priority:
Major
-
Resolution: Fixed
-
Affects Version/s: None
-
Fix Version/s: None
-
Component/s: None
-
Labels:None
-
Number of attachments :
Description
Hello
I have been doing quite a bit of analysis of the
verify error that I am seeing with Janino compiled
code. This is on a Win32 system with JDK 1.4 from Sun.
It seems the problem is actually in the Java verifier,
but Sun's javac does not trigger it because it generates
code that differs from the code generated by Janino.
Here is a very small example that triggers the bug.
public class Test { public static void foo() { try { // 3 vars must be declared in try block int dummy5 = 5; int dummy4 = 4; boolean b = true; while (b) { try { return; } catch (Exception ex) {} } } finally {} } public static void main(String[] args) { int loops = 0; foo(); if (loops == 0) { System.out.println("OK"); } else { System.out.println("ERROR"); } } }
This class compiles and runs correctly with javac.
When compiled with Janino, the following error
is generated:
$ janinoc Test.java $ java Test Exception in thread "main" java.lang.VerifyError: (class: Test, method: foo sign ature: ()V) Accessing value from uninitialized register 2
The problem appears to be that a use of the local var slot 2
generating the error. The while loop reads slot 2 when
it tests the boolean "b". It turn out that the generated
code also uses slot 2 is jsr/ret instructions.
One can test this slot 2 thing by commenting out the "dummy1"
and "dummy2" variables. The following example compiles and
does not fail the validate step, the only diff is that the
two local vars are commented out.
public class Test2 { public static void foo() { try { // 3 vars must be declared in try block //int dummy5 = 5; //int dummy4 = 4; boolean b = true; while (b) { try { return; } catch (Exception ex) {} } } finally {} } public static void main(String[] args) { int loops = 0; foo(); if (loops == 0) { System.out.println("OK"); } else { System.out.println("ERROR"); } } }
$ janinoc Test2.java $ java Test2 OK
Taking a peek at the generated code emitted by javac and
janino helps to understand the problem. I have only included
the byte code for the foo() method here.
// Javac output for Test.java public static void foo(); Code: 0: iconst_5 1: istore_0 2: iconst_4 3: istore_1 4: iconst_1 5: istore_2 (while non-constant expr test) 6: iload_2 7: ifeq 15 (end of method foo) 10: return 11: astore_3 12: goto 6 (catch block, invoke finally block) 15: goto 23 (unhandled exception) 18: astore 4 20: aload 4 22: athrow (finally block) 23: return
// Janino output (Test.java, this fails validation) public static void foo(); throws Code: 0: iconst_5 1: istore_0 2: iconst_4 3: istore_1 (b = false) 4: iconst_1 5: istore_2 (while) 6: goto 14 (return) 9: jsr 27 12: return 13: astore_3 (while non-constant expr test) 14: iload_2 15: ifne 9 18: goto 30 (catch block, invoke finally block) 21: astore_0 22: jsr 27 (unhandled exception) 25: aload_0 26: athrow (finally block) 27: astore_2 28: ret 2 30: jsr 27 33: return
At first glance, it does not look like anything is wrong
with the code emitted by Janino. In fact, one can compare
the janino emitted code for Test.java to the janino emitted
code for Test2.java:
// Janino code for Test2.java (no dummy vars) public static void foo(); throws Code: (b = false) 0: iconst_1 1: istore_0 (while) 2: goto 10 (return) 5: jsr 23 8: return 9: astore_1 (while non-constant expr test) 10: iload_0 11: ifne 5 14: goto 26 (catch block, invoke finally block) 17: astore_0 18: jsr 23 (unhandled exception) 21: aload_0 22: athrow (finally block) 23: astore_2 24: ret 2 26: jsr 23 29: return
The janino emitted code for Test.java fails, but the same logic
does not fail for Test2.java. The diff is that Test2.java does
not use local slot 2 for the boolean in the while expr condition.
The boolean "b" in Test2.java uses slot 0 so the use of slot 2
in the jsr/ret instructions does not trigger the validation error.
I see a couple of possible approaches to work around this issue.
Janino could be modified to emit code that worked more like javac,
so that jsr/ret instructions would not be used in these situations.
Another possible approach would be to detect when a loop uses a
register that will also be used in try/catch/finally blocks. The
try/catch/finally blocks could then use some other register to
implement the jsr/ret logic. Any advice as to which approach
would be better?
cheers
Mo DeJong
I added a unit test on "ReportedBugs.java" that reproduces the problems, but haven't got around to fixing it. Stay tuned!