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