Issue Details (XML | Word | Printable)

Key: JANINO-56
Type: Bug Bug
Status: Resolved Resolved
Resolution: Fixed
Priority: Major Major
Assignee: Arno Unkrig
Reporter: Arno Unkrig
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
Janino

VerifyError in the context of a FINALLY caluse

Created: 08/May/06 03:04 PM   Updated: 26/Jun/06 05:06 PM
Component/s: None
Affects Version/s: None
Fix Version/s: None

Time Tracking:
Not Specified

File Attachments: 1. Text File try.patch (3 kB)



 Description  « Hide
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.

Test.java
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.

Test2.java
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



 All   Comments   Work Log   Change History      Sort Order: Ascending order - Click to sort in descending order
Arno Unkrig added a comment - 16/May/06 03:17 PM
I added a unit test on "ReportedBugs.java" that reproduces the problems, but haven't got around to fixing it. Stay tuned!

Mo DeJong added a comment - 16/May/06 04:02 PM
This is not really the correct solution, but I was able to work around
the verifier errors in my code with the following patch. This patch uses
a higher register number and that does not trigger the error in the
verifier. This patch does not fix the problem for the small test case
in this bug report.

Arno Unkrig added a comment - 26/Jun/06 05:06 PM
See comments in UnitCompiler.java:

// Allocate a LV for the JSR of the FINALLY clause.
//
// Notice:
// For unclear reasons, this variable must not overlap with any of the body's
// variables (although the body's variables are out of scope when it comes to the
// FINALLY clause!?), otherwise you get
// java.lang.VerifyError: ... Accessing value from uninitialized localvariable 4
// See bug #56.

// Save the exception object [ of the FINALLY clause] in an anonymous local variable.
...
// The exception object local variable allocated above MUST NOT BE RELEASED
// until after the FINALLY block is compiled, for otherwise you get
// java.lang.VerifyError: ... Accessing value from uninitialized register 7

Fix will go into 2.4.5.