Is it possible that javac generates unreachable bytecode for the following procedure?
public void ex06(String name) throws Exception {
File config = new File(name);
try (FileOutputStream fos = new FileOutputStream(config);
PrintWriter writer = new PrintWriter(new OutputStreamWriter(
fos , "rw"))) {
bar();
}
}
When I look into the exception table of the bytecode (javap -v) there are the following entries that look strange:
43 48 86 Class java/lang/Throwable
43 48 95 any
and
21 135 170 Class java/lang/Throwable
21 135 179 any
Now the problem is that some code is only reachable if the exceptions of type "any" rather than Throwable are caught. Is there any situation where this can actually happen?
====== EDIT ====== Thanks for the answers so far. Let me give another piece of evidence to show that I really don't understand exception handling: Consider the following procedure
Object constraintsLock;
private String[] constraints;
private String constraint;
public void fp01() {
// Add this constraint to the set for our web application
synchronized (constraintsLock) {
String results[] =
new String[constraints.length + 1];
for (int i = 0; i < constraints.length; i++)
results[i] = constraints[i];
results[constraints.length] = constraint;
constraints = results;
}
}
If you look in the bytecode you have:
65: astore 4
67: aload_1
68: monitorexit
69: aload 4
and the exception table
Exception table:
from to target type
7 62 65 any
65 69 65 any
Does that mean that this guy can loop forever?
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 theThrowable
type hierarchy, that idea fails as today’s class files must have aStackMapTable
for methods containing exception handlers and thatStackMapTable
will refer to the any throwable as an instance ofjava.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 objectathrow
is allowed to throw.http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.athrow
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 howjavac
works internally but can’t be intentional.The example looks like this:
(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:
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 withnull
).dummy()
andbar()
are invoked, then theAutoCloseable
is checked fornull
to see whether it must be closed.We get here if the
AutoCloseable
is notnull
and the first weird thing happens, the throwable which is definitelynull
is checked fornull
The following code will close the
AutoCloseable
, guarded by the first exception handler from the table above which will invokeaddSuppressed
. Since at this point, variable #1 isnull
this is dead-code:Note that the last instruction of the dead code is
goto 86
, a branch to areturn
so if the code above was not dead code anyway, we could start wondering why bother invokingaddSuppressed
on aThrowable
that is ignored right afterwards.Now follows the code that is executed if variable #1 is
null
(read, always). It simply invokesclose
and branches to thereturn
instruction, not catching any exception, so an exception thrown byclose()
propagates to the caller:Now we enter the second exception handler, covering the body of the
try
statement, declared to catchThrowable
, read all exceptions. It stores theThrowable
into the variable #1, as expected, but also stores it into the obsolete variable #2. Then it re-throws theThrowable
.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:So the first thing is to introduce a third variable to hold the same throwable. Now the the
AutoCloseable
is checked fornull
.Now the throwable of variable #1 is checked for
null
. It can benull
only if the hypothetical throwable not being aThrowable
exists. But note that the entire code would be rejected by the verifier in that case as theStackMapTable
declares all variables and operand stack entries holding the any throwable to be assignment compatible tojava.lang.Throwable
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 thatjavac
indeed never usesswap
even where appropriate.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.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 caseaddSuppressed
must not be called. Thejavac
generated code does not care for this.