1 Answer
1

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.

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 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.

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:

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:

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

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 javacindeed never uses swap even where appropriate.

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.

Great analysis. I came to more or less the same conclusions while trying to answer this last week. It seems likely that it's a ~bug~. In any case, +1 and all. Thanks!
– Sotirios DelimanolisSep 11 '14 at 14:37

This explains why Emma code coverage in eclipse is significantly higher than maven/Sonar coverage when you have a lot of try-with-resources blocks.
– anydobyMay 8 at 3:01