RFR: JDK-8313626: C2 crash due to unexpected exception control flow

Tobias Holenstein tholenstein at openjdk.org
Fri Aug 18 13:40:06 UTC 2023


# Problem 
The following JASM code:

static Method test1:"()V" stack 1 {
    try t;
        invokestatic m:"()V";
        return;

        catch t java/lang/Throwable;
        stack_map class java/lang/Throwable;
        athrow;
    endtry t;
}

produces this java bytecode

static void m();
  Code:
     0: return

static void test1();
  Code:
     0: invokestatic  #4      // Method m:()V
     3: return
     4: athrow
  Exception table:
     from    to  target type
         0     5     4   Class java/lang/Throwable


from https://docs.oracle.com/javase/specs/jvms/se20/jvms20.pdf _exception_table[] (p.116)_

> The values of the two items start_pc and end_pc indicate the ranges in the code array at which the exception handler is active. The value of start_pc must be a valid index into the code array of the opcode of an instruction. The value of end_pc either must be a valid index into the code array of the opcode of an instruction or must be equal to code_length, the length of the code array. The value of start_pc must be less than the value of end_pc.
> The start_pc is inclusive and end_pc is exclusive; that is, the exception handler must be active while the program counter is within the interval [start_pc, end_pc).
> 
> handler_pc
> The value of the handler_pc item indicates the start of the exception handler. The value of the item must be a valid index into the code array and must be the index of the opcode of an instruction.

and from _§athrow (p.420)_

> 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. It is popped from the operand stack. The objectref is then thrown by searching the current method (§2.6) for the first exception handler that matches the class of objectref, as given by the algorithm in §2.10.
> If an exception handler that matches objectref is found, it contains the location of the code intended to handle this exception. The pc register is reset to that location, the operand stack of the current frame is cleared, objectref is pushed back onto the operand stack, and execution continues.

In out case: **[start_pc=0, end_pc=5)** and **handler_pc=4** and **objectref=Class java/lang/Throwable**

By this definition we have indeed valid bytecode for `test1()`.  Therefore we would expect C2 to create an infinite loop for

     4: athrow


The C2 graph indeed shows an infinite loop 92/81:
<img width="576" alt="graph1" src="https://github.com/openjdk/jdk/assets/71546117/9e3ba501-81b0-41f5-ad94-74400d346bfa">

During IGVN the graph degenerates: 
1)
<img width="134" alt="graph2" src="https://github.com/openjdk/jdk/assets/71546117/61295251-f83f-4191-8ed1-443d5105b08f">
2)
<img width="152" alt="graph3" src="https://github.com/openjdk/jdk/assets/71546117/b0c8dde5-8441-4921-acc3-998121af4148">
3)
<img width="345" alt="graph4" src="https://github.com/openjdk/jdk/assets/71546117/6a8a5350-c51c-4a3f-acac-7bbc8dacf364">
And in the end we get an ` assert(false) failed: malformed control flow` 

# Solution
We usually have a safepoint in infinite loops. The edge case that an exception can cause an infinite loop was not handled. With normal Java it is not possible to create such in infinite loop with try-catch, but with Jasm/bytecode it is allowed. Fix: By adding a safepoint to the backedge
<img width="489" alt="safepoint1" src="https://github.com/openjdk/jdk/assets/71546117/eab07581-9935-418c-9bbe-283d90ff73a5">
we prevent the infinite loop from being removed during IGVN
<img width="298" alt="safepoint2" src="https://github.com/openjdk/jdk/assets/71546117/237ed34d-793c-4f88-b036-09ed1d8ae90e">

### Testing
We also found some other test cases that are very similar;
`test2` is similar to `test1`. The endless loop is from/to `5: athrow`

static void test2();
  Code:
     0: invokestatic  #6                  // Method m:()V
     3: return
     4: return
     5: athrow
  Exception table:
     from    to  target type
         0     3     4   Class java/lang/Exception
         0     6     5   Class java/lang/Throwable


- in `test3` and `test4` ` th()` gets inlined `athrow` has then a backedge to `new` that creates an infinite loop and is missing a safepoint

public static void th() throws java.lang.Exception;
  Code:
     0: new           #9                  // class java/lang/Throwable
     3: dup
     4: invokespecial #3                  // Method java/lang/Throwable."<init>":()V
     7: athrow

static void test3();
  Code:
     0: invokestatic  #6                  // Method m:()V
     3: iconst_1
     4: istore_0
     5: iconst_0
     6: istore_1
     7: return
     8: invokestatic  #4                  // Method th:()V
    11: return
  Exception table:
     from    to  target type
         0    12     8   Class java/lang/Throwable

static void test4();
  Code:
     0: invokestatic  #6                  // Method m:()V
     3: iconst_1
     4: istore_0
     5: iconst_0
     6: istore_1
     7: return
     8: iconst_1
     9: istore_0
    10: invokestatic  #4                  // Method th:()V
    13: return
  Exception table:
     from    to  target type
         0    14     8   Class java/lang/Throwable

-------------

Commit messages:
 - Update TestMissingSafepointOnTryCatch
 - JDK-8313626: C2 crash due to unexpected exception control flow

Changes: https://git.openjdk.org/jdk/pull/15292/files
 Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=15292&range=00
  Issue: https://bugs.openjdk.org/browse/JDK-8313626
  Stats: 186 lines in 3 files changed: 186 ins; 0 del; 0 mod
  Patch: https://git.openjdk.org/jdk/pull/15292.diff
  Fetch: git fetch https://git.openjdk.org/jdk.git pull/15292/head:pull/15292

PR: https://git.openjdk.org/jdk/pull/15292


More information about the hotspot-compiler-dev mailing list