RFR JDK-8194978: Javac produces dead code for try-with-resource

Jan Lahoda jan.lahoda at oracle.com
Thu Mar 15 15:40:42 UTC 2018


Hi,

Bug: https://bugs.openjdk.java.net/browse/JDK-8194978

The issue here is that the bytecode generated for try-with-resources 
produces some unreachable instructions, and may be unnecessarily big.

Consider this code:
---
         try (InputStream in = new FileInputStream(str)) {
             int read = in.read();
             if (read == (-1)) return ;
             System.err.println(read);
         }
---

javac desugars it to this:
---
         {
             final InputStream in = new FileInputStream(str);
             /*synthetic*/ Throwable primaryException0$ = null;
             try {
                 int read = in.read();
                 if (read == (-1)) return;
                 System.err.println(read);
             } catch (/*synthetic*/ final Throwable t$) {
                 primaryException0$ = t$;
                 throw t$;
             } finally {
                 if (primaryException0$ != null) try {
                     in.close();
                 } catch (/*synthetic*/ Throwable x2) {
                     primaryException0$.addSuppressed(x2);
                 } else in.close();
             }
         }
---

Which seems fine, but when the try-finally construct is written into 
bytecode, the "finally" section is duplicated into all (non-exceptional) 
exit points from the try body (it would also be copied to non-exception 
exit points from all catches, but there are no such exit points in this 
case). A rough idea on how the bytecode looks like is below - please 
note that some try-catches don't embed nicely with the rest of the code, 
so are denoted in comments only.

---
         {
             final InputStream in = new FileInputStream(str);
             /*synthetic*/ Throwable primaryException0$ = null;
             // try
                 int read = in.read();
                 if (read == (-1)) {
             // catch Throwable: goto CATCH
             // catch any: goto FINALLY [1]
                     //start injected finally
                     if (primaryException0$ != null) try { // [2]
                         in.close();
                     } catch (/*synthetic*/ Throwable x2) {
                         primaryException0$.addSuppressed(x2);
                     } else in.close();
                     //end injected finally

                     return;
                 }

             // try
                 System.err.println(read);
             // catch Throwable: goto CATCH
             // catch any: goto FINALLY [1]

             //start injected finally
             if (primaryException0$ != null) try { // [2]
                 in.close();
             } catch (/*synthetic*/ Throwable x2) {
                 primaryException0$.addSuppressed(x2);
             } else in.close();
             //end injected finally

             //goto END

             CATCH: // catch (/*synthetic*/ final Throwable t$)
                 // try
                 primaryException0$ = t$;
                 throw t$;
                 // catch any: goto FINALLY
             FINALLY:
                 if (primaryException0$ != null) try {// [3]
                     in.close();
                 } catch (/*synthetic*/ Throwable x2) {
                     primaryException0$.addSuppressed(x2);
                 } else in.close();
             END:
         }
---

A few comments about the code:
-[1]: this catches seem unnecessary: all Throwables have already been 
caught?
-[2]: at this place, it is clear that primaryException0$ is null, so the 
is-non-null section is not useful
-[3]: at this place, it is mostly clear that primaryException0$ is 
not-null, so the is-null section is not useful

So, it seems it might be possible to construct the code so that only the 
needed sections of the finally would be used at the appropriate places. 
I.e. the original try-with-resources would get desugared to something like:

---
         {
             final InputStream in = new FileInputStream(str);
             try {
                 int read = in.read();
                 if (read == (-1)) return;
                 System.err.println(read);
             } body-only-finally { //copied to exit points in try body
                                   //only, not used as a real finally
                 in.close();
             } catch (/*synthetic*/ final Throwable t$) {
                 try {
                     in.close();
                 } catch (/*synthetic*/ Throwable x2) {
                     t$.addSuppressed(x2);
                 }
                 throw t$;
             }
         }
---

Which would then get generated to bytecode as:
---
         {
             final InputStream in = new FileInputStream(str);
             //try
                 int read = in.read();
                 if (read == (-1)) {
             // catch Throwable: goto CATCH
                     in.close();
                     return;
                 }

             //try
                 System.err.println(read);
             // catch Throwable: goto CATCH

             in.close();

             //goto END

             CATCH: // catch (/*synthetic*/ final Throwable t$)
                 try {
                     in.close();
                 } catch (/*synthetic*/ Throwable x2) {
                     t$.addSuppressed(x2);
                 }
                 throw t$;
             END:
         }
---

The current desugaring is trying to pull out the finally code into a 
separate method when that appears to be worthwhile, but this proposed 
desugaring seems to be producing a better bytecode with less javac code.

Does this updated desugaring/transformation look safe?

Webrev: http://cr.openjdk.java.net/~jlahoda/8194978/webrev.00/

Thanks,
      Jan


More information about the compiler-dev mailing list