56

Given this program:

class Test {
    public static void main(String[] args) {
        try {
            throw new NullPointerException();
        } catch (NullPointerException npe) {
            System.out.println("In catch");
        } finally {
            System.out.println("In finally");
        }
    }
}

Sun's javac (v 1.6.0_24) produces the following bytecode:

public static void main(java.lang.String[]);

        // Instantiate / throw NPE
   0:   new     #2;         // class NullPointerException
   3:   dup
   4:   invokespecial   #3; // Method NullPointerException."<init>":()V
   7:   athrow

        // Start of catch clause
   8:   astore_1
   9:   getstatic       #4; // Field System.out
   12:  ldc     #5;         // "In catch"
   14:  invokevirtual   #6; // Method PrintStream.println
   17:  getstatic       #4; // Field System.out

        // Inlined finally block
   20:  ldc     #7;         // String In finally
   22:  invokevirtual   #6; // Method PrintStream.println
   25:  goto    39

        // Finally block
        // store "incomming" exception(?)
   28:  astore_2
   29:  getstatic       #4; // Field System.out
   32:  ldc     #7;         // "In finally"
   34:  invokevirtual   #6; // Method PrintStream.println

        // rethrow "incomming" exception
   37:  aload_2
   38:  athrow

   39:  return

With the following exception table:

  Exception table:
   from   to  target type
     0     8     8   Class NullPointerException
     0    17    28   any
    28    29    28   any


My question is: Why on earth does it include that last entry in the exception table?!

As I understand it, it basically says "if the astore_2 throws an exception, catch it, and retry the same instruction".

Such entry is produced even with empty try / catch / finally clauses such as

try {} catch (NullPointerException npe) {} finally {}

Some observations

  • Eclipse compiler does not produce any such exception table entry
  • The JVM spec does not document any runtime exceptions for the astore instruction.
  • I know that it is legal for the JVM to throw VirtualMachineError for any instruction. I guess the peculiar entry prevents any such errors from propagating out from that instruction.
aioobe
  • 413,195
  • 112
  • 811
  • 826
  • 7
    I'll post this as a comment, since I've not managed to to wrap my head around this concept. There is an entry on this topic, as to why the last entry is generated at [a blog](http://cliffhacks.blogspot.com/2008/02/java-6-tryfinally-compilation-without.html). Apparently, the behavior of the compiler for compiling the finally block, as specified in the VM spec is a bit off the mark, as far as the Sun/Oracle compiler is concerned. The last exception table entry is in place to guard the "generated exception handler". I didn't figure out how the guard operates and why it should work in such a way. – Vineet Reynolds Jun 17 '11 at 15:43

3 Answers3

8

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!

fernacolo
  • 7,012
  • 5
  • 40
  • 61
  • 3
    "*any exception thrown by a finally block itself must send execution flow to outer exception handler*" -- No, not if you have an inner catch-block. Besides, you're mixing up Java with bytecode: The bytecode program should implement the semantics of the Java program to be compiled. It can do so by throwing / catching all kinds of crazy exceptions. There is no "right" or "wrong" way of compiling the program. My suspicion is that the entry is a side-effect of some more general construct in the compiler. Perhaps such entry will even be emitted from the eclipse compiler in some rare corner case. – aioobe Jun 20 '11 at 18:45
  • @aioobe While JVM can be a target for other languages instead of Java, this question explicitly shows a Java program being compiled for JVM. The JVM may allow an exception to be handled by the very code that threw it, but astore_2 instruction belongs to a Java finally block that have no inner try, so any exception thrown by it must actually send execution flow to an outer exception handler. "My suspicion is that the entry is a side-effect of some more general construct in the compiler." - I also think so, but nevertheless this breaks Java language contract. – fernacolo Jun 20 '11 at 19:36
  • *this breaks Java language contract* -- no, it doesn't. You could for instance reason like this: Any exception thrown by any statement within the finally clause should be propagated out of the clause. Thus if no statements exists in the finally clause, no exceptions may be propagated out from there. Thus it's perfectly legal to suppress all exceptions in the "compiled finally block" if the block that was compiled contained no statements. – aioobe Jun 20 '11 at 19:47
  • 1
    @aioobe Yes, but the finally block in question does contain statements!!! Another way of explaining why it's a bug: If some JVM throws a VirtualMachineError (or another Error exception) on the astore_2 function, it may enter an infinite loop that consumes 100% of a CPU, even without any loop in the Java code, when compiled with javac. Now if the same class were compiled with Eclipse, the VirtualMachineError will correctly send execution flow to outside. – fernacolo Jun 20 '11 at 19:48
  • "*The finally block in question does not contain statements*" -- Right. That is precisely the reason it is actually ok to supress any exceptions thrown by the bytecode corresponding to the no-statement block. Regarding the VirtualMachineError: Then that exception is due to an error in the JVM, i.e., the JVM broke the contract before the exception was caught in the first place. – aioobe Jun 20 '11 at 19:50
  • @aioobe FYI it may not be an error in the JVM. `OutOfMemoryError` inherits from VirtualMachineError and that is most likely programmer error. –  Sep 20 '11 at 05:31
  • @Bringer128, now that's interesting. I hadn't noticed that. – aioobe Sep 20 '11 at 07:49
8

Looking at the OpenJDK 7 source code, I would venture to guess the reason for that last 28 29 28 any exception table entry is because the code that handles the astore bytecode (see code starting at line 1871) can throw an java.lang.LinkageError exception if the popped value from the operand stack is not a returnAddress or reference type (see the Java Virtual Machine Specification for astore) and they want this error condition to show up on the stack trace.

In the event that there is a bad operand type on the operand stack, the JVM will clear the operand stack (getting rid of that bad operand), put a LinkageError on the operand stack, and execute the astore bytecode again, this time successfully executing the astore bytecode using a JVM provided LinkageError object reference. See the athrow documentation for more information.

I would greatly suspect the root cause of throwing a LinkageError during astore processing is due to the complexities JSR/RET subroutines introduce into bytecode verification (OpenJDK changes 6878713, 6932496 and 7020373 are recent evidence of JSR's continued complexity; I'm sure Sun/Oracle has other closed source tests that we're not seeing in OpenJDK). The OpenJDK 7020373 change uses LinkageError to validate/invalidate test results.

Go Dan
  • 15,194
  • 6
  • 41
  • 65
  • Hmm.. wouldn't such type-error be caught in the bytecode verification step? (Before executing the code.) – aioobe Sep 29 '11 at 19:23
  • Updated my answer with information about JSR/RET subroutine complexity, which makes bytecode verification painful. – Go Dan Sep 30 '11 at 13:10
1

My understanding is that the second exception table entry is the implicit catch everything clause added by the compiler to cover any exceptions/errors thrown in the body or the catch handlers and the third entry is the guard on that implicit catch to force the flow through the finally execution.

philwb
  • 3,805
  • 19
  • 20