Resolution/translation of VarHandle.compareAndSet seems to conflict with JVMS/javadoc.
Dan Smith
daniel.smith at oracle.com
Thu Jan 10 20:36:12 UTC 2019
Thanks for this report. The research you've already put into it is quite helpful.
> On Jan 5, 2019, at 12:56 PM, Jorn Vernee <jbvernee at xs4all.nl> wrote:
>
> For the following code:
>
> public class Main {
> public int publicTestVariable = 10;
>
> public static void main(String[] args) throws Throwable {
> Main e = new Main();
> e.update();
> }
>
> public void update() throws Throwable {
> VarHandle publicIntHandle = MethodHandles.lookup()
> .findVarHandle(Main.class, "publicTestVariable", int.class);
> publicIntHandle.compareAndSet(this, 10, 100);
> }
> }
>
> javac derives the descriptor for the call to `compareAndSet` [2] as `(Lmain/Main;II)Z`, but eclipsec derives the descriptor as `(Lmain/Main;II)V`. Note that `compareAndSet` is signature polymorphic.
>
> Each compiler generates a different return type. The one generated by eclipsec seems to be correct since the call site has no indication that the call should return a boolean. According to the javadoc for Signature polymorphism [3] "The unusual part is that the symbolic type descriptor is derived from the actual argument and return types, not from the method declaration.", and the code generated by javac definitely seems to depend on the method declaration, and not just the call site.
javac is correct here. See
https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.3-400-B
"If the signature polymorphic method is either void or has a return type other than Object, the compile-time result is the result of the invocation type of the compile-time declaration"
The informal description in javadoc that you reference is incomplete, and I've filed a bug to fix that:
https://bugs.openjdk.java.net/browse/JDK-8216511
> Regardless of the question whether discarding the returned value is a good idea or not, the exception being thrown seems to conflict with JVMS-5.4.3.3 [4] which states:
>
> "If C declares exactly one method with the name specified by the method reference, and the declaration is a signature polymorphic method (§2.9.3), then method lookup succeeds. All the class names mentioned in the descriptor are resolved (§5.4.3.1). The resolved method is the signature polymorphic method declaration. It is not necessary for C to declare a method with the descriptor specified by the method reference."
>
> The javadoc [3] also says that: "When the JVM processes bytecode containing signature polymorphic calls, it will successfully link any such call, regardless of its symbolic type descriptor".
>
> So the VM should not be throwing an NSME here AFAICT. But, it looks like this case is being checked explicitly and an error is being thrown [5]:
>
> if (ak.at.isMonomorphicInReturnType) {
> if (ak.at.returnType != mtype.returnType()) {
> // The caller contains a different return type than that
> // defined by the method
> throw newNoSuchMethodErrorOnVarHandle(name, mtype);
> }
> // Adjust the return type of the signature method type
> guardReturnType = ak.at.returnType;
> }
You are correct. A link-time NoSuchMethodError is not specified here. Rather, per the VarHandle javadoc, we should see a run-time WrongMethodTypeException.
Bug report:
https://bugs.openjdk.java.net/browse/JDK-8216520
This case probably hasn't gotten much attention because javac will never produce it.
> imho the call should still succeed with `void` as a return type (discarding the value). For other types, it would be more consistent with the spec/doc if the return type was dynamically cast to that of the descriptor, with possible CCE, but linking still succeeded.
Linking should succeed, but the run-time model these calls follow is 'invokeExact', not 'invoke': by design, they should fail immediately if there's a mismatch between expected and actual types, even if there's an easy adaptation (like dropping the result) from one to the other.
—Dan
More information about the jdk-dev
mailing list