Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 353535 - Eclipse compiler generates wrong bytecode for nested try-with-resources statements
Summary: Eclipse compiler generates wrong bytecode for nested try-with-resources state...
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.7   Edit
Hardware: PC Windows 7
: P3 major (vote)
Target Milestone: 3.7.1   Edit
Assignee: Olivier Thomann CLA
QA Contact:
URL:
Whiteboard:
Keywords:
: 354108 356018 (view as bug list)
Depends on:
Blocks:
 
Reported: 2011-08-02 02:15 EDT by vsb CLA
Modified: 2011-09-14 12:19 EDT (History)
6 users (show)

See Also:
srikanth_sankaran: review+


Attachments
Proposed fix + regression tests (5.93 KB, patch)
2011-08-16 11:29 EDT, Olivier Thomann CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description vsb CLA 2011-08-02 02:15:54 EDT
Simple example to reproduce:

package test;

public class Main {
    public static void main(String[] args) throws Throwable {
        int tmp;
        try (A a = null) {
            try (A b = null) {
                tmp = 0;
            }
        }
    }
}

class A implements AutoCloseable {
    @Override
    public void close() {
    }
}

When this code is compiled and run under Eclipse, following exception throwed:

Exception in thread "main" java.lang.VerifyError: Stack map does not match the one at exception handler 103 in method test.Main.main([Ljava/lang/String;)V at offset 4
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2442)
	at java.lang.Class.getMethod0(Class.java:2685)
	at java.lang.Class.getMethod(Class.java:1620)
	at sun.launcher.LauncherHelper.getMainMethod(LauncherHelper.java:484)
	at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:476)

When using Oracle Java compiler from JDK 7.0, this code compiles and run without problems.

I used component named "Eclipse Java Development Tools Patch for Java 7 Support (BETA) 1.0.0.v20110714-1300" to enable Java 7 support.
Comment 1 Srikanth Sankaran CLA 2011-08-02 02:43:02 EDT
I'll take a look.
Comment 2 Srikanth Sankaran CLA 2011-08-02 03:05:27 EDT
May be a dup or variant of bug 351653.
Very similar to that problem, the verify error goes away if
you initialize the variable tmp.
Comment 3 Srikanth Sankaran CLA 2011-08-08 04:45:23 EDT
*** Bug 354108 has been marked as a duplicate of this bug. ***
Comment 4 Srikanth Sankaran CLA 2011-08-08 04:46:24 EDT
Another test case from bug 354108

