Try with Resources Introduce Unreachable Bytecode

try with resources introduce unreachable bytecode

TL;DR: this has been addressed with JDK-11; at the end of the answer is an example of JDK-11’s javac output for comparison.

The fact that every throwable is an instance of java.lang.Throwable is implied at various places of the Java byte code/ JVM. Even if handlers for any were meant to represent something possibly outside the Throwable type hierarchy, that idea fails as today’s class files must have a StackMapTable for methods containing exception handlers and that StackMapTable will refer to the any throwable as an instance of java.lang.Throwable1.

Even with the old type inferring verifier, a handler which re-throws a throwable implicitly contains the assertion that any throwable is an instance of java.lang.Throwable as that’s the only object athrow is allowed to throw.

http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.athrow

The objectref must be of type reference and must refer to an object that is an instance of class Throwable or of a subclass of Throwable.

Short answer: no, it is impossible to have a situation where something other than an instance of java.lang.Throwable (or a subclass) can be thrown or caught.

I tried to create a minimal example of a try-with-resource statement to analyse the output of javac. The result clearly shows that the structure is an artifact of how javac works internally but can’t be intentional.

The example looks like this:

public static void tryWithAuto() throws Exception {
try (AutoCloseable c=dummy()) {
bar();
}
}
private static AutoCloseable dummy() {
return null;
}
private static void bar() {
}

(I compiled with jdk1.8.0_20)

I put the exception handler table at the beginning of the resulting byte code so it’s easier to refer to the location while looking at the instruction sequence:

Exception table:
from to target type
17 23 26 Class java/lang/Throwable
6 9 44 Class java/lang/Throwable
6 9 49 any
58 64 67 Class java/lang/Throwable
44 50 49 any

Now to the instructions:

The beginning is straightforward, two local variables are used, one to hold the AutoCloseable (index 0), the other for the possible throwable (index 1, initialized with null). dummy() and bar() are invoked, then the AutoCloseable is checked for null to see whether it must be closed.

     0: invokestatic  #2         // Method dummy:()Ljava/lang/AutoCloseable;
3: astore_0
4: aconst_null
5: astore_1
6: invokestatic #3 // Method bar:()V
9: aload_0
10: ifnull 86

We get here if the AutoCloseable is not null and the first weird thing happens, the throwable which is definitely null is checked for null

    13: aload_1
14: ifnull 35

The following code will close the AutoCloseable, guarded by the first exception handler from the table above which will invoke addSuppressed. Since at this point, variable #1 is null this is dead-code:

    17: aload_0
18: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
23: goto 86
26: astore_2
27: aload_1
28: aload_2
29: invokevirtual #6 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
32: goto 86

Note that the last instruction of the dead code is goto 86, a branch to a return so if the code above was not dead code anyway, we could start wondering why bother invoking addSuppressed on a Throwable that is ignored right afterwards.

Now follows the code that is executed if variable #1 is null (read, always). It simply invokes close and branches to the return instruction, not catching any exception, so an exception thrown by close() propagates to the caller:

    35: aload_0
36: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
41: goto 86

Now we enter the second exception handler, covering the body of the try statement, declared to catch Throwable, read all exceptions. It stores the Throwable into the variable #1, as expected, but also stores it into the obsolete variable #2. Then it re-throws the Throwable.

    44: astore_2
45: aload_2
46: astore_1
47: aload_2
48: athrow

The following code is the target of two exception handlers. First, its the target of the superfluous any exception handler that covers the same range as the Throwable handler, hence, as you suspected, this handler doesn’t do anything. Further, it is the target of the fourth exception handler, catching anything and covering the exception handler above so we catch the re-thrown exception of instruction #48 right one instruction later. To make things even more funny, the exception handler covers more than the handler above; ending at #50, exclusive, it even covers the first instruction of itself:

    49: astore_3

So the first thing is to introduce a third variable to hold the same throwable. Now the the AutoCloseable is checked for null.

    50: aload_0
51: ifnull 84

