Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

try with resources introduce unreachable bytecode

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?

like image 347
Martin Schäf Avatar asked Sep 02 '14 03:09

Martin Schäf


People also ask

How to handle exception in try-with-resources?

If an exception is thrown from within a Java try-with-resources block, any resource opened inside the parentheses of the try block will still get closed automatically. The throwing of the exception will force the execution to leave the try block, and this will force the automatic closing of the resource.

What is try() in java?

The try statement allows you to define a block of code to be tested for errors while it is being executed. The catch statement allows you to define a block of code to be executed, if an error occurs in the try block.

What is use of try-with-resources in java?

The try -with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try -with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.

How try-with-resources works internally?

In the try-with-resources method, there is no use of the finally block. The file resource is opened in try block inside small brackets. Only the objects of those classes can be opened within the block which implements the AutoCloseable interface, and those objects should also be local.


1 Answers

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.

like image 199
Holger Avatar answered Oct 23 '22 11:10

Holger