import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class Test {

public static void main(String[] args) throws Exception {
  int b;
  try (final InputStream in = new ByteArrayInputStream(new byte[] { 42 })) {
    b = in.read();
  }
}
}
Comment 5 Olivier Thomann CLA 2011-08-15 16:04:40 EDT
Unless I missed something, this seems to be fixed into the HEAD stream.
I'll verify the 3.7 maintenance stream.
Comment 6 Olivier Thomann CLA 2011-08-15 16:10:33 EDT
It looks like this is also fixed into version v_B74_R37x.
Vbs, could you please verify with either one of the latest maintenance build (3.7.x or 4.1.x) or one of the latest I build (3.8 or 4.2).
Comment 7 Srikanth Sankaran CLA 2011-08-16 03:23:52 EDT
(In reply to comment #5)
> Unless I missed something, this seems to be fixed into the HEAD stream.
> I'll verify the 3.7 maintenance stream.

Olivier, both Ayush and I could reproduce this in HEAD as well as
3.7.1 maintenance stream top of branch, using JDK 7b147.

I have released two disabled regression tests for these via

org.eclipse.jdt.core.tests.compiler.regression.TryWithResourcesStatementTest._test055() and

org.eclipse.jdt.core.tests.compiler.regression.TryWithResourcesStatementTest._test055a().

Thanks for looking into this.
Comment 8 Olivier Thomann CLA 2011-08-16 07:44:50 EDT
(In reply to comment #7)
> Olivier, both Ayush and I could reproduce this in HEAD as well as
> 3.7.1 maintenance stream top of branch, using JDK 7b147.
Ok, I got it now. The option -preserveAllLocals needs to be used to reproduce the issue. I'll take a look today.
Comment 9 Olivier Thomann CLA 2011-08-16 10:22:17 EDT
Ok, I know what is going on. Let's take the example from comment 4.
public static void main(String[] args) throws Exception {
  int b;
  try (final InputStream in = new ByteArrayInputStream(new byte[] { 42 })) {
    b = in.read();
  }
}
The uninitialized variable 'b' that is initialized within the twr block has a wrong initialization range. Note that the javac code generation is not perfect as well for the local variable table. It considers only one interval where the variable is actually initialized on multiple intervals.

Here is the generated code:
  public static void main(java.lang.String[] args) throws java.lang.Exception;
     0  aconst_null
     1  astore_2
     2  aconst_null
     3  astore_3
     4  new java.io.ByteArrayInputStream [19]
     7  dup
     8  iconst_1
     9  newarray byte [8]
    11  dup
    12  iconst_0
    13  bipush 42
    15  bastore
    16  invokespecial java.io.ByteArrayInputStream(byte[]) [21]
    19  astore 4 [in]
    21  aload 4 [in]
    23  invokevirtual java.io.InputStream.read() : int [24]
    26  istore_1 [b]
    27  aload 4 [in]
    29  ifnull 75
    32  aload 4 [in]
    34  invokevirtual java.io.InputStream.close() : void [30]
    37  goto 75
    40  astore_2
    41  aload 4 [in]
    43  ifnull 51
    46  aload 4 [in]
    48  invokevirtual java.io.InputStream.close() : void [30]
    51  aload_2
    52  athrow
    53  astore_3
    54  aload_2
    55  ifnonnull 63
    58  aload_3
    59  astore_2
    60  goto 73
    63  aload_2
    64  aload_3
    65  if_acmpeq 73
    68  aload_2
    69  aload_3
    70  invokevirtual java.lang.Throwable.addSuppressed(java.lang.Throwable) : void [33]
    73  aload_2
    74  athrow
    75  getstatic java.lang.System.out : java.io.PrintStream [39]
    78  ldc <String "Done"> [45]
    80  invokevirtual java.io.PrintStream.println(java.lang.String) : void [47]
    83  return
      Exception Table:
        [pc: 21, pc: 27] -> 40 when : java.lang.Throwable
        [pc: 4, pc: 53] -> 53 when : java.lang.Throwable
      Line numbers:
        [pc: 0, line: 7]
        [pc: 21, line: 8]
        [pc: 27, line: 9]
        [pc: 75, line: 10]
        [pc: 83, line: 11]
      Local variable table:
        [pc: 0, pc: 84] local: args index: 0 type: java.lang.String[]
        [pc: 27, pc: 84] local: b index: 1 type: int
        [pc: 21, pc: 51] local: in index: 4 type: java.io.InputStream
      Stack map table: number of frames 6
        [pc: 40, full, stack: {java.lang.Throwable}, locals: {java.lang.String[], int, java.lang.Throwable, java.lang.Throwable, java.io.InputStream}]
        [pc: 51, chop 1 local(s)]
        [pc: 53, same_locals_1_stack_item, stack: {java.lang.Throwable}]
        [pc: 63, same]
        [pc: 73, same]
        [pc: 75, chop 2 local(s)]

You can see thgat the local b is considered to be initialized from 27 till the end of the method. If an exception occurs between line 4 and 27, b is not initialized when reaching the pc 53 (any exception handler), but the stack map in 53 is said to have the same locals from the stack at pc 40 minus the last local (chop at pc 51). This is wrong. The local at position 1 is actually not initialized so we should issue a full stack item instead of the actual stack item.
I'll investigate how to fix it.

Javac generates the following code:
  public static void main(String[] args) throws Exception;
      0  new ByteArrayInputStream [2]
      3  dup
      4  iconst_1
      5  newarray byte [8]
      7  dup
      8  iconst_0
      9  bipush 42
     11  bastore
     12  invokespecial ByteArrayInputStream(byte[]) [3]
     15  astore_2 [in]
     16  aconst_null
     17  astore_3
     18  aload_2 [in]
     19  invokevirtual InputStream.read() : int [4]
     22  istore_1 [b]
     23  getstatic System.out : PrintStream [5]
     26  iload_1 [b]
     27  invokevirtual PrintStream.println(int) : void [6]
     30  aload_2 [in]
     31  ifnull 106
     34  aload_3
     35  ifnull 56
     38  aload_2 [in]
     39  invokevirtual InputStream.close() : void [7]
     42  goto 106
     45  astore 4 [x2]
     47  aload_3
     48  aload 4 [x2]
     50  invokevirtual Throwable.addSuppressed(Throwable) : void [9]
     53  goto 106
     56  aload_2 [in]
     57  invokevirtual InputStream.close() : void [7]
     60  goto 106
     63  astore 4
     65  aload 4
     67  astore_3
     68  aload 4
     70  athrow
     71  astore 5
     73  aload_2 [in]
     74  ifnull 103
     77  aload_3
     78  ifnull 99
     81  aload_2 [in]
     82  invokevirtual InputStream.close() : void [7]
     85  goto 103
     88  astore 6 [x2]
     90  aload_3
     91  aload 6 [x2]
     93  invokevirtual Throwable.addSuppressed(Throwable) : void [9]
     96  goto 103
     99  aload_2 [in]
    100  invokevirtual InputStream.close() : void [7]
    103  aload 5
    105  athrow
    106  getstatic System.out : PrintStream [5]
    109  ldc <String "Done"> [10]
    111  invokevirtual PrintStream.println(String) : void [11]
    114  return
      Exception Table:
        [pc: 38, pc: 42] -> 45 when : Throwable
        [pc: 18, pc: 30] -> 63 when : Throwable
        [pc: 18, pc: 30] -> 71 when : any
        [pc: 81, pc: 85] -> 88 when : Throwable
        [pc: 63, pc: 73] -> 71 when : any
      Line numbers:
        [pc: 0, line: 7]
        [pc: 18, line: 8]
        [pc: 23, line: 9]
        [pc: 30, line: 10]
        [pc: 63, line: 7]
        [pc: 71, line: 10]
        [pc: 106, line: 11]
        [pc: 114, line: 12]
      Local variable table:
        [pc: 47, pc: 53] local: x2 index: 4 type: Throwable
        [pc: 90, pc: 96] local: x2 index: 6 type: Throwable
        [pc: 16, pc: 106] local: in index: 2 type: InputStream
        [pc: 0, pc: 115] local: args index: 0 type: String[]
        [pc: 23, pc: 115] local: b index: 1 type: int
      Stack map table: number of frames 8
        [pc: 45, full, stack: {Throwable}, locals: {String[], int, InputStream, Throwable}]
        [pc: 56, same]
        [pc: 63, full, stack: {Throwable}, locals: {String[], _, InputStream, Throwable}]
        [pc: 71, same_locals_1_stack_item, stack: {Throwable}]
        [pc: 88, full, stack: {Throwable}, locals: {String[], _, InputStream, Throwable, _, Throwable}]
        [pc: 99, same]
        [pc: 103, same]
        [pc: 106, full, stack: {}, locals: {String[], int}]
}

You can see the full stack item that is showing an uninitialized variable at position 1. But their local variable entry for 'b' is bogus as well.
Comment 10 Olivier Thomann CLA 2011-08-16 11:29:21 EDT
Created attachment 201585 [details]
Proposed fix + regression tests

Srikanth, please verify.
Comment 11 Srikanth Sankaran CLA 2011-08-18 05:18:41 EDT
Analysis and fix look good.
Comment 12 Olivier Thomann CLA 2011-08-18 13:08:17 EDT
Released for both HEAD and 3.7 maintenance streams.
Comment 13 Jay Arthanareeswaran CLA 2011-08-25 06:57:15 EDT
Verified for 3.7 RC2 with build M20110824-0800.
Comment 14 Olivier Thomann CLA 2011-08-29 12:34:07 EDT
*** Bug 356018 has been marked as a duplicate of this bug. ***
Comment 15 Ayushman Jain CLA 2011-09-14 12:18:39 EDT
Verified for 3.8M2 using build I20110911-2000.
Comment 16 Ayushman Jain CLA 2011-09-14 12:19:16 EDT
(In reply to comment #13)
> Verified for 3.7 RC2 with build M20110824-0800.

This should be 3.7.1 and not 3.7 :)