8

VarHandle is showing below error -

Exception in thread "main" java.lang.NoSuchMethodError: VarHandle.compareAndSet(VarHandleExample,int,int)void
    at java.base/java.lang.invoke.MethodHandleNatives.newNoSuchMethodErrorOnVarHandle(MethodHandleNatives.java:492)
    at java.base/java.lang.invoke.MethodHandleNatives.varHandleOperationLinkerMethod(MethodHandleNatives.java:445)
    at java.base/java.lang.invoke.MethodHandleNatives.linkMethodImpl(MethodHandleNatives.java:378)
    at java.base/java.lang.invoke.MethodHandleNatives.linkMethod(MethodHandleNatives.java:366)
    at j9.VarHandleExample.update(VarHandleExample.java:23)
    at j9.VarHandleExample.main(VarHandleExample.java:14)

My Program is :

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class VarHandleExample {
    public int publicTestVariable = 10;
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        VarHandleExample e= new VarHandleExample();
        e.update();
    }
    public void update() throws NoSuchFieldException, IllegalAccessException {
        VarHandle publicIntHandle = MethodHandles.lookup()
              .in(VariableHandlesTest.class)
              .findVarHandle(VarHandleExample.class, "publicTestVariable", int.class);
        publicIntHandle.compareAndSet(this, 10, 100); // CAS
    }
}
Naman
  • 27,789
  • 26
  • 218
  • 353
  • Hi, _stack snippets_ don't run Java. Please edit to just list it as code instead of snippet. – jonathangersam Oct 24 '18 at 07:20
  • How are you executing this code? Is it executed on Java9+? *VarHandle.compareAndSet(VarHandleExample,int,int)void* is wierd in the stacktrace – Naman Oct 24 '18 at 07:26
  • I am executing this code in eclipse on java 10 – user3168018 Oct 24 '18 at 08:13
  • @user3168018 Since the method does exist there. Could you check your Eclipse version and its compatibility with JDK version you're running with? Looks mostly a configuration issue. Are you able to use `var` for example in your code using eclipse? – Naman Oct 24 '18 at 08:46
  • No var is not working. but VarHandle.getAndAdd(this, 210) was working fine. – user3168018 Oct 24 '18 at 09:46
  • Yes, You are right. Now it is working, If I run directly using javac and java. Please let me know which eclipse version should I use – user3168018 Oct 24 '18 at 09:52

1 Answers1

6

This seems to be a bug in the JVM/JDK/Spec/Doc, which depends on how the signature of a signature polymorphic method is translated by the compiler.


compareAndSet is marked with @MethodHandle.PolymorphicSignature. This means semantically (paraphrased) that whatever signature is found at the call site will be used to invoke the method. This mainly prevents the boxing of the arguments.

The full signature of compareAndSet in VarHandle is:

public final native
@MethodHandle.PolymorphicSignature
@HotSpotIntrinsicCandidate
boolean compareAndSet(Object... args);

Note that it returns a boolean, but the error shows us the VM is trying to link VarHandle.compareAndSet(VarHandleExample,int,int)void, which has a different return type. This can be seen in the bytecode generated by the Eclipse compiler as well:

publicIntHandle.compareAndSet(this, 10, 100); // CAS

Is (partly) translated as:

25: invokevirtual #55 // Method java/lang/invoke/VarHandle.compareAndSet:(LVarHandleExample;II)V

(Note the comment there which shows us the signature of the method reference constant in the constant pool which is used to link the method)

So at runtime it seems that the VM will try and find a method with V (i.e. void) as a return type, which indeed doesn't exist.

javac on the other hand generates this signature:

25: invokevirtual #11 // Method java/lang/invoke/VarHandle.compareAndSet:(LVarHandleExample;II)Z

Where the return type is Z (meaning boolean) instead of V.


You can work around this by explicitly making the return type boolean, either by using the return value:

boolean b = publicIntHandle.compareAndSet(this, 10, 100); // CAS

Or by using a blank if in case you don't need the value:

if(publicIntHandle.compareAndSet(this, 10, 100)); // CAS

Now on to the language lawyer part.

I could find limited info about signature polymorphic methods (the ones marked with @PolymorphicSignature) [1] (nothing in the language spec). There doesn't seem to be any mandate about how a descriptor for a signature polymorphic method should be derived and translated by the compiler in the specification.

Perhaps most interesting is this passage from jvms-5.4.3.3 (emphasis mine):

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.

Where C in this case would be VarHandle, the method being looked up would be compareAndSet, and the descriptor either (LVarHandleExample;II)Z or (LVarHandleExample;II)V depending on the compiler.

Also interesting is the javadoc about Signature Polymorphism:

When the JVM processes bytecode containing signature polymorphic calls, it will successfully link any such call, regardless of its symbolic type descriptor. (In order to retain type safety, the JVM will guard such calls with suitable dynamic type checks, as described elsewhere.)

VarHandle does have exactly one method with the name compareAndSet and it is signature polymorphic, so the lookup should succeed. Imho it is a problem with the VM that an exception is thrown in this case, since the descriptor, and therefore the return type should not matter according to the specification.

There also seems to be a problem with javac emitting Z as a return type in the descriptor. According to that same javadoc section:

The unusual part is that the symbolic type descriptor is derived from the actual argument and return types, not from the method declaration.

However, the descriptor emitted by javac definitely depends on the method declaration.

So there seem to be 2 bugs here, according to the spec/doc;

  1. in the VM you're using, which incorrectly fails to link a signature polymorphic method. I can also reproduce it with OpenJDK 64-Bit Server VM (build 13-internal+0-adhoc.Jorn.jdk, mixed mode, sharing), which is the latest OpenJDK source.

  2. in javac which emits the wrong return type for the descriptor.

I'm assuming the specification as the primary authority, but it seems more likely in this case that the specification/documentation just wasn't updated after the implementation was, and that's what's biting eclipsec.


I got a response to my email to jdk-dev, answered by Dan Smith.

For 2.)

javac is correct here. See 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

So it appears that Eclipse is generating the wrong descriptor for the call, not javac.

For 1.)

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

Community
  • 1
  • 1
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • I've also sent an email asking about this to jdk-dev, will update with a link once it gets posted. – Jorn Vernee Jan 05 '19 at 20:03
  • In analogy to `MethodHandle.invoke[Exact]`, the spec says that the linkage must succeed, but that doesn’t imply that the invocation will, i.e. it may throw an exception when the types mismatch. So in practice, it doesn’t matter to the developer whether the invocation fails as a linkage error or as a `WrongMethodTypeException` when the `VarHandle` implementation considers a return type of `void` as mismatch. – Holger Jan 10 '19 at 17:19
  • Regarding what the compiler needs to do, JLS 1.4 refers to the API documentation and JLS 2.9.3 to `java.lang.invoke` explicitly. And `VarHandle` says “*From the viewpoint of source code, these methods can take any arguments and their polymorphic result (if expressed) can be cast to any return type. Formally this is accomplished by giving the access mode methods variable arity Object arguments and Object return types (if the return type is polymorphic)*, …” which (very subtly) suggests that return types may not be polymorphic when not declared as `Object`. – Holger Jan 10 '19 at 17:22
  • @Holger Indeed whether this fails with a linker error or with an invocation error doesn't really matter. But the spec says explicitly that linkage should succeed, so that should disallow the possibility of a linkage error. On the other thing, yes, the return type is not polymorphic, but the spec on signature polymorphic methods doesn't have a special provision for that (it seems to assume the return type is always `Object`). It seems to me like the doc/spec just wasn't updated after the implementation was changed (and that's biting eclipsec). – Jorn Vernee Jan 10 '19 at 17:35
  • 1
    Well yes, but that’s not the first time that a particular compiler behavior is assumed without saying anything in the JLS or JVMS. In practice, `javac` and HotSpot developers work together as they like and when others point to a mismatch, the specification gets adapted to reflect what `javac` and HotSpot actually do. That happened when HotSpot resolved interfaces with `default` methods eagerly (became mandated behavior then)[Q&A](https://stackoverflow.com/q/23096084/2711488) and it happened with anonymous inner classes actually no being final[Q&A](https://stackoverflow.com/a/54019398/2711488). – Holger Jan 10 '19 at 17:52
  • 1
    And, of course, that’s biting any other implementor, but I remember the Java 5 language specification being released months after the release, obviously to reflect what the compiler developers did rather than what was intended, so since then, I’m not surprised about anything anymore… – Holger Jan 10 '19 at 17:56