Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8362167

HotSpot vs OpenJ9 Handle Access Control Differently, Leading to IAE vs AME

XMLWordPrintable

    • x86_64
    • windows

      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 "11.0.26" 2025-01-21 LTS
      Java(TM) SE Runtime Environment 18.9 (build 11.0.26+7-LTS-187)
      Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.26+7-LTS-187, mixed mode)

      openjdk version "11.0.27" 2025-04-15
      IBM Semeru Runtime Open Edition 11.0.27.0 (build 11.0.27+6)
      Eclipse OpenJ9 VM 11.0.27.0 (build openj9-0.51.0, JRE 11 Windows 11 amd64-64-Bit Compressed References 20250504_1192 (JIT enabled, AOT enabled)
      OpenJ9 - 31cf5538b0
      OMR - 9bcff94a2
      JCL - 3e17c0897e based on jdk-11.0.27+6)

      A DESCRIPTION OF THE PROBLEM :
      Given a test case, we found that the execution results of this test case on different JVMs are different, the simplified test case can be found below. In summary, Hotspot(11.0.26) threw IllegalAccessError while J9(11.0.27) threw AbstractMethodError.

      REGRESSION : Last worked in version 11

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      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 P0 = new File(baseDir + "/" + "P0");
              File P3 = new File(baseDir + "/" + "P3");
              P0.mkdirs();
              P3.mkdirs();

              String P0_C2_bytecodeInStr = "CAFEBABE00000034001201000550302F433207000101000550332F43310700030100063C696E69743E0100032829560C000500060A000400070100016D01001528294C6A6176612F6C616E672F496E74656765723B0100116A6176612F6C616E672F496E746567657207000B0300000002010004284929560C0005000E0A000C000F010004436F6465002100020004000000000002000100050006000100110000001100010001000000052AB70008B10000000000020009000A0001001100000016000300010000000ABB000C59120DB70010B0000000000000" ;
              String P0_helper_bytecodeInStr = "CAFEBABE00000034000F01000950302F48656C7065720700010100106A6176612F6C616E672F4F626A6563740700030100063C696E69743E0100032829560C000500060A00040007010005676574433201000928294C50302F43323B01000550302F433207000B0A000C0007010004436F64650021000200040000000000020001000500060001000E0000001100010001000000052AB70008B10000000000090009000A0001000E00000016000200010000000ABB000C59B7000D4B2AB0000000000000" ;
              String P0_I0_bytecodeInStr = "CAFEBABE00000034002701000550302F49300700010100106A6176612F6C616E672F4F626A6563740700030100016D01001528294C6A6176612F6C616E672F496E74656765723B0100047465737401000950302F48656C706572070008010005676574433201000928294C50302F43323B0C000A000B0A0009000C0C000500060B0002000E0100046D61696E010016285B4C6A6176612F6C616E672F537472696E673B29560C000700060B000200120100106A6176612F6C616E672F53797374656D0700140100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C00160017090015001801000E52657475726E2076616C75653A2008001A0100136A6176612F696F2F5072696E7453747265616D07001C0100057072696E74010015284C6A6176612F6C616E672F537472696E673B29560C001E001F0A001D00200100077072696E746C6E010015284C6A6176612F6C616E672F4F626A6563743B29560C002200230A001D0024010004436F646506010002000400000000000304010005000600000009000700060001002600000017000100010000000BB8000D4B2AB9000F0100B000000000000900100011000100260000001E0003000200000012B800134CB2001959121BB600212BB60025B1000000000000" ;
              String P3_C1_bytecodeInStr = "CAFEBABE00000034001401000550332F43310700010100106A6176612F6C616E672F4F626A65637407000301000550302F49300700050100063C696E69743E0100032829560C000700080A000400090100016D01001528294C6A6176612F6C616E672F496E74656765723B0100116A6176612F6C616E672F496E746567657207000D0300000001010004284929560C000700100A000E0011010004436F64650021000200040001000600000002000100070008000100130000001100010001000000052AB7000AB1000000000000000B000C0001001300000016000300010000000ABB000E59120FB70012B0000000000000" ;
              String P3_Helper_bytecodeInStr = "CAFEBABE00000034000F01000950332F48656C7065720700010100106A6176612F6C616E672F4F626A6563740700030100063C696E69743E0100032829560C000500060A00040007010005676574433101000928294C50332F43313B01000550332F433107000B0A000C0007010004436F64650021000200040000000000020001000500060001000E0000001100010001000000052AB70008B10000000000090009000A0001000E00000016000200010000000ABB000C59B7000D4B2AB0000000000000" ;

              try (FileOutputStream fos = new FileOutputStream(P0.getAbsolutePath() + "/" + "C2.class")) {
                  byte[] P0_C2_bytecode = hexStringToByteArray(P0_C2_bytecodeInStr);
                  fos.write(P0_C2_bytecode);
              } catch (IOException e) {
                  e.printStackTrace();
              }

              try (FileOutputStream fos = new FileOutputStream(P0.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(P0.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(P3.getAbsolutePath() + "/" + "C1.class")) {
                  byte[] P3_C1_bytecode = hexStringToByteArray(P3_C1_bytecodeInStr);
                  fos.write(P3_C1_bytecode);
              } catch (IOException e) {
                  e.printStackTrace();
              }

              try (FileOutputStream fos = new FileOutputStream(P3.getAbsolutePath() + "/" + "Helper.class")) {
                  byte[] P3_Helper_bytecode = hexStringToByteArray(P3_Helper_bytecodeInStr);
                  fos.write(P3_Helper_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.

      Repoduce:

      >>> path_to_jdk/Hotspot_11.0.26/bin/java -cp . P0.I0

      >>> path_to_jdk/openj9/11.0.27/bin/java -cp . P0.I0

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      According to this interesting result, we first decompiled P0.I0 using javap. The results are as follows:

      ```java
      Classfile /D:/Lab/TestJDoc/reduce/Test_JDoc/log/jvm-test/openj9-8/InterfaceSignatureConflictTest/case_21/P0/I0.class
        Last modified Jul 3, 2025; size 478 bytes
        MD5 checksum f92264b361e06d195ccb364c30d7271a
      public interface P0.I0
        minor version: 0
        major version: 52
        flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
      Constant pool:
         #1 = Utf8 P0/I0
         #2 = Class #1 // P0/I0
         #3 = Utf8 java/lang/Object
         #4 = Class #3 // java/lang/Object
         #5 = Utf8 m
         #6 = Utf8 ()Ljava/lang/Integer;
         #7 = Utf8 test
         #8 = Utf8 P0/Helper
         #9 = Class #8 // P0/Helper
        #10 = Utf8 getC2
        #11 = Utf8 ()LP0/C2;
        #12 = NameAndType #10:#11 // getC2:()LP0/C2;
        #13 = Methodref #9.#12 // P0/Helper.getC2:()LP0/C2;
        #14 = NameAndType #5:#6 // m:()Ljava/lang/Integer;
        #15 = InterfaceMethodref #2.#14 // P0/I0.m:()Ljava/lang/Integer;
        #16 = Utf8 main
        #17 = Utf8 ([Ljava/lang/String;)V
        #18 = NameAndType #7:#6 // test:()Ljava/lang/Integer;
        #19 = InterfaceMethodref #2.#18 // P0/I0.test:()Ljava/lang/Integer;
        #20 = Utf8 java/lang/System
        #21 = Class #20 // java/lang/System
        #22 = Utf8 out
        #23 = Utf8 Ljava/io/PrintStream;
        #24 = NameAndType #22:#23 // out:Ljava/io/PrintStream;
        #25 = Fieldref #21.#24 // java/lang/System.out:Ljava/io/PrintStream;
        #26 = Utf8 Return value:
        #27 = String #26 // Return value:
        #28 = Utf8 java/io/PrintStream
        #29 = Class #28 // java/io/PrintStream
        #30 = Utf8 print
        #31 = Utf8 (Ljava/lang/String;)V
        #32 = NameAndType #30:#31 // print:(Ljava/lang/String;)V
        #33 = Methodref #29.#32 // java/io/PrintStream.print:(Ljava/lang/String;)V
        #34 = Utf8 println
        #35 = Utf8 (Ljava/lang/Object;)V
        #36 = NameAndType #34:#35 // println:(Ljava/lang/Object;)V
        #37 = Methodref #29.#36 // java/io/PrintStream.println:(Ljava/lang/Object;)V
        #38 = Utf8 Code
      {
        public abstract java.lang.Integer m();
          descriptor: ()Ljava/lang/Integer;
          flags: ACC_PUBLIC, ACC_ABSTRACT

        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 #13 // Method P0/Helper.getC2:()LP0/C2;
               3: astore_0
               4: aload_0
               5: invokeinterface #15, 1 // InterfaceMethod 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 #19 // InterfaceMethod test:()Ljava/lang/Integer;
               3: astore_1
               4: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
               7: dup
               8: ldc #27 // String Return value:
              10: invokevirtual #33 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
              13: aload_1
              14: invokevirtual #37 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
              17: return
      }

      ```

      In analyzing the call to `var0.m()` in `P0/I0.test()`, we observe that it uses `invokeinterface`. According to the JVM SE11 specification, specifically section [5.4.6](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.6), “A method is selected with respect to class `C` and the resolved method.” We then examined the method selection logic, which states:

      > Otherwise, the selected method is determined by the following lookup procedure:
      >
      > - If `C` contains a declaration of an instance method `m` that can override `mR` ([§5.4.5](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.5)), then `m` is the selected method.
      > - Otherwise, if `C` has a superclass, a search for a declaration of an instance method that can override `mR` is performed, starting with the direct superclass of `C` and continuing with the direct superclass of that class, and so forth, until a method is found or no further superclasses exist. If a method is found, it is the selected method.

      Although `C2` is the `Objectref`, its method `m()` is `private` and thus cannot override `I0.m()`. Considering `C2`’s superclass `C1`, which contains `m()`, we referred to section [5.4.5](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.5), which states: “`mA` is marked neither `ACC_PUBLIC` nor `ACC_PROTECTED` nor `ACC_PRIVATE`, and either (a) the declaration of `mA` appears in the same run-time package as the declaration of `mC`, or (b) if `mA` is declared in a class `A` and `mC` is declared in a class `C`, then there exists a method `mB` declared in a class `B` such that `C` is a subclass of `B` and `B` is a subclass of `A` and `mC` can override `mB` and `mB` can override `mA`.” However, since `C1` directly implements `I0` and `C1.m()` is package-private, it cannot override `I0.m()`. Consequently, no method is found during the method selection phase.

      Returning to `invokeinterface`, the specification states: “Otherwise, if no method is selected, and there are no maximally-specific superinterface methods of `C` that match the resolved method's name and descriptor and are not `abstract`, *invokeinterface* throws an `AbstractMethodError`.”

      Thus, for this test case in HotSpot (11.0.26), the expected outcome is an `AbstractMethodError`, rather than the `IllegalAccessError` observed in the output.
      ACTUAL -
      The output is as follows:

      Hotspot(11.0.26):

      ```

      Exception in thread "main" java.lang.IllegalAccessError: 'java.lang.Integer P0.C2.m()'

          at P0.I0.test(Unknown Source)
          at P0.I0.main(Unknown Source)

      ```

      OpenJ9(11.0.27):

      ```

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

      ```

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

      ```java
      package P0;

      import P3.C1;

      public class C2 extends C1 {
          public C2() {
          }

          private Integer m() {
              return new Integer(2);
          }
      }
      ```

      ```java
      package P0;

      public class Helper {
          public Helper() {
          }

          public static C2 getC2() {
              C2 var0 = new C2();
              return var0;
          }
      }
      ```

      ```java
      package P0;

      import java.io.PrintStream;

      public interface I0 {
          Integer m();

          static Integer test() {
              C2 var0 = Helper.getC2();
              return var0.m();
          }

          static void main(String[] var0) {
              Integer var1 = test();
              PrintStream var10000 = System.out;
              var10000.print("Return value: ");
              var10000.println(var1);
          }
      }
      ```

      ```java
      package P3;

      import P0.I0;

      public class C1 implements I0 {
          public C1() {
          }

          Integer m() {
              return new Integer(1);
          }
      }
      ```

      ```java
      package P3;

      public class Helper {
          public Helper() {
          }

          public static C1 getC1() {
              C1 var0 = new C1();
              return var0;
          }
      }
      ```
      ---------- END SOURCE ----------

            dholmes David Holmes
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: