RFR JDK-8194978: Javac produces dead code for try-with-resource
Jan Lahoda
jan.lahoda at oracle.com
Wed Mar 21 09:12:23 UTC 2018
Hi Maurizio,
Thanks a lot for the comments. I've updated the patch according to the
comments, updated webrev is here:
http://cr.openjdk.java.net/~jlahoda/8194978/webrev.01/
How does that look?
Thanks,
Jan
On 20.3.2018 13:13, Maurizio Cimadamore wrote:
> Hi Jan,
> great work. The desugaring looks correct, at least there's nothing wrong
> I can spot about it. Some comments:
>
> * resourceNonNull = TreeInfo.skipParens(resource).hasTag(NEWCLASS);
>
> Is the above needed? I can't seem to support evidence that this syntax
> is even legal (in the branch where 'resource' is an expression, I mean)
>
> * The javadoc in Lower::makeTwrTry seems out of sync what is being
> actually generated
>
> * I think the Lower code would benefit from better variable names - the
> patch has a mixture of old names (e.g. primaryException) and new names
> (e.g. 'closeStatement2'). I suggest picking a naming scheme in the
> javadoc, and then stick to that in the code, so that there are explicit
> references to what bit it is that we're currently generating.
>
> * I find the code in Gen bit subtle; you want the new finally block to
> act as a finally - but only during the try block; that means, I believe,
> that you want genFinalizer to do the usual thing (e.g. copy finalizer
> body on exit points) but then skip doing that when it comes to
> exceptions. That is, you don't want a catch-all block and you don't want
> to copy finalizer body on exceptional paths. Another stacking would be
> to completely replace the 'env.info.finalize' after the body is done, e.g.
>
> <visit try body>
>
> if (tree.finalizer.flags & BODY_ONLY_FINALIZE != 0) {
> env.info.finalize == null;
> }
>
> Wouldn't this be more consistent with what you are trying to achieve?
> Seems to me that with the current approach there are side effects that
> occur in the GenFinalizer even when afterBody is set (e.g. setting
> finalizer gaps in the encl environment).
>
> Cheers
> Maurizio
>
> On 15/03/18 15:40, Jan Lahoda wrote:
>> 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