JDK-8365179 : 1.8.0_451 Incorrectly Misses ClassFormatError for Invalid clinit
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 8
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows
  • CPU: x86_64
  • Submitted: 2025-07-28
  • Updated: 2025-08-11
Description
ADDITIONAL SYSTEM INFORMATION :
ADDITIONAL SYSTEM INFORMATION :

```
OS:
	Operating System Name: Windows 11
	Operating System Architecture: amd64
	Operating System Version: 10.0

```
	
OpenJDK version:

```

java version "1.8.0_451"
Java(TM) SE Runtime Environment (build 1.8.0_451-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.451-b10, mixed mode)

java version "11.0.27" 2025-04-15 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.27+8-LTS-232)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.27+8-LTS-232, mixed mode)

```
Output from `java -version`.

A DESCRIPTION OF THE PROBLEM :
Given a test case, we found that the execution results of this test case on different versions are different, the simplified test case can be found below. In summary, 11.0.27 threw 
`ClassFormatError` while 1.8.0_451 threw `VerifyError`.

If the JVM fails to properly validate the bytecode, it could easily lead to the execution of incorrect files, thereby introducing potential risks.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Repoduce:

```

>>> path_to_jdk/hotspot/1.8.0_451/bin/java -cp . TestLauncher 

>>> path_to_jdk/hotspot/11.0.27/bin/java -cp . TestLauncher 

```

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Based on the results above, we consulted the JVM SE8 specification, and in [JVM SE8 Chapter 2.9](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9), we found the following statement: "In a `class` file whose version number is 51.0 or above, the method must additionally have its `ACC_STATIC` flag ([§4.6](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6)) set in order to be the class or interface initialization method."

Subsequently, we disassembled `OuterClass1$InnerClass1` using `javap` and discovered that the class file version is major version 52. Therefore, the test case should throw a `ClassFormatError` rather than running `OuterClass1` successfully.
ACTUAL -
The output is as follows:

hotspot(1.8.0_451):

```

123
Exception in thread "main" java.lang.VerifyError: Operand stack underflow
Exception Details:
Location:
OuterClass2.run()V @11: invokevirtual
Reason:
Attempt to pop empty stack.
Current Frame:
bci: @11
flags: { }
locals: { }
stack: { integer }
Bytecode:
0x0000000: b200 0c12 0eb6 0014 b200 1ab6 001e b800
0x0000010: 21b2 000c 1223 b600 14b2 001a b600 1eb1
0x0000020:

    at TestLauncher.main(Unknown Source)

```

hotspot(11.0.27):

```

Exception in thread "main" java.lang.ClassFormatError: Method <clinit> is not static in class file OuterClass1$InnerClass1
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1021)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:800)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:698)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:621)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:579)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
at OuterClass1.run(Unknown Source)
at TestLauncher.main(Unknown Source)

```

---------- BEGIN SOURCE ----------
The following code can be used to generate the test cases(.class file) that reproduces the above process:

```java
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class BytecodeUtil {
    public static void main(String args[]) {
        // create package
        String baseDir = "./your_dir"; // your base dir
        File baseDirFile = new File(baseDir);
        baseDirFile.mkdirs();

        String OuterClass1_bytecodeInStr = "CAFEBABE00000034001A01000B4F75746572436C617373310700010100106A6176612F6C616E672F4F626A65637407000301000372756E0100032829560100106A6176612F6C616E672F53797374656D0700070100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C0009000A090008000B0100174F75746572436C6173733124496E6E6572436C6173733107000D01000B7374617469634669656C64010001490C000F001009000E00110100136A6176612F696F2F5072696E7453747265616D0700130100077072696E746C6E010004284929560C001500160A00140017010004436F64650021000200040000000000010009000500060001001900000016000200000000000AB2000CB20012B60018B1000000000000" ;
        String OuterClass1$InnerClass1_bytecodeInStr = "CAFEBABE00000034000C0100174F75746572436C6173733124496E6E6572436C617373310700010100106A6176612F6C616E672F4F626A65637407000301000B7374617469634669656C6401000149030000007B0100083C636C696E69743E01000328295601000D436F6E7374616E7456616C7565010004436F6465000900020004000000010009000500060001000A00000002000700010001000800090001000B0000000D0000000100000001B1000000000000" ;
        String OuterClass2_bytecodeInStr = "CAFEBABE00000034002501000B4F75746572436C617373320700010100106A6176612F6C616E672F4F626A65637407000301000372756E0100032829560100106A6176612F6C616E672F53797374656D0700070100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C0009000A090008000B0100154265666F7265206D6F64696669636174696F6E3A2008000D0100136A6176612F696F2F5072696E7453747265616D07000F0100057072696E74010015284C6A6176612F6C616E672F537472696E673B29560C001100120A001000130100174F75746572436C6173733224496E6E6572436C6173733207001501000B7374617469634669656C64010001490C0017001809001600190100077072696E746C6E010004284929560C001B001C0A0010001D0100116D6F646966795374617469634669656C640C001F00060A001600200100144166746572206D6F64696669636174696F6E3A20080022010004436F6465002100020004000000000001000900050006000100240000002C0002000000000020B2000C120EB60014B2001AB6001EB80021B2000C1223B60014B2001AB6001EB1000000000000" ;
        String OuterClass2$InnerClass2_bytecodeInStr = "CAFEBABE00000034000E0100174F75746572436C6173733224496E6E6572436C617373320700010100106A6176612F6C616E672F4F626A65637407000301000B7374617469634669656C640100014903000001C80100116D6F646966795374617469634669656C640100032829560C00050006090002000A01000D436F6E7374616E7456616C7565010004436F6465000900020004000000010009000500060001000C00000002000700010009000800090001000D000000150002000000000009B2000B0860B5000BB1000000000000" ;
        String OuterClass3_bytecodeInStr = "CAFEBABE00000034001601000B4F75746572436C617373330700010100106A6176612F6C616E672F4F626A65637407000301000372756E0100032829560100106A6176612F6C616E672F53797374656D0700070100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C0009000A090008000B01002A56616C696420636F64652C20496E6E6572436C617373332077696C6C206661696C20746F206C6F61642E08000D0100136A6176612F696F2F5072696E7453747265616D07000F0100077072696E746C6E010015284C6A6176612F6C616E672F537472696E673B29560C001100120A00100013010004436F646500210002000400000000000100090005000600010015000000150002000000000009B2000C120EB60014B1000000000000" ;
        String OuterClass3$InnerClass3_bytecodeInStr = "CAFEBABE00000034000E0100174F75746572436C6173733324496E6E6572436C617373330700010100106A6176612F6C616E672F4F626A65637407000301000B7374617469634669656C640100014903000003150100083C636C696E69743E0100032829560C00050006090002000A01000D436F6E7374616E7456616C7565010004436F6465000900020004000000010009000500060001000C00000002000700010008000800090001000D000000150002000000000009B2000B0460B5000BB1000000000000" ;
        String TestLauncher_bytecodeInStr = "CAFEBABE00000034001401000C546573744C61756E636865720700010100106A6176612F6C616E672F4F626A6563740700030100046D61696E010016285B4C6A6176612F6C616E672F537472696E673B295601000B4F75746572436C6173733107000701000372756E0100032829560C0009000A0A0008000B01000B4F75746572436C6173733207000D0A000E000B01000B4F75746572436C617373330700100A0011000B010004436F64650021000200040000000000010009000500060001001300000016000000010000000AB8000CB8000FB80012B1000000000000" ;

        try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "OuterClass1.class")) {
            byte[] OuterClass1_bytecode =  hexStringToByteArray(OuterClass1_bytecodeInStr);
            fos.write(OuterClass1_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "OuterClass1$InnerClass1.class")) {
            byte[] OuterClass1$InnerClass1_bytecode =  hexStringToByteArray(OuterClass1$InnerClass1_bytecodeInStr);
            fos.write(OuterClass1$InnerClass1_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "OuterClass2.class")) {
            byte[] OuterClass2_bytecode =  hexStringToByteArray(OuterClass2_bytecodeInStr);
            fos.write(OuterClass2_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "OuterClass2$InnerClass2.class")) {
            byte[] OuterClass2$InnerClass2_bytecode =  hexStringToByteArray(OuterClass2$InnerClass2_bytecodeInStr);
            fos.write(OuterClass2$InnerClass2_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "OuterClass3.class")) {
            byte[] OuterClass3_bytecode =  hexStringToByteArray(OuterClass3_bytecodeInStr);
            fos.write(OuterClass3_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "TestLauncher.class")) {
            byte[] TestLauncher_bytecode =  hexStringToByteArray(TestLauncher_bytecodeInStr);
            fos.write(TestLauncher_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static byte[] hexStringToByteArray(String hexString) {
        int length = hexString.length();
        byte[] byteArray = new byte[length / 2];
        for (int i = 0; i < length; i += 2) {
            int byteValue = Integer.parseInt(hexString.substring(i, i + 2), 16);
            byteArray[i / 2] = (byte) byteValue;
        }
        return byteArray;
    }
}

```

Note: modify the paths according to your needs.

After obtaining the test cases, you can derive the following test cases through decompilation:

```java
public class OuterClass1 {
    public static void run() {
        System.out.println(OuterClass1$InnerClass1.staticField);
    }
}
```

```java
public static class OuterClass1$InnerClass1 {
    public static int staticField;
}
```

```java
public class OuterClass2 {
    public static void run() {
        // $FF: Couldn't be decompiled
    }
}
```

```java
public static class OuterClass2$InnerClass2 {
    public static int staticField;

    public static void modifyStaticField() {
        // $FF: Couldn't be decompiled
    }
}
```

```java
public class OuterClass3 {
    public static void run() {
        System.out.println("Valid code, InnerClass3 will fail to load.");
    }
}
```

```java
public static class OuterClass3$InnerClass3 {
    public static int staticField;

    static {
        // $FF: Couldn't be decompiled
    }
}
```

```java
public class TestLauncher {
    public static void main(String[] var0) {
        OuterClass1.run();
        OuterClass2.run();
        OuterClass3.run();
    }
}
```
---------- END SOURCE ----------