Skip to content

IncompatibleClassChangeError Wrongly Thrown #965

@JoeyLYZ666

Description

@JoeyLYZ666

Description
我们发现同一个测试用例在不同的OpenJDK Verison上的结果不同,具体而言JDK8正常运行,JDK11、17、21抛出IncompatibleClassChangeError。

Steps to Reproduce
复现:

>>> path_to_jdk8/bin/java -cp . TestLauncher
>>> path_to_jdk11/bin/java -cp . TestLauncher
>>> path_to_jdk17/bin/java -cp . TestLauncher
>>> path_to_jdk21/bin/java -cp . TestLauncher

实际结果:
jdk8:

TestClass1 correctly overrides conflicting methods.
TestClass2 does not override conflicting methods.
InvalidTestClass should throw an error for conflicting methods.

jdk11:

Exception in thread "main" java.lang.IncompatibleClassChangeError: Method 'void TestClass1.run()' must be InterfaceMethodref constant
        at TestLauncher.main(Unknown Source)

jdk17:

Exception in thread "main" java.lang.IncompatibleClassChangeError: Method 'void TestClass1.run()' must be InterfaceMethodref constant
        at TestLauncher.main(Unknown Source)

jdk21:

Exception in thread "main" java.lang.IncompatibleClassChangeError: Method 'void TestClass1.run()' must be InterfaceMethodref constant
        at TestLauncher.main(Unknown Source)

Expected behavior
根据上述的执行结果,我们首先使用javap将TestLauncher反汇编,得到结果:

Classfile /xxx/TestLauncher.class
  Last modified 2025年7月24日; size 536 bytes
  MD5 checksum e4b325e8eeac8e5df91e606cc434ddc6
public class TestLauncher
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // TestLauncher
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 1, attributes: 0
Constant pool:
   #1 = Utf8               TestLauncher
   #2 = Class              #1             // TestLauncher
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               main
   #6 = Utf8               ([Ljava/lang/String;)V
   #7 = Utf8               TestClass1
   #8 = Class              #7             // TestClass1
   #9 = Utf8               run
  #10 = Utf8               ()V
  #11 = NameAndType        #9:#10         // run:()V
  #12 = Methodref          #8.#11         // TestClass1.run:()V
  #13 = Utf8               TestClass2
  #14 = Class              #13            // TestClass2
  #15 = Methodref          #14.#11        // TestClass2.run:()V
  #16 = Utf8               InvalidTestClass
  #17 = Class              #16            // InvalidTestClass
  #18 = Methodref          #17.#11        // InvalidTestClass.run:()V
  #19 = Utf8               java/lang/System
  #20 = Class              #19            // java/lang/System
  #21 = Utf8               out
  #22 = Utf8               Ljava/io/PrintStream;
  #23 = NameAndType        #21:#22        // out:Ljava/io/PrintStream;
  #24 = Fieldref           #20.#23        // java/lang/System.out:Ljava/io/PrintStream;
  #25 = Utf8               InvalidTestClass load fail:
  #26 = String             #25            // InvalidTestClass load fail:
  #27 = Utf8               java/io/PrintStream
  #28 = Class              #27            // java/io/PrintStream
  #29 = Utf8               print
  #30 = Utf8               (Ljava/lang/String;)V
  #31 = NameAndType        #29:#30        // print:(Ljava/lang/String;)V
  #32 = Methodref          #28.#31        // java/io/PrintStream.print:(Ljava/lang/String;)V
  #33 = Utf8               java/lang/Throwable
  #34 = Class              #33            // java/lang/Throwable
  #35 = Utf8               toString
  #36 = Utf8               ()Ljava/lang/String;
  #37 = NameAndType        #35:#36        // toString:()Ljava/lang/String;
  #38 = Methodref          #34.#37        // java/lang/Throwable.toString:()Ljava/lang/String;
  #39 = Utf8               println
  #40 = NameAndType        #39:#30        // println:(Ljava/lang/String;)V
  #41 = Methodref          #28.#40        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #42 = Utf8               Code
  #43 = Utf8               StackMapTable
{
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokestatic  #12                 // Method TestClass1.run:()V
         3: invokestatic  #15                 // Method TestClass2.run:()V
         6: invokestatic  #18                 // Method InvalidTestClass.run:()V
         9: goto          31
        12: astore_1
        13: getstatic     #24                 // Field java/lang/System.out:Ljava/io/PrintStream;
        16: ldc           #26                 // String InvalidTestClass load fail:
        18: invokevirtual #32                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        21: getstatic     #24                 // Field java/lang/System.out:Ljava/io/PrintStream;
        24: aload_1
        25: invokevirtual #38                 // Method java/lang/Throwable.toString:()Ljava/lang/String;
        28: invokevirtual #41                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        31: return
      Exception table:
         from    to  target type
             6     9    12   Class java/lang/Throwable
      StackMapTable: number_of_entries = 2
        frame_type = 76 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 18 /* same */
}

我们观察到,在 TestLauncher.main 中,指令 TestClass1.run(); 使用的是 invokestatic,而解析的目标 TestClass1 是一个接口。根据 JVM SE8 §6.5 invokestatic 的规定:“指定的方法将被解析(§5.4.3.3)。”而 第 5.4.3.3 章 明确指出:“1. 如果 C 是一个接口,方法解析将抛出 IncompatibleClassChangeError。”因此,在 jdk8 中,应抛出 IncompatibleClassChangeError,而不是正常执行该方法。

同样地,根据 JVM SE11 §6.5 invokestatic 的规定:“指定的方法将被解析(§5.4.3.3§5.4.3.4),”该版本允许在接口上解析方法,因此在 jdk11 中,测试用例应正常执行,而不是抛出 IncompatibleClassChangeError。同理V17,V21也应正常运行。

JDK version

openjdk version "1.8.0_462"
OpenJDK Runtime Environment (Alibaba Dragonwell Standard Edition 8.26.25) (build 1.8.0_462-b01)
OpenJDK 64-Bit Server VM (Alibaba Dragonwell Standard Edition 8.26.25) (build 25.462-b01, mixed mode)

openjdk version "11.0.28.24" 2025-07-15
OpenJDK Runtime Environment (Alibaba Dragonwell Standard Edition)-11.0.28.24+6-GA (build 11.0.28.24+6)
OpenJDK 64-Bit Server VM (Alibaba Dragonwell Standard Edition)-11.0.28.24+6-GA (build 11.0.28.24+6, mixed mode)

openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment (Alibaba Dragonwell Standard Edition)-17.0.16.0.17+8-GA (build 17.0.16+8)
OpenJDK 64-Bit Server VM (Alibaba Dragonwell Standard Edition)-17.0.16.0.17+8-GA (build 17.0.16+8, mixed mode, sharing)

openjdk version "21.0.8.0.8" 2025-07-15
OpenJDK Runtime Environment (Alibaba Dragonwell Standard Edition)-21.0.8.0.8+9-GA (build 21.0.8.0.8)
OpenJDK 64-Bit Server VM (Alibaba Dragonwell Standard Edition)-21.0.8.0.8+9-GA (build 21.0.8.0.8, mixed mode, sharing)

Execution environment

OS:
	Operating System Name: Linux
	Operating System Architecture: amd64
	Operating System Version: 4.15.0-45-generic

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions