Skip to content

AbstractMethodError Due to Incorrect Handling of Private Method #964

@JoeyLYZ666

Description

@JoeyLYZ666

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

Steps to Reproduce
首先执行下述的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
        String packageName = "P0";
        File folder = new File(baseDir + "/" + packageName);
        folder.mkdirs();

        String P0_I0_bytecodeInStr = "CAFEBABE00000034000F01000550302F49300700010100106A6176612F6C616E672F4F626A6563740700030100016D01001528294C6A6176612F6C616E672F496E74656765723B0100116A6176612F6C616E672F496E746567657207000703000000000100063C696E69743E010004284929560C000A000B0A0008000C010004436F64650601000200040000000000010001000500060001000E00000016000300010000000ABB0008591209B7000DB0000000000000" ;
        String P0_C1_bytecodeInStr = "CAFEBABE00000034000E01000550302F43310700010100106A6176612F6C616E672F4F626A65637407000301000550302F493007000501000550302F49320700070100063C696E69743E0100032829560C0009000A0A0004000B010004436F64650021000200040002000600080000000100010009000A0001000D0000001100010001000000052AB7000CB1000000000000" ;
        String P0_Helper_bytecodeInStr = "CAFEBABE00000034001E01000950302F48656C7065720700010100106A6176612F6C616E672F4F626A6563740700030100063C696E69743E0100032829560C000500060A00040007010005676574433101000928294C50302F43313B01000550302F433107000B0A000C0007010005676574433301000928294C50302F43333B01000550302F43330700100A00110007010005676574433401000928294C50302F43343B01000550302F43340700150A00160007010005676574433501000928294C50302F43353B01000550302F433507001A0A001B0007010004436F64650021000200040000000000050001000500060001001D0000001100010001000000052AB70008B10000000000090009000A0001001D00000016000200010000000ABB000C59B7000D4B2AB0000000000009000E000F0001001D00000016000200010000000ABB001159B700124B2AB0000000000009001300140001001D00000016000200010000000ABB001659B700174B2AB0000000000009001800190001001D00000016000200010000000ABB001B59B7001C4B2AB0000000000000" ;
        String P0_I2_bytecodeInStr = "CAFEBABE00000034000F01000550302F49320700010100106A6176612F6C616E672F4F626A6563740700030100016D01001528294C6A6176612F6C616E672F496E74656765723B0100116A6176612F6C616E672F496E746567657207000703000000020100063C696E69743E010004284929560C000A000B0A0008000C010004436F64650601000200040000000000010001000500060001000E00000016000300010000000ABB0008591209B7000DB0000000000000" ;
        String P0_C3_bytecodeInStr = "CAFEBABE00000034002D01000550302F43330700010100106A6176612F6C616E672F4F626A65637407000301000550302F49300700050100063C696E69743E0100032829560C000700080A000400090100047465737401001528294C6A6176612F6C616E672F496E74656765723B01000950302F48656C70657207000D010005676574433501000928294C50302F43353B0C000F00100A000E00110100016D0C0013000C0B000600140100046D61696E010016285B4C6A6176612F6C616E672F537472696E673B29560C000B000C0A000200180100106A6176612F6C616E672F53797374656D07001A0100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C001C001D09001B001E01000E52657475726E2076616C75653A200800200100136A6176612F696F2F5072696E7453747265616D0700220100057072696E74010015284C6A6176612F6C616E672F537472696E673B29560C002400250A002300260100077072696E746C6E010015284C6A6176612F6C616E672F4F626A6563743B29560C002800290A0023002A010004436F646500210002000400010006000000030001000700080001002C0000001100010001000000052AB7000AB1000000000009000B000C0001002C00000017000100010000000BB800124B2AB900150100B0000000000009001600170001002C0000001E0003000200000012B800194CB2001F591221B600272BB6002BB1000000000000" ;
        String P0_C4_bytecodeInStr = "CAFEBABE00000034001401000550302F43340700010100106A6176612F6C616E672F4F626A65637407000301000550302F49300700050100063C696E69743E0100032829560C000700080A000400090100016D01001528294C6A6176612F6C616E672F496E74656765723B0100116A6176612F6C616E672F496E746567657207000D0300000004010004284929560C000700100A000E0011010004436F64650021000200040001000600000002000100070008000100130000001100010001000000052AB7000AB1000000000002000B000C0001001300000016000300010000000ABB000E59120FB70012B0000000000000" ;
        String P0_C5_bytecodeInStr = "CAFEBABE00000034000A01000550302F433507000101000550302F43340700030100063C696E69743E0100032829560C000500060A00040007010004436F6465002100020004000000000001000100050006000100090000001100010001000000052AB70008B1000000000000" ;

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "I0.class")) {
            byte[] P0_I0_bytecode =  hexStringToByteArray(P0_I0_bytecodeInStr);
            fos.write(P0_I0_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "C1.class")) {
            byte[] P0_I0_bytecode =  hexStringToByteArray(P0_C1_bytecodeInStr);
            fos.write(P0_I0_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "Helper.class")) {
            byte[] P0_Helper_bytecode =  hexStringToByteArray(P0_Helper_bytecodeInStr);
            fos.write(P0_Helper_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "I2.class")) {
            byte[] P0_I2_bytecode =  hexStringToByteArray(P0_I2_bytecodeInStr);
            fos.write(P0_I2_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "C3.class")) {
            byte[] P0_C3_bytecode =  hexStringToByteArray(P0_C3_bytecodeInStr);
            fos.write(P0_C3_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "C4.class")) {
            byte[] P0_C4_bytecode =  hexStringToByteArray(P0_C4_bytecodeInStr);
            fos.write(P0_C4_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "C5.class")) {
            byte[] P0_C5_bytecode =  hexStringToByteArray(P0_C5_bytecodeInStr);
            fos.write(P0_C5_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;
    }
}

可以根据你的需要修改路径。

复现

>>> path_to_jdk8/bin/java -cp . P0.C3
>>> path_to_jdk11/bin/java -cp . P0.C3
>>> path_to_jdk17/bin/java -cp . P0.C3
>>> path_to_jdk21/bin/java -cp . P0.C3

实际结果
jdk8:

Exception in thread "main" java.lang.IllegalAccessError: P0.C5.m()Ljava/lang/Integer;
        at P0.C3.test(Unknown Source)
        at P0.C3.main(Unknown Source)

jdk11:

Exception in thread "main" java.lang.AbstractMethodError: Receiver class P0.C5 does not define or inherit an implementation of the resolved method 'java.lang.Integer m()' of interface P0.I0.
        at P0.C3.test(Unknown Source)
        at P0.C3.main(Unknown Source)

jdk17:

Exception in thread "main" java.lang.AbstractMethodError: Receiver class P0.C5 does not define or inherit an implementation of the resolved method 'java.lang.Integer m()' of interface P0.I0.
        at P0.C3.test(Unknown Source)
        at P0.C3.main(Unknown Source)

jdk21:

Exception in thread "main" java.lang.AbstractMethodError: Receiver class P0.C5 does not define or inherit an implementation of the resolved method 'java.lang.Integer m()' of interface P0.I0.
        at P0.C3.test(Unknown Source)
        at P0.C3.main(Unknown Source)

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

Classfile /D:/Lab/TestJDoc/JSmith-main/your_dir/P0/C3.class
  Last modified May 12, 2025; size 539 bytes
  MD5 checksum dbff33781d5155f4f302757476bf08e2
public class P0.C3 implements P0.I0
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               P0/C3
   #2 = Class              #1             // P0/C3
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               P0/I0
   #6 = Class              #5             // P0/I0
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = NameAndType        #7:#8          // "<init>":()V
  #10 = Methodref          #4.#9          // java/lang/Object."<init>":()V
  #11 = Utf8               test
  #12 = Utf8               ()Ljava/lang/Integer;
  #13 = Utf8               P0/Helper
  #14 = Class              #13            // P0/Helper
  #15 = Utf8               getC5
  #16 = Utf8               ()LP0/C5;
  #17 = NameAndType        #15:#16        // getC5:()LP0/C5;
  #18 = Methodref          #14.#17        // P0/Helper.getC5:()LP0/C5;
  #19 = Utf8               m
  #20 = NameAndType        #19:#12        // m:()Ljava/lang/Integer;
  #21 = InterfaceMethodref #6.#20         // P0/I0.m:()Ljava/lang/Integer;
  #22 = Utf8               main
  #23 = Utf8               ([Ljava/lang/String;)V
  #24 = NameAndType        #11:#12        // test:()Ljava/lang/Integer;
  #25 = Methodref          #2.#24         // P0/C3.test:()Ljava/lang/Integer;
  #26 = Utf8               java/lang/System
  #27 = Class              #26            // java/lang/System
  #28 = Utf8               out
  #29 = Utf8               Ljava/io/PrintStream;
  #30 = NameAndType        #28:#29        // out:Ljava/io/PrintStream;
  #31 = Fieldref           #27.#30        // java/lang/System.out:Ljava/io/PrintStream;
  #32 = Utf8               Return value:
  #33 = String             #32            // Return value:
  #34 = Utf8               java/io/PrintStream
  #35 = Class              #34            // java/io/PrintStream
  #36 = Utf8               print
  #37 = Utf8               (Ljava/lang/String;)V
  #38 = NameAndType        #36:#37        // print:(Ljava/lang/String;)V
  #39 = Methodref          #35.#38        // java/io/PrintStream.print:(Ljava/lang/String;)V
  #40 = Utf8               println
  #41 = Utf8               (Ljava/lang/Object;)V
  #42 = NameAndType        #40:#41        // println:(Ljava/lang/Object;)V
  #43 = Methodref          #35.#42        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #44 = Utf8               Code
{
  public P0.C3();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: return

  public static java.lang.Integer test();
    descriptor: ()Ljava/lang/Integer;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: invokestatic  #18                 // Method P0/Helper.getC5:()LP0/C5;
         3: astore_0
         4: aload_0
         5: invokeinterface #21,  1           // InterfaceMethod P0/I0.m:()Ljava/lang/Integer;
        10: areturn

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: invokestatic  #25                 // Method test:()Ljava/lang/Integer;
         3: astore_1
         4: getstatic     #31                 // Field java/lang/System.out:Ljava/io/PrintStream;
         7: dup
         8: ldc           #33                 // String Return value:
        10: invokevirtual #39                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        13: aload_1
        14: invokevirtual #43                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        17: return
}

我们观察到,在 P0\C3.test() 中对 var0.m() 的调用使用了 invokeinterface。我们以V11为例,根据 Java SE 11 JVM 规范,invokeinterface 指令指出:“指定的接口方法会被解析(§5.4.3.4)。”此外,§5.4.3.4 规定:“否则,如果 C 声明了一个与接口方法引用指定的名称和描述符相匹配的方法,则方法查找成功。”基于此,解析出的接口方法是 I0.m()。

进入方法选择阶段(§5.4.6),已解析的方法 mR 是 I0.m(),类 C 是 C5。根据 §5.4.6 的规则:“如果 C 包含一个可以覆盖 mR(§5.4.5)的实例方法 m,则 m 即为被选择的方法。”由于 C5 没有声明这样的方法,流程继续执行下一条规则:“否则,如果 C 有超类,则从 C 的直接超类开始,沿着超类链向上搜索,查找可以覆盖 mR 的实例方法声明,直到找到方法或不再有超类为止。如果找到方法,则该方法即为被选择的方法。”然而,C5 的超类 C4 中的方法是 private 的,无法覆盖 I0.m(),因此这一步也失败了。

于是,流程进入最后一条规则:“否则,确定 C 的最具体超接口方法(§5.4.3.3)。如果恰好有一个方法与 mR 的名称和描述符匹配,并且不是 abstract 的,则该方法即为被选择的方法。”根据 §5.4.3.3,C5 的最具体超接口方法只有 I0.m(),它是非抽象的。因此,被选择的方法是 I0.m()。

回到 invokeinterface 指令,规范指出:“否则,如果被选择的方法既不是 public 也不是 private,则 invokeinterface 抛出 IllegalAccessError。”因此,结果应该是抛出 IllegalAccessError,而不是 AbstractMethodError。类似地,V17和V21也应抛出IAE而不是AME。

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