changeset 59087:f796bd335b45 nestmates

[nestmates] Lambda proxy uses live MethodHandle for a protected member inherited from its superclass in a different package. Summary: class C attempts to access a protected member D::m inherited from its supertype in a different package. The lambda proxy class HC is not a subtype of D and D::m is not inaccessible to HC. C may not necessarily have the bridge for example D::m was public member when C was compiled and later D::m is changed from public to protected in a separation compilation. D::m is accessible to C with a receiver object of C. Changing D::m to protected is a supported binary compatibility. JDK-8199386 should be explored as a long-term solution that a framework can use Lookup::in to teleport from HC to C to obtain a method handle for D::m.
author mchung
date Fri, 21 Feb 2020 12:03:58 -0800
parents 8713339a1601
children 72e8ad32c65c
files src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java src/java.base/share/classes/java/lang/invoke/MethodHandles.java test/jdk/java/lang/invoke/lambda/superProtectedMethod/InheritedProtectedMethod.java test/jdk/java/lang/invoke/lambda/superProtectedMethod/SuperMethodTest.java test/jdk/java/lang/invoke/lambda/superProtectedMethod/modified/MethodSupplierOuter.java test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MethodInvoker.java test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MyFunctionalInterface.java test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/anotherpkg/MethodSupplierOuter.java
diffstat 9 files changed, 269 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java	Fri Feb 21 14:22:32 2020 +0000
+++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java	Fri Feb 21 12:03:58 2020 -0800
@@ -34,6 +34,7 @@
 import java.io.Serializable;
 import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.LinkedHashSet;
@@ -62,13 +63,17 @@
     private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;";
     private static final String DESCR_METHOD_WRITE_OBJECT = "(Ljava/io/ObjectOutputStream;)V";
     private static final String DESCR_METHOD_READ_OBJECT = "(Ljava/io/ObjectInputStream;)V";
+    private static final String DESCR_SET_IMPL_METHOD = "(Ljava/lang/invoke/MethodHandle;)V";
+
     private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace";
     private static final String NAME_METHOD_READ_OBJECT = "readObject";
     private static final String NAME_METHOD_WRITE_OBJECT = "writeObject";
+    private static final String NAME_FIELD_IMPL_METHOD = "protectedImplMethod";
 
     private static final String DESCR_CLASS = "Ljava/lang/Class;";
     private static final String DESCR_STRING = "Ljava/lang/String;";
     private static final String DESCR_OBJECT = "Ljava/lang/Object;";
+    private static final String DESCR_METHOD_HANDLE = "Ljava/lang/invoke/MethodHandle;";
     private static final String DESCR_CTOR_SERIALIZED_LAMBDA
             = "(" + DESCR_CLASS + DESCR_STRING + DESCR_STRING + DESCR_STRING + "I"
             + DESCR_STRING + DESCR_STRING + DESCR_STRING + DESCR_STRING + "[" + DESCR_OBJECT + ")V";
@@ -104,6 +109,7 @@
     private final String[] argNames;                 // Generated names for the constructor arguments
     private final String[] argDescs;                 // Type descriptors for the constructor arguments
     private final String lambdaClassName;            // Generated name for the generated class "X$$Lambda$1"