Now the throwable of variable #1 is checked for null. It can be null only if the hypothetical throwable not being a Throwable exists. But note that the entire code would be rejected by the verifier in that case as the StackMapTable declares all variables and operand stack entries holding the any throwable to be assignment compatible to java.lang.Throwable

    54: aload_1
55: ifnull 78
58: aload_0
59: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
64: goto 84

Last but not least we have the exception handler which handles exception thrown by close when a pending exception exists which will invoke addSuppressed and re-throws the primary exception. It introduces another local variables which indicates that javac indeed never uses swap even where appropriate.

    67: astore        4
69: aload_1
70: aload 4
72: invokevirtual #6 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
75: goto 84

So the following two instructions are only invoked if catch any could imply something other than java.lang.Throwable which is not the case. The code path joins at #84 with the regular case.

    78: aload_0
79: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
84: aload_3
85: athrow

86: return

So the bottom line is that the additional exception handler for any is responsible for dead code of four instructions only, #54, #55, #78 and #79 while there is even more dead code for other reasons (#17 - #32), plus a strange “throw-and-catch” (#44 - #48) code which might also be an artifact of the idea to handle any differently than Throwable. Further, one exception handler has a wrong range covering itself which might be related to “Strange exception table entry produced by Sun's javac” as suggested in the comments.


As a side-note, Eclipse produces more straightforward code taking only 60 bytes rather than 87 for the instruction sequence, having the two expected exception handlers only and three local variables instead of five. And within that more compact code it handles the possible case that the exception thrown by the body might be the same as the one throw by close in which case addSuppressed must not be called. The javac generated code does not care for this.

     0: aconst_null
1: astore_0
2: aconst_null
3: astore_1
4: invokestatic #18 // Method dummy:()Ljava/lang/AutoCloseable;
7: astore_2
8: invokestatic #22 // Method bar:()V
11: aload_2
12: ifnull 59
15: aload_2
16: invokeinterface #25, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
21: goto 59
24: astore_0
25: aload_2
26: ifnull 35
29: aload_2
30: invokeinterface #25, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
35: aload_0
36: athrow
37: astore_1
38: aload_0
39: ifnonnull 47
42: aload_1
43: astore_0
44: goto 57
47: aload_0
48: aload_1
49: if_acmpeq 57
52: aload_0
53: aload_1
54: invokevirtual #30 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
57: aload_0
58: athrow
59: return
  Exception table:
from to target type
8 11 24 any
4 37 37 any

Starting with JDK-11, javac compiles the example to

Code:
0: invokestatic #2 // Method dummy:()Ljava/lang/AutoCloseable;
3: astore_0
4: invokestatic #3 // Method bar:()V
7: aload_0
8: ifnull 42
11: aload_0
12: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
17: goto 42
20: astore_1
21: aload_0
22: ifnull 40
25: aload_0
26: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
31: goto 40
34: astore_2
35: aload_1
36: aload_2
37: invokevirtual #6 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
40: aload_1
41: athrow
42: return
Exception table:
from to target type
4 7 20 Class java/lang/Throwable
25 31 34 Class java/lang/Throwable

It now has even less redundancy than the ECJ compiled version. It still doesn’t check whether the throwables are the same, but I’d rather add another exception handler entry covering the addSuppressed invocation instruction and targeting the re-throwing code at 40, rather than inserting a pre-check for this corner case. Then, it would still be less code than the alternatives.

JaCoCo branch coverage try with resources

Per the change history in version 0.8.2:

Branches and instructions generated by javac 11 for try-with-resources statement are filtered out

I've tested this out locally using openjdk java8, and my try-with-resources now reports 100% branch coverage (even though the IOException is never thrown in my tests).

While it is good to test this behavior out, there are times when you can't easily reproduce such exceptions. For instance, in a method that just returns an open port:

public int getOpenPort() {
try (ServerSocket boundSocket = new ServerSocket(0)) {
return boundSocket.getLocalPort();
}
}

I know of no simple way to force this code to throw IOException without adding a bunch of confusing and unnecessarily complicated code, just to pass a branch coverage check. Luckily, the new (v0.8.2) jacoco library gives this method 100% coverage with a single test just calling Assert.assertNotEquals(0, portChecker.getOpenPort());.

Does Java's try-with-resources catch errors or just exceptions?

It does not catch anything. But it does finally close all resources.

finally blocks are run even when an Error is thrown.

8 branches for try with resources - jacoco coverage possible?

Well I can't tell you what the exact problem with Jacoco is, but I can show you how Try With Resources is compiled. Basically, there are a lot of compiler generated switches to handle exceptions thrown at various points.

If we take the following code and compile it

public static void main(String[] args){
String a = "before";

try (CharArrayWriter br = new CharArrayWriter()) {
br.writeTo(null);
} catch (IOException e){
System.out.println(e.getMessage());
}

String a2 = "after";
}

And then disassemble, we get

.method static public main : ([Ljava/lang/String;)V
.limit stack 2
.limit locals 7
.catch java/lang/Throwable from L26 to L30 using L33
.catch java/lang/Throwable from L13 to L18 using L51
.catch [0] from L13 to L18 using L59
.catch java/lang/Throwable from L69 to L73 using L76
.catch [0] from L51 to L61 using L59
.catch java/io/IOException from L3 to L94 using L97
ldc 'before'
astore_1
L3:
new java/io/CharArrayWriter
dup
invokespecial java/io/CharArrayWriter <init> ()V
astore_2
aconst_null
astore_3
L13:
aload_2
aconst_null
invokevirtual java/io/CharArrayWriter writeTo (Ljava/io/Writer;)V
L18:
aload_2
ifnull L94
aload_3
ifnull L44
L26:
aload_2
invokevirtual java/io/CharArrayWriter close ()V
L30:
goto L94
L33:
.stack full
locals Object [Ljava/lang/String; Object java/lang/String Object java/io/CharArrayWriter Object java/lang/Throwable
stack Object java/lang/Throwable
.end stack
astore 4
aload_3
aload 4
invokevirtual java/lang/Throwable addSuppressed (Ljava/lang/Throwable;)V
goto L94
L44:
.stack same
aload_2
invokevirtual java/io/CharArrayWriter close ()V
goto L94
L51:
.stack same_locals_1_stack_item
stack Object java/lang/Throwable
.end stack
astore 4
aload 4
astore_3
aload 4
athrow
L59:
.stack same_locals_1_stack_item
stack Object java/lang/Throwable
.end stack
astore 5
L61:
aload_2
ifnull L91
aload_3
ifnull L87
L69:
aload_2
invokevirtual java/io/CharArrayWriter close ()V
L73:
goto L91
L76:
.stack full
locals Object [Ljava/lang/String; Object java/lang/String Object java/io/CharArrayWriter Object java/lang/Throwable Top Object java/lang/Throwable
stack Object java/lang/Throwable
.end stack
astore 6
aload_3
aload 6
invokevirtual java/lang/Throwable addSuppressed (Ljava/lang/Throwable;)V
goto L91
L87:
.stack same
aload_2
invokevirtual java/io/CharArrayWriter close ()V
L91:
.stack same
aload 5
athrow
L94:
.stack full
locals Object [Ljava/lang/String; Object java/lang/String
stack
.end stack
goto L108
L97:
.stack same_locals_1_stack_item
stack Object java/io/IOException
.end stack
astore_2
getstatic java/lang/System out Ljava/io/PrintStream;
aload_2
invokevirtual java/io/IOException getMessage ()Ljava/lang/String;
invokevirtual java/io/PrintStream println (Ljava/lang/String;)V
L108:
.stack same
ldc 'after'
astore_2
return
.end method

For those who don't speak bytecode, this is roughly equivalent to the following pseudo Java. I had to use gotos because the bytecode doesn't really correspond to Java control flow.

As you can see, there are a lot of cases to handle the various possibilities of suppressed exceptions. It's not reasonable to be able to cover all these cases. In fact, the goto L59 branch on the first try block is impossible to reach, since the first catch Throwable will catch all exceptions.

try{
CharArrayWriter br = new CharArrayWriter();
Throwable x = null;

try{
br.writeTo(null);
} catch (Throwable t) {goto L51;}
catch (Throwable t) {goto L59;}

if (br != null) {
if (x != null) {
try{
br.close();
} catch (Throwable t) {
x.addSuppressed(t);
}
} else {br.close();}
}
break;

try{
L51:
x = t;
throw t;

L59:
Throwable t2 = t;
} catch (Throwable t) {goto L59;}

if (br != null) {
if (x != null) {
try{
br.close();
} catch (Throwable t){
x.addSuppressed(t);
}
} else {br.close();}
}
throw t2;
} catch (IOException e) {
System.out.println(e)
}

Java 8 lambdas don't like extra parenthesis?

I suppose, this is related to the horrible way, javac compiles try(…) constructs, but that’s only a guess. At least, I can confirm that this behavior is reproducible with the following MCVE that doesn’t use any 3rd party code nor byte code transformation tools:

import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Tmp {
static <T> T readValue(Supplier<T> s, Class<T> type) throws Exception {
return s.get();
}
interface Source extends Supplier<String>, AutoCloseable {
public default void close() throws Exception {}
}

public static void main(String[] args) {
Stream.of("one", "two", "three")
.map(s -> {
try(Source source = () -> s) {
return (readValue(source, String.class));
}
catch(Exception ex) {
return null;
}
})
.forEach(System.out::println);
}
}

Testable on Ideone and removing the braces does indeed change the result


As far as I can see, this affects all versions of Java 8 and earlier versions of Java 9 (works with b66 and newer).

Strange exception table entry produced by Sun's javac

There are only two possible explanations: the compiler contains a bug or it's placing a kind of watermark for obscure reasons.

That entry is certainly bogus because any exception thrown by a finally block itself must send execution flow to outer exception handler or finally block, but never "run again" the same finally block.

Also, a good evidence that it's a bug/watermark, is the fact that Eclipse (and perhaps other Java compilers) are not generating such entry, and even so Eclipse-generated classes work fine on Sun's JVM.

That said, this post is interesting because it seems that the class file is valid and verified. If I were a JVM implementor, I would ignore that entry and fill a bug for Sun/Oracle!

Try with multiple resource causes a sonar qube issue

It's because getClassLoader().getResourceAsStream("hello.txt") can return null and you're using it just after to create the BufferedReader, without checking for null value.

Understanding local var position in JVM bytecode on finally

The stack frame describes the state of local variables and the operand stack at the point where it appears. Later instructions can of course modify things like normal. As you correctly identified, the stack frame at L6 says that there are two local variables when control flow reaches L6. The following instruction stores to slot 4, which is perfectly legal.

It may help to understand the purpose of the stack map. Originally, there was no stack map at all and the verifier used inference to calculate the local variables at every point in the method. When encountering control flow, it would merge in the values at that point and iterate until convergence.

Unfortunately, this was slow, so in an attempt to speed things up, Oracle added stack maps. This essentially precomputes the verification results at any point where control flow is joined. That way, the verifier can do a single linear pass through the code, because control flow doesn't change results. When the verifier encounters control flow, it checks whether the current state matches the stack frame declared at the jump target, and if not, throws an error. In sections of linear code, there is obviously no need to include stack frames, since the verifier can just do the same thing it did before.

Stack frames are not meant for debugging, they're meant to speed up verification, so they include the minimum information necessary for verification. If the compiler were to hypothetically insert a stack frame at every instruction, then the stack frame after the astore 4 would of course show a new variable in the 4th slot.

As for why it used slot 4 when it could have used slot 3, that's just a whim of the compiler. Perhaps it simplified the implementation of javac, but that's just speculation.



Related Topics



Leave a reply



Submit