Is it possible that javac generates unreachable bytecode for the following procedure?
public void ex06(String name) throws Exception {
File config = new
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.Throwable
1.
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 classThrowable
or of a subclass ofThrowable
.
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.