+    private final boolean useImplMethodHandle;       // use MethodHandle invocation instead of symbolic bytecode invocation
 
     /**
      * General meta-factory constructor, supporting both standard cases and
@@ -160,6 +166,8 @@
         implMethodDesc = implInfo.getMethodType().toMethodDescriptorString();
         constructorType = invokedType.changeReturnType(Void.TYPE);
         lambdaClassName = lambdaClassName(targetClass);
+        useImplMethodHandle = !implClass.getPackageName().equals(implInfo.getDeclaringClass().getPackageName())
+                                && !Modifier.isPublic(implInfo.getModifiers());
         cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
         int parameterCount = invokedType.parameterCount();
         if (parameterCount > 0) {
@@ -297,6 +305,23 @@
             }
         }
 
+        if (useImplMethodHandle) {
+            FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_STATIC,
+                                            NAME_FIELD_IMPL_METHOD,
+                                            DESCR_METHOD_HANDLE,
+                                            null, null);
+            fv.visitEnd();
+
+            mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC,
+                                "setImplMethod", DESCR_SET_IMPL_METHOD,
+                                null, null);
+            mv.visitVarInsn(ALOAD, 0);
+            mv.visitFieldInsn(PUTSTATIC, lambdaClassName, NAME_FIELD_IMPL_METHOD, DESCR_METHOD_HANDLE);
+            mv.visitInsn(RETURN);
+            mv.visitMaxs(-1, -1);
+            mv.visitEnd();
+        }
+
         if (isSerializable)
             generateSerializationFriendlyMethods();
         else if (accidentallySerializable)
@@ -322,9 +347,23 @@
         }
         try {
             // this class is linked at the indy callsite; so define a hidden nestmate
-            return caller.defineHiddenClass(classBytes, !disableEagerInitialization, NESTMATE).lookupClass();
+            Lookup lookup = caller.defineHiddenClass(classBytes, !disableEagerInitialization, NESTMATE);
+            if (useImplMethodHandle) {
+                // If the target class invokes a method reference this::m which is
+                // resolved to a protected method inherited from a superclass in a different
+                // package, the target class does not have a bridge and this method reference
+                // has been changed from public to protected after the target class was compiled.
+                // This lambda proxy class has no access to the resolved method.
+                // So this workaround by passing the live implMethod method handle
+                // to the proxy class to invoke directly.
+                MethodHandle mh = lookup.findStaticSetter(lookup.lookupClass(), NAME_FIELD_IMPL_METHOD, MethodHandle.class);
+                mh.invokeExact(implMethod);
+            }
+            return lookup.lookupClass();
         } catch (IllegalAccessException e) {
             throw new LambdaConversionException("Exception defining lambda proxy class", e);
+        } catch (Throwable t) {
+            throw new InternalError(t);
         }
     }
 
@@ -441,6 +480,10 @@
                 visitTypeInsn(NEW, implMethodClassName);
                 visitInsn(DUP);
             }
+            if (useImplMethodHandle) {
+                visitVarInsn(ALOAD, 0);
+                visitFieldInsn(GETSTATIC, lambdaClassName, NAME_FIELD_IMPL_METHOD, DESCR_METHOD_HANDLE);
+            }
             for (int i = 0; i < argNames.length; i++) {
                 visitVarInsn(ALOAD, 0);
                 visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]);
@@ -448,11 +491,16 @@
 
             convertArgumentTypes(methodType);
 
-            // Invoke the method we want to forward to
-            visitMethodInsn(invocationOpcode(), implMethodClassName,
-                            implMethodName, implMethodDesc,
-                            implClass.isInterface());
-
+            if (useImplMethodHandle) {
+                MethodType mtype = implInfo.getMethodType().insertParameterTypes(0, implClass);
+                visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle",
+                                "invokeExact", mtype.descriptorString(), false);
+            } else {
+                // Invoke the method we want to forward to
+                visitMethodInsn(invocationOpcode(), implMethodClassName,
+                                implMethodName, implMethodDesc,
+                                implClass.isInterface());
+            }
             // Convert the return value (if any) and return it
             // Note: if adapting from non-void to void, the 'return'
             // instruction will pop the unneeded result
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java	Fri Feb 21 14:22:32 2020 +0000
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java	Fri Feb 21 12:03:58 2020 -0800
@@ -1169,7 +1169,7 @@
                  */
                 String name = targetClass.getName() + "$$InjectedInvoker";
                 Class<?> invokerClass = new Lookup(targetClass)
-                        .makeHiddenClassDefiner(name, INJECTED_INVOKER_TEMPLATE).defineClass(true);
+                        .makeHiddenClassDefiner(name, INJECTED_INVOKER_TEMPLATE, 0).defineClass(true);
                 assert checkInjectedInvoker(targetClass, invokerClass);
                 return IMPL_LOOKUP.findStatic(invokerClass, "invoke_V", INVOKER_MT);
             } catch (ReflectiveOperationException ex) {
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Fri Feb 21 14:22:32 2020 +0000
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Fri Feb 21 12:03:58 2020 -0800
@@ -1824,8 +1824,7 @@
             }
 
             Set<ClassOption> opts = (options != null && options.length > 0) ? Set.of(options) : Set.of();
-            Class<?> c =  makeHiddenClassDefiner(bytes.clone(), opts).defineClass(initialize, null);
-            return new Lookup(c, null, FULL_POWER_MODES);
+            return makeHiddenClassDefiner(bytes.clone(), opts).defineClassAsLookup(initialize);
         }
 
         /**
@@ -1879,8 +1878,7 @@
             }
 
             Set<ClassOption> opts = (options != null && options.length > 0) ? Set.of(options) : Set.of();
-            Class<?> c = makeHiddenClassDefiner(bytes.clone(), opts).defineClass(initialize, classData);
-            return new Lookup(c, null, FULL_POWER_MODES);
+            return makeHiddenClassDefiner(bytes.clone(), opts).defineClassAsLookup(initialize, classData);
         }
 
         private ClassDefiner makeClassDefiner(byte[] bytes) {
@@ -1906,8 +1904,8 @@
          * @param bytes class bytes
          * @return ClassDefiner that defines a hidden class of the given bytes
          */
