JDK-8194978 : Javac produces dead code for try-with-resource
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 7,8,9.0.4,10
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2018-01-11
  • Updated: 2018-07-26
  • Resolved: 2018-03-22
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11
11 b07Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
CYGWIN_NT-10.0 INFORMA-L7T6GPJ 2.9.0(0.318/5/3) 2017-09-12 10:18 x86_64 Cygwin

A DESCRIPTION OF THE PROBLEM :
A try-with-resource produces dead code.  This results in inability to get to full-code-coverage (as reported by tools like jacoco).

The root of the problem is the strange "aconst_null"/"astore" that happens which is subseqently put through a "ifnull" checks.  Many have noted it, such as this thread:
https://stackoverflow.com/questions/25615417/try-with-resources-introduce-unreachable-bytecode

try-with-resource has been broken since introduction (I think) in Java7.  Full coverage has never been possible.   Using older try-finally techniques (ala Java6) allows for full coverage.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
compile code and view in javap - output shown in 'expected result'

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
There should be no aconst_null (line 24) or astore 5 (line 25).  

Having it causes the ifnull check (line 47) to be forced and lines 50-57 are unreachable, lines 60-69 unreachable (no exception possible from 50-57), and another forced outcome at line 100 making 103-110 unreachable, and 113-122 unreachable (no exception from 103-110 possible)
ACTUAL -
 public void dummy() throws java.sql.SQLException;
    Code:
       0: ldc           #88                 // String 1
       2: astore_1
       3: ldc           #89                 // String
       5: astore_2
       6: ldc           #89                 // String
       8: astore_3
       9: aload_0
      10: invokevirtual #90                 // Method baz:()V
      13: ldc           #88                 // String 1
      15: ldc           #89                 // String
      17: ldc           #89                 // String
      19: invokestatic  #91                 // Method java/sql/DriverManager.getConnection:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/sql/Connection;
      22: astore        4
      24: aconst_null
      25: astore        5
      27: aload_0
      28: invokevirtual #92                 // Method foo:()V
      31: aload         4
      33: ifnull        40
      36: aload_0
      37: invokevirtual #93                 // Method bar:()V
      40: aload         4
      42: ifnull        135
      45: aload         5
      47: ifnull        72
      50: aload         4
      52: invokeinterface #94,  1           // InterfaceMethod java/sql/Connection.close:()V
      57: goto          135
      60: astore        6
      62: aload         5
      64: aload         6
      66: invokevirtual #96                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
      69: goto          135
      72: aload         4
      74: invokeinterface #94,  1           // InterfaceMethod java/sql/Connection.close:()V
      79: goto          135
      82: astore        6
      84: aload         6
      86: astore        5
      88: aload         6
      90: athrow
      91: astore        7
      93: aload         4
      95: ifnull        132
      98: aload         5
     100: ifnull        125
     103: aload         4
     105: invokeinterface #94,  1           // InterfaceMethod java/sql/Connection.close:()V
     110: goto          132
     113: astore        8
     115: aload         5
     117: aload         8
     119: invokevirtual #96                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     122: goto          132
     125: aload         4
     127: invokeinterface #94,  1           // InterfaceMethod java/sql/Connection.close:()V
     132: aload         7
     134: athrow
     135: aload_0
     136: invokevirtual #97                 // Method bak:()V
     139: return
    Exception table:
       from    to  target type
          50    57    60   Class java/lang/Throwable
          27    40    82   Class java/lang/Throwable
          27    40    91   any
         103   110   113   Class java/lang/Throwable
          82    93    91   any


ERROR MESSAGES/STACK TRACES THAT OCCUR :
Code works OK, just produces dead code

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
	public void dummy() throws SQLException
	{
		final String dbUrl1 = "1";
		final String username = "";
		final String password = "";
		baz();
        try (Connection conn1 = DriverManager.getConnection(dbUrl1,username,password)) {
        	foo();
        	if (conn1 != null)
        		bar();
        }
        
		bak();
	}
	public void foo() {/**/ }
	public void bar() {/**/ }
	public void baz() {/**/ }
	public void bak() {/**/ }

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
switch back to try-finally for autocloseables.


Comments
Review thread: http://mail.openjdk.java.net/pipermail/compiler-dev/2018-March/011753.html
16-03-2018

I think the problem is along these lines: -consider code like: --- public static void main(String[] args) throws IOException { try (InputStream in = new FileInputStream("")) { System.err.println(in.read()); } } --- -javac desugars the try-with-resources to a code along these lines (JDK 8, there are some more variants in JDK 9): --- public static void main(String[] args) throws IOException { { final InputStream in = new FileInputStream(""); /*synthetic*/ Throwable primaryException0$ = null; try { System.err.println(in.read()); } catch (/*synthetic*/ final Throwable t$) { primaryException0$ = t$; throw t$; } finally { if (in != null) if (primaryException0$ != null) try { in.close(); } catch (/*synthetic*/ Throwable x2) { primaryException0$.addSuppressed(x2); } else in.close(); } } } --- That by itself is not problematic, and is consistent with the JLS (JLS 14.20.3.1). But then, when the bytecode for this is generated, the statements from finally are copied to all catches and the main try block. Roughly as follows, per my understanding (in classfile, the try-catch-finally are represented a little bit differently): --- public static void main(String[] args) throws IOException { { final InputStream in = new FileInputStream(""); /*synthetic*/ Throwable primaryException0$ = null; try { System.err.println(in.read()); } catch (/*synthetic*/ final Throwable t$) { try { primaryException0$ = t$; throw t$; } finally { //copied finally block start if (in != null) if (primaryException0$ != null) try { in.close(); } catch (/*synthetic*/ Throwable x2) { primaryException0$.addSuppressed(x2); } else in.close(); //copied finally block end } } //copied finally block start if (in != null) if (primaryException0$ != null) try { in.close(); } catch (/*synthetic*/ Throwable x2) { primaryException0$.addSuppressed(x2); } else in.close(); //copied finally block end } } --- (This is unfortunately necessary, because javac can't use the "jsr" instruction to implement finally.) (The in != null tests are eliminated in JDK 9 where possible.) The thing to note is that for the last copy (which is for the try block), primaryException0$ is never non-null, hence the test is unnecessary and so is the "true" section.
23-01-2018

Desugaring of try-with-resources is handled in javac's Lower class; e.g. http://hg.openjdk.java.net/jdk/jdk/file/20ed1cebe5f8/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java#l1547
17-01-2018

This issue is reproducble in all the versions, verified with 8u162, 9.0.1, 10 ea b37. 8u162 - Fail 9.0.1 GA - Fail 10 ea b37 - Fail
12-01-2018