RFR: 8282080: Lambda deserialization fails for Object method references on interfaces

Maurizio Cimadamore mcimadamore at openjdk.java.net
Thu Apr 28 11:10:50 UTC 2022


On Thu, 31 Mar 2022 08:13:57 GMT, Jan Lahoda <jlahoda at openjdk.org> wrote:

> Per JLS, every interface with no superinterfaces implicitly has all public methods `java.lang.Object` has (unless the interface declares them explicitly). So, having:
> 
> interface I {}
> 
> the interface has methods like `hashCode()`. But, before https://bugs.openjdk.java.net/browse/JDK-8272564, javac was referring to them as if they were a `java.lang.Object` methods, not the interface methods. E.g. saying `I i = null; i.hashCode()` lead to a reference to `java.lang.Object.hashCode()` not `I.hashCode()`. That was changed in JDK-8272564, and now the reference is to `I.hashCode()`.
> 
> There is one issue, though: when the reference is for a serializable (and serialized) method handle, the runtime method is still `java.lang.Object::hashCode`, but the deserialization code generated by javac expects `I::hashCode`, and hence the serialized method handle fails to deserialize.
> 
> The proposal here is to fix just this case, by changing the deserialization code to expect the method from `java.lang.Object`, which should revert the deserialization behavior back to JDK 17 behavior.

I have to admit I'm a bit worried about the impact of the original fix (JDK-8272564). Object methods are special cases on many occasions - one I recall on top of my head is in Gen::binaryQualifier:


        // leave alone methods inherited from Object
        // JLS 13.1.
        if (sym.owner == syms.objectType.tsym)
            return sym;


That is, when emitting an `invokevirtual`, if the source code says `X::toString`, the bytecode should still say `Object::toString`, as per JLS, as shown below (this is a quote from 13.1):


Given a method invocation expression or a method reference expression in a class or interface C, referencing a method named m declared (or implicitly declared ([§9.2](https://docs.oracle.com/javase/specs/jls/se18/html/jls-9.html#jls-9.2))) in a (possibly distinct) class or interface D, we define the qualifying class or interface of the method invocation as follows:

    If D is Object then the qualifying class or interface of the method invocation is Object.


Consider this simple example:


interface A { }

class Test {
   void test(A a) {
      a.toString();
   }
}


Before the fix, the javap output for `test` was like this:


void test(A);
    descriptor: (LA;)V
    flags: (0x0000)
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: invokevirtual #2                  // Method java/lang/Object.toString:()Ljava/lang/String;
         4: pop
         5: return


But now, we get this instead:


void test(A);
    descriptor: (LA;)V
    flags: (0x0000)
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: invokeinterface #7,  1            // InterfaceMethod A.toString:()Ljava/lang/String;
         6: pop
         7: return


This seems to be in clear violation of what the JLS says. So, I think JDK-8272564 should be either reverted or amended to restore compliance. There is a risk that, in doing point fixes (like in this PR, or additionally addressing the issue in `Gen::binaryQualifier`) we go down a whack-a-mole path.

-------------

PR: https://git.openjdk.java.net/jdk/pull/8054


More information about the compiler-dev mailing list