-        ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes) {
-            return new ClassDefiner(this, name, bytes, HIDDEN_CLASS);
+        ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes, int flags) {
+            return new ClassDefiner(this, name, bytes, HIDDEN_CLASS|flags);
         }
 
         static class ClassDefiner {
@@ -1948,6 +1946,11 @@
                 return defineClass(initialize, null);
             }
 
+            Lookup defineClassAsLookup(boolean initialize) {
+                Class<?> c = defineClass(initialize, null);
+                return new Lookup(c, null, FULL_POWER_MODES);
+            }
+
             /**
              * Defines the class of the given bytes and the given classData.
              * If {@code initialize} parameter is true, then the class will be initialized.
@@ -1967,6 +1970,11 @@
                 return c;
             }
 
+            Lookup defineClassAsLookup(boolean initialize, Object classData) {
+                Class<?> c = defineClass(initialize, classData);
+                return new Lookup(c, null, FULL_POWER_MODES);
+            }
+
             private boolean isNestmate() {
                 return (classFlags & NESTMATE_CLASS) != 0;
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/InheritedProtectedMethod.java	Fri Feb 21 12:03:58 2020 -0800
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020, 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 8239384
+ * @library /test/lib
+ * @build jdk.test.lib.Utils
+ *        jdk.test.lib.compiler.CompilerUtils
+ * @run testng/othervm InheritedProtectedMethod
+ * @summary Test method reference to a method inherited from its
+ *          superclass in a different package.  Such method's modifier
+ *          is changed from public to protected.
+ */
+
+import jdk.test.lib.compiler.CompilerUtils;
+import jdk.test.lib.Utils;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.testng.Assert.*;
+
+public class InheritedProtectedMethod {
+    private static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "src");
+    private static final Path CLASSES_DIR = Paths.get("classes");
+
+    @BeforeTest
+    static void setup() throws IOException {
+        assertTrue(CompilerUtils.compile(SRC_DIR, CLASSES_DIR));
+
+        // compile the modified version of MethodSupplierOuter.java
+        Path file = Paths.get(Utils.TEST_SRC, "modified", "MethodSupplierOuter.java");
+        assertTrue(CompilerUtils.compile(file, CLASSES_DIR));
+    }
+
+    @Test
+    public static void run() throws Exception {
+        URLClassLoader loader = new URLClassLoader("loader", new URL[]{ CLASSES_DIR.toUri().toURL()},
+                ClassLoader.getPlatformClassLoader());
+        Class<?> methodInvokeClass = Class.forName("MethodInvoker", false, loader);
+        Method invokeMethod = methodInvokeClass.getMethod("invoke");
+
+        String result = (String)invokeMethod.invoke(null);
+        assertEquals(result, "protected inherited method");
+    }
+}
--- a/test/jdk/java/lang/invoke/lambda/superProtectedMethod/SuperMethodTest.java	Fri Feb 21 14:22:32 2020 +0000
+++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/SuperMethodTest.java	Fri Feb 21 12:03:58 2020 -0800
@@ -63,4 +63,3 @@
         }
     }
 }
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/modified/MethodSupplierOuter.java	Fri Feb 21 12:03:58 2020 -0800
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+package anotherpkg;
+
+public class MethodSupplierOuter {
+    // MethodSupplier is "public" for javac compilation, modified to "protected" for test.
+    public static class MethodSupplier {
+        protected String m()
+        {
+            return "protected inherited method";
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MethodInvoker.java	Fri Feb 21 12:03:58 2020 -0800
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+import anotherpkg.MethodSupplierOuter;
+
+public class MethodInvoker extends MethodSupplierOuter.MethodSupplier  {
+    public static String invoke() {
+        MyFunctionalInterface fi = new MethodInvoker()::m;
+        return fi.invokeMethodReference();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MyFunctionalInterface.java	Fri Feb 21 12:03:58 2020 -0800
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+interface MyFunctionalInterface {
+    String invokeMethodReference();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/anotherpkg/MethodSupplierOuter.java	Fri Feb 21 12:03:58 2020 -0800
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+package anotherpkg;
+
+public class MethodSupplierOuter {
+    // MethodSupplier is "public" for javac compilation, modified to "protected" for test.
+    public static class MethodSupplier {
+        public String m()
+        {
+            return "public inherited method";
+        }
+    }
+}