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

java.lang.IncompatibleClassChangeError: Conflicting default methods

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: P4 P4
    • None
    • 25, 26
    • hotspot
    • generic
    • generic

      A DESCRIPTION OF THE PROBLEM :
      JVMS §5.4.3.3 Method Resolution says:

      >[When] method resolution attempts to locate the referenced method in the superinterfaces of the specified class C:
      >
      >If the maximally-specific superinterface methods of C for the name and descriptor specified by the method reference include exactly one method that does not have its ACC_ABSTRACT flag set, then this method is chosen and method lookup succeeds.
      >
      >Otherwise, if any superinterface of C declares a method with the name and descriptor specified by the method reference that has neither its ACC_PRIVATE flag nor its ACC_STATIC flag set, one of these is arbitrarily chosen and method lookup succeeds."

      I.e., if we have a class which inherits from its interfaces two equally-specific competing definitions of a default method, one of these is chosen arbitrarily.

      In reality, as shown by the attached test case, the VM throws an error:

      java.lang.IncompatibleClassChangeError: Conflicting default methods: I1.foo I2.foo

      In trying to determine if it is the VM or the spec that is wrong here, I notice that JLS §13.5.7, which gives an equivalent example involving "Cowboy" and "CowboyArtist", says that an IncompatibleClassChangeError will be thrown.


      ---------- BEGIN SOURCE ----------
      import java.lang.classfile.ClassFile;
      import java.lang.constant.*;
      import java.lang.reflect.Method;

      class MaxSpecificBug {
          public static void main(String... args) throws Throwable {
              // public class C implements I1, I2 {}
              byte[] cBytes = ClassFile.of().build(ClassDesc.of("C"), cb -> {
                  cb.withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_SUPER);
                  cb.withInterfaceSymbols(ClassDesc.of("I1"), ClassDesc.of("I2"));
                  cb.withMethodBody("<init>", ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, cdb -> {
                      cdb.aload(0);
                      cdb.invokespecial(ConstantDescs.CD_Object, "<init>", ConstantDescs.MTD_void);
                      cdb.return_();
                  });
              });
              
              // public interface I1 { default void foo() { System.out.println("Hello from I1"); } }
              byte[] i1Bytes = ClassFile.of().build(ClassDesc.of("I1"), cb -> {
                  cb.withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_INTERFACE | ClassFile.ACC_ABSTRACT);
                  cb.withMethodBody("foo", ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, cdb -> {
                      cdb.getstatic(ClassDesc.of("java.lang.System"), "out", ClassDesc.of("java.io.PrintStream"));
                      cdb.ldc("Hello from I1");
                      cdb.invokevirtual(ClassDesc.of("java.io.PrintStream"), "println",
                          MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object));
                      cdb.return_();
                  });
              });
              
              // public interface I2 { default void foo() { System.out.println("Hello from I2"); } }
              byte[] i2Bytes = ClassFile.of().build(ClassDesc.of("I2"), cb -> {
                  cb.withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_INTERFACE | ClassFile.ACC_ABSTRACT);
                  cb.withMethodBody("foo", ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, cdb -> {
                      cdb.getstatic(ClassDesc.of("java.lang.System"), "out", ClassDesc.of("java.io.PrintStream"));
                      cdb.ldc("Hello from I2");
                      cdb.invokevirtual(ClassDesc.of("java.io.PrintStream"), "println",
                          MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object));
                      cdb.return_();
                  });
              });
              
              DirectClassLoader dcl = new DirectClassLoader();
              dcl.load(i1Bytes);
              dcl.load(i2Bytes);
              Class<?> clss = dcl.load(cBytes);
              
              Object instance = clss.getConstructor().newInstance();
              Method foo = clss.getMethod("foo");
              
              // expected: "Hello from I1" or "Hello from I2"; no error
              // reality: IncompatibleClassChangeError
              foo.invoke(instance);
          }
          
          
          static final class DirectClassLoader extends ClassLoader {
              public Class<?> load(byte[] classBytes) {
                  return super.defineClass(null, classBytes, 0, classBytes.length);
              }
          }
      }
      ---------- END SOURCE ----------

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: