8195650: Method references to VarHandle accessors
authorpsandoz
Mon, 25 Jun 2018 14:39:44 -0700
changeset 50771 9ca95539747d
parent 50770 c545db4fc9bd
child 50772 c1b82eee939f
8195650: Method references to VarHandle accessors Reviewed-by: jrose
src/hotspot/share/classfile/systemDictionary.cpp
src/hotspot/share/prims/methodHandles.cpp
src/hotspot/share/prims/methodHandles.hpp
src/java.base/share/classes/java/lang/invoke/MethodHandles.java
test/hotspot/jtreg/runtime/ConstantPool/BadMethodHandles.java
test/jdk/java/lang/invoke/VarHandles/VarHandleMethodReferenceTest.java
--- a/src/hotspot/share/classfile/systemDictionary.cpp	Mon Jun 25 14:36:16 2018 -0700
+++ b/src/hotspot/share/classfile/systemDictionary.cpp	Mon Jun 25 14:39:44 2018 -0700
@@ -2709,11 +2709,11 @@
   java_lang_invoke_MemberName::set_flags(mname(), MethodHandles::ref_kind_to_flags(ref_kind));
 
   if (ref_kind == JVM_REF_invokeVirtual &&
-      callee->name() == vmSymbols::java_lang_invoke_MethodHandle() &&
-      (name == vmSymbols::invoke_name() || name == vmSymbols::invokeExact_name())) {
-    // Skip resolution for j.l.i.MethodHandle.invoke()/invokeExact().
-    // They are public signature polymorphic methods, but require appendix argument
-    // which MemberName resolution doesn't handle. There's special logic on JDK side to handle them
+      MethodHandles::is_signature_polymorphic_public_name(callee, name)) {
+    // Skip resolution for public signature polymorphic methods such as
+    // j.l.i.MethodHandle.invoke()/invokeExact() and those on VarHandle
+    // They require appendix argument which MemberName resolution doesn't handle.
+    // There's special logic on JDK side to handle them
     // (see MethodHandles.linkMethodHandleConstant() and MethodHandles.findVirtualForMH()).
   } else {
     MethodHandles::resolve_MemberName(mname, caller, /*speculative_resolve*/false, CHECK_(empty));
--- a/src/hotspot/share/prims/methodHandles.cpp	Mon Jun 25 14:36:16 2018 -0700
+++ b/src/hotspot/share/prims/methodHandles.cpp	Mon Jun 25 14:39:44 2018 -0700
@@ -491,6 +491,24 @@
   return vmIntrinsics::_none;
 }
 
+// Returns true if method is signature polymorphic and public
+bool MethodHandles::is_signature_polymorphic_public_name(Klass* klass, Symbol* name) {
+  if (is_signature_polymorphic_name(klass, name)) {
+    InstanceKlass* iklass = InstanceKlass::cast(klass);
+    int me;
+    int ms = iklass->find_method_by_name(name, &me);
+    assert(ms != -1, "");
+    for (; ms < me; ms++) {
+      Method* m = iklass->methods()->at(ms);
+      int required = JVM_ACC_NATIVE | JVM_ACC_VARARGS | JVM_ACC_PUBLIC;
+      int flags = m->access_flags().as_int();
+      if ((flags & required) == required && ArgumentCount(m->signature()).size() == 1) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
 
 // convert the external string or reflective type to an internal signature
 Symbol* MethodHandles::lookup_signature(oop type_str, bool intern_if_not_found, TRAPS) {
@@ -741,7 +759,7 @@
 
   vmIntrinsics::ID mh_invoke_id = vmIntrinsics::_none;
   if ((flags & ALL_KINDS) == IS_METHOD &&
-      (defc == SystemDictionary::MethodHandle_klass()) &&
+      (defc == SystemDictionary::MethodHandle_klass() || defc == SystemDictionary::VarHandle_klass()) &&
       (ref_kind == JVM_REF_invokeVirtual ||
        ref_kind == JVM_REF_invokeSpecial ||
        // static invocation mode is required for _linkToVirtual, etc.:
--- a/src/hotspot/share/prims/methodHandles.hpp	Mon Jun 25 14:36:16 2018 -0700
+++ b/src/hotspot/share/prims/methodHandles.hpp	Mon Jun 25 14:39:44 2018 -0700
@@ -143,6 +143,7 @@
   static bool is_signature_polymorphic_name(Klass* klass, Symbol* name) {
     return signature_polymorphic_name_id(klass, name) != vmIntrinsics::_none;
   }
+  static bool is_signature_polymorphic_public_name(Klass* klass, Symbol* name);
 
   static Bytecodes::Code signature_polymorphic_intrinsic_bytecode(vmIntrinsics::ID id);
 
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Mon Jun 25 14:36:16 2018 -0700
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Mon Jun 25 14:39:44 2018 -0700
@@ -2452,12 +2452,18 @@
                 checkSymbolicClass(defc);
                 return mh;
             }
-            // Treat MethodHandle.invoke and invokeExact specially.
             if (defc == MethodHandle.class && refKind == REF_invokeVirtual) {
+                // Treat MethodHandle.invoke and invokeExact specially.
                 mh = findVirtualForMH(member.getName(), member.getMethodType());
                 if (mh != null) {
                     return mh;
                 }
+            } else if (defc == VarHandle.class && refKind == REF_invokeVirtual) {
+                // Treat signature-polymorphic methods on VarHandle specially.
+                mh = findVirtualForVH(member.getName(), member.getMethodType());
+                if (mh != null) {
+                    return mh;
+                }
             }
             MemberName resolved = resolveOrFail(refKind, member);
             mh = getDirectMethodForConstant(refKind, defc, resolved);
--- a/test/hotspot/jtreg/runtime/ConstantPool/BadMethodHandles.java	Mon Jun 25 14:36:16 2018 -0700
+++ b/test/hotspot/jtreg/runtime/ConstantPool/BadMethodHandles.java	Mon Jun 25 14:39:44 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8087223
+ * @bug 8087223 8195650
  * @summary Adding constantTag to keep method call consistent with it.
  * @modules java.base/jdk.internal.org.objectweb.asm
  *          java.base/jdk.internal.misc
@@ -46,9 +46,9 @@
         ClassWriter cw = new ClassWriter(0);
         cw.visit(52, ACC_PUBLIC | ACC_SUPER, "BadInterfaceMethodref", null, "java/lang/Object", null);
         Handle handle1 =
-            new Handle(Opcodes.H_INVOKEINTERFACE, "BadInterfaceMethodref", "m", "()V");
+            new Handle(Opcodes.H_INVOKEINTERFACE, "BadInterfaceMethodref", "m", "()V", true);
         Handle handle2 =
-            new Handle(Opcodes.H_INVOKEINTERFACE, "BadInterfaceMethodref", "staticM", "()V");
+            new Handle(Opcodes.H_INVOKEINTERFACE, "BadInterfaceMethodref", "staticM", "()V", true);
 
         {
             MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
@@ -137,9 +137,9 @@
         ClassWriter cw = new ClassWriter(0);
         cw.visit(52, ACC_PUBLIC | ACC_SUPER,  "BadMethodref", null, "java/lang/Object", new String[]{"IBad"});
         Handle handle1 =
-            new Handle(Opcodes.H_INVOKEINTERFACE, "BadMethodref", "m", "()V");
+            new Handle(Opcodes.H_INVOKEINTERFACE, "BadMethodref", "m", "()V", true);
         Handle handle2 =
-            new Handle(Opcodes.H_INVOKEINTERFACE, "BadMethodref", "staticM", "()V");
+            new Handle(Opcodes.H_INVOKEINTERFACE, "BadMethodref", "staticM", "()V", true);
 
         {
             MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
@@ -176,6 +176,37 @@
         cw.visitEnd();
         return cw.toByteArray();
     }
+
+    static byte[] dumpInvokeBasic() {
+        ClassWriter cw = new ClassWriter(0);
+        cw.visit(52, ACC_PUBLIC | ACC_SUPER,  "InvokeBasicref", null, "java/lang/Object", null);
+        Handle handle =
+                new Handle(Opcodes.H_INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeBasic", "([Ljava/lang/Object;)Ljava/lang/Object;", false);
+
+        {
+            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+            mv.visitCode();
+            mv.visitVarInsn(ALOAD, 0);
+            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+            mv.visitInsn(RETURN);
+            mv.visitMaxs(1, 1);
+            mv.visitEnd();
+        }
+
+        {
+            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "runInvokeBasicM", "()V", null, null);
+            mv.visitCode();
+            mv.visitLdcInsn(handle);
+            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invoke", "()V", false);
+            mv.visitInsn(RETURN);
+            mv.visitMaxs(1, 1);
+            mv.visitEnd();
+        }
+
+        cw.visitEnd();
+        return cw.toByteArray();
+    }
+
     static class CL extends ClassLoader {
         @Override
         protected Class<?> findClass(String name) throws ClassNotFoundException {
@@ -184,6 +215,7 @@
             case "BadInterfaceMethodref": classBytes = dumpBadInterfaceMethodref(); break;
             case "BadMethodref"         : classBytes = dumpBadMethodref(); break;
             case "IBad"                 : classBytes = dumpIBad(); break;
+            case "InvokeBasicref"       : classBytes = dumpInvokeBasic(); break;
             default                     : throw new ClassNotFoundException(name);
             }
             return defineClass(name, classBytes, 0, classBytes.length);
@@ -200,6 +232,9 @@
         try (FileOutputStream fos = new FileOutputStream("BadMethodref.class")) {
           fos.write(dumpBadMethodref());
         }
+        try (FileOutputStream fos = new FileOutputStream("InvokeBasicref.class")) {
+            fos.write(dumpInvokeBasic());
+        }
 
         Class<?> cls = (new CL()).loadClass("BadInterfaceMethodref");
         String[] methods = {"runm", "runStaticM"};
@@ -249,5 +284,30 @@
             throw new Exception("BadMethodRef Failed to catch IncompatibleClassChangeError");
          }
 
+        System.out.println("Test InvokeBasicref:");
+        cls = (new CL()).loadClass("InvokeBasicref");
+        success = 0;
+        methods = new String[] {"runInvokeBasicM"};
+        for (String name : methods) {
+            try {
+                System.out.printf("invoke %s: \n", name);
+                cls.getMethod(name).invoke(cls.newInstance());
+                System.out.println("FAILED - ICCE should be thrown");
+            } catch (Throwable e) {
+                e.printStackTrace();
+                if (e instanceof InvocationTargetException && e.getCause() != null &&
+                    e.getCause() instanceof IncompatibleClassChangeError) {
+                    System.out.println("PASSED - expected ICCE thrown");
+                    success++;
+                    continue;
+                } else {
+                    System.out.println("FAILED with wrong exception" + e);
+                    throw e;
+                }
+            }
+        }
+        if (success != methods.length) {
+            throw new Exception("InvokeBasicref Failed to catch IncompatibleClassChangeError");
+        }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleMethodReferenceTest.java	Mon Jun 25 14:39:44 2018 -0700
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8195650
+ * @summary Test linking of method references to VarHandle access methods.
+ * @run testng VarHandleMethodReferenceTest
+ */
+
+import org.testng.annotations.Test;
+
+import java.lang.invoke.*;
+
+public class VarHandleMethodReferenceTest {
+
+  interface R {
+      void apply();
+  }
+
+  @Test
+  public void testMethodReferences() {
+      VarHandle vh = MethodHandles.arrayElementVarHandle(int[].class);
+
+      // The compilation of these method references will result in the
+      // placement of MethodHandles in the constant pool that reference
+      // VarHandle signature polymorphic methods.
+      // When those constant method handles are loaded the VarHandle invoker
+      // mechanism will be used where the first argument to invocation will be
+      // the bound VarHandle instance
+
+      // VarHandle invokers are tested by other test classes so here it
+      // is just necessary to check that functional objects can be successfully
+      // obtained, it does not matter about the signature of the functional
+      // interface
+
+      R r;
+      r = vh::get;
+      r = vh::set;
+      r = vh::getVolatile;
+      r = vh::setVolatile;
+      r = vh::getOpaque;
+      r = vh::setOpaque;
+      r = vh::getAcquire;
+      r = vh::setRelease;
+
+      r = vh::compareAndSet;
+      r = vh::compareAndExchange;
+      r = vh::compareAndExchangeAcquire;
+      r = vh::compareAndExchangeRelease;
+      r = vh::weakCompareAndSetPlain;
+      r = vh::weakCompareAndSet;
+      r = vh::weakCompareAndSetAcquire;
+      r = vh::weakCompareAndSetRelease;
+
+      r = vh::getAndSet;
+      r = vh::getAndSetAcquire;
+      r = vh::getAndSetRelease;
+      r = vh::getAndAdd;
+      r = vh::getAndAddAcquire;
+      r = vh::getAndAddRelease;
+      r = vh::getAndBitwiseOr;
+      r = vh::getAndBitwiseOrAcquire;
+      r = vh::getAndBitwiseOrRelease;
+      r = vh::getAndBitwiseAnd;
+      r = vh::getAndBitwiseAndAcquire;
+      r = vh::getAndBitwiseAndRelease;
+      r = vh::getAndBitwiseXor;
+      r = vh::getAndBitwiseXorAcquire;
+      r = vh::getAndBitwiseXorRelease;
+  }
+}