8242451: ensure semantics of non-capturing lambdas are preserved independent of execution mode

Reviewed-by: mchung
This commit is contained in:
Gilles Duboscq 2020-09-25 10:10:36 +00:00
parent dc1ef58351
commit 1b79326c05
4 changed files with 165 additions and 57 deletions
src/java.base/share/classes/java/lang/invoke
test
jdk/java/lang/invoke/lambda
langtools/tools/javac/lambda
lambdaExpression
methodReference

@ -57,6 +57,7 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE);
private static final String JAVA_LANG_OBJECT = "java/lang/Object";
private static final String NAME_CTOR = "<init>";
private static final String LAMBDA_INSTANCE_FIELD = "LAMBDA_INSTANCE$";
//Serialization support
private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda";
@ -206,32 +207,42 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
@Override
CallSite buildCallSite() throws LambdaConversionException {
final Class<?> innerClass = spinInnerClass();
if (invokedType.parameterCount() == 0 && !disableEagerInitialization) {
if (invokedType.parameterCount() == 0) {
// In the case of a non-capturing lambda, we optimize linkage by pre-computing a single instance,
// unless we've suppressed eager initialization
final Constructor<?>[] ctrs = AccessController.doPrivileged(
new PrivilegedAction<>() {
@Override
public Constructor<?>[] run() {
Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
if (ctrs.length == 1) {
// The lambda implementing inner class constructor is private, set
// it accessible (by us) before creating the constant sole instance
ctrs[0].setAccessible(true);
}
return ctrs;
if (disableEagerInitialization) {
try {
return new ConstantCallSite(caller.findStaticGetter(innerClass, LAMBDA_INSTANCE_FIELD,
invokedType.returnType()));
} catch (ReflectiveOperationException e) {
throw new LambdaConversionException(
"Exception finding " + LAMBDA_INSTANCE_FIELD + " static field", e);
}
} else {
final Constructor<?>[] ctrs = AccessController.doPrivileged(
new PrivilegedAction<>() {
@Override
public Constructor<?>[] run() {
Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
if (ctrs.length == 1) {
// The lambda implementing inner class constructor is private, set
// it accessible (by us) before creating the constant sole instance
ctrs[0].setAccessible(true);
}
return ctrs;
}
});
if (ctrs.length != 1) {
throw new LambdaConversionException("Expected one lambda constructor for "
+ innerClass.getCanonicalName() + ", got " + ctrs.length);
}
});
if (ctrs.length != 1) {
throw new LambdaConversionException("Expected one lambda constructor for "
+ innerClass.getCanonicalName() + ", got " + ctrs.length);
}
try {
Object inst = ctrs[0].newInstance();
return new ConstantCallSite(MethodHandles.constant(samBase, inst));
} catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception instantiating lambda object", e);
try {
Object inst = ctrs[0].newInstance();
return new ConstantCallSite(MethodHandles.constant(samBase, inst));
} catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception instantiating lambda object", e);
}
}
} else {
try {
@ -331,6 +342,10 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
generateConstructor();
if (invokedType.parameterCount() == 0 && disableEagerInitialization) {
generateClassInitializer();
}
// Forward the SAM method
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
samMethodType.toMethodDescriptorString(), null, null);
@ -398,6 +413,32 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
}
}
/**
* Generate a static field and a static initializer that sets this field to an instance of the lambda
*/
private void generateClassInitializer() {
String lambdaTypeDescriptor = invokedType.returnType().descriptorString();
// Generate the static final field that holds the lambda singleton
FieldVisitor fv = cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL,
LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor, null, null);
fv.visitEnd();
// Instantiate the lambda and store it to the static final field
MethodVisitor clinit = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
clinit.visitCode();
clinit.visitTypeInsn(NEW, lambdaClassName);
clinit.visitInsn(Opcodes.DUP);
assert invokedType.parameterCount() == 0;
clinit.visitMethodInsn(INVOKESPECIAL, lambdaClassName, NAME_CTOR, constructorType.toMethodDescriptorString(), false);
clinit.visitFieldInsn(PUTSTATIC, lambdaClassName, LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor);
clinit.visitInsn(RETURN);
clinit.visitMaxs(-1, -1);
clinit.visitEnd();
}
/**
* Generate the constructor for the class
*/

@ -0,0 +1,91 @@
/*
* 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 8242451
* @library /test/lib
* @summary Test that the LAMBDA_INSTANCE$ field is present depending
* on disableEagerInitialization
* @run main LambdaEagerInitTest
* @run main/othervm -Djdk.internal.lambda.disableEagerInitialization=true LambdaEagerInitTest
*/
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import static jdk.test.lib.Asserts.*;
public class LambdaEagerInitTest {
interface H {Object m(String s);}
private static Set<String> allowedStaticFields(boolean nonCapturing) {
Set<String> s = new HashSet<>();
if (Boolean.getBoolean("jdk.internal.lambda.disableEagerInitialization")) {
if (nonCapturing) s.add("LAMBDA_INSTANCE$");
}
return s;
}
private void nonCapturingLambda() {
H la = s -> s;
assertEquals("hi", la.m("hi"));
Class<? extends H> c1 = la.getClass();
verifyLambdaClass(la.getClass(), true);
}
private void capturingLambda() {
H la = s -> concat(s, "foo");
assertEquals("hi foo", la.m("hi"));
verifyLambdaClass(la.getClass(), false);
}
private void verifyLambdaClass(Class<?> c, boolean nonCapturing) {
Set<String> staticFields = new HashSet<>();
Set<String> instanceFields = new HashSet<>();
for (Field f : c.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers())) {
staticFields.add(f.getName());
} else {
instanceFields.add(f.getName());
}
}
assertEquals(instanceFields.size(), nonCapturing ? 0 : 1, "Unexpected instance fields");
assertEquals(staticFields, allowedStaticFields(nonCapturing), "Unexpected static fields");
}
private String concat(String... ss) {
return Arrays.stream(ss).collect(Collectors.joining(" "));
}
public static void main(String[] args) {
LambdaEagerInitTest test = new LambdaEagerInitTest();
test.nonCapturingLambda();
test.capturingLambda();
}
}

@ -26,11 +26,9 @@
* @bug 8003280
* @summary Add lambda tests
* Test bridge methods for certain SAM conversions
* Tests that jdk.internal.lambda.disableEagerInitialization=true creates a
* get$Lambda method for non-capturing lambdas
* Test the set of generated methods
* @compile LambdaTest6.java
* @run main LambdaTest6
* @run main/othervm -Djdk.internal.lambda.disableEagerInitialization=true LambdaTest6
*/
import java.lang.reflect.Method;

@ -26,15 +26,11 @@
* @bug 8003280
* @summary Add lambda tests
* Test bridge methods in certain SAM conversion
* Tests that jdk.internal.lambda.disableEagerInitialization=true creates a
* get$Lambda method for non-capturing lambdas
* @compile BridgeMethod.java
* @run main BridgeMethod
* @run main/othervm -Djdk.internal.lambda.disableEagerInitialization=true BridgeMethod
*/
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@ -72,35 +68,19 @@ public class BridgeMethod {
return s;
}
private static Set<String> allowedMethods() {
Set<String> s = new HashSet<>();
s.add("m");
return s;
}
private static boolean matchingMethodNames(Method[] methods) {
Set<String> methodNames = new HashSet<>();
for (Method m : methods) {
methodNames.add(m.getName());
}
return methodNames.equals(allowedMethods());
}
public static void main(String[] args) {
L la = BridgeMethod::bar; //static reference
la.m("hi");
Class<? extends L> c1 = la.getClass();
Method[] methods = c1.getDeclaredMethods();
assertTrue(matchingMethodNames(methods));
Set<String> types = setOfStringObject();
System.out.println("methods in SAM conversion of L:");
for(Method m : methods) {
if (m.getName().equals("m")) {
System.out.println(m.toGenericString());
Class[] parameterTypes = m.getParameterTypes();
assertTrue(parameterTypes.length == 1);
assertTrue(types.remove(parameterTypes[0].getName()));
}
assertTrue(m.getName().equals("m"));
System.out.println(m.toGenericString());
Class[] parameterTypes = m.getParameterTypes();
assertTrue(parameterTypes.length == 1);
assertTrue(types.remove(parameterTypes[0].getName()));
}
assertTrue(types.isEmpty() || (types.size() == 1 && types.contains("java.lang.String")));
@ -108,16 +88,14 @@ public class BridgeMethod {
//km.m("hi"); //will be uncommented when CR7028808 fixed
Class<? extends KM> c2 = km.getClass();
methods = c2.getDeclaredMethods();
assertTrue(matchingMethodNames(methods));
types = setOfStringObject();
System.out.println("methods in SAM conversion of KM:");
for(Method m : methods) {
if (m.getName().equals("m")) {
System.out.println(m.toGenericString());
Class<?>[] parameterTypes = m.getParameterTypes();
assertTrue(parameterTypes.length == 1);
assertTrue(types.remove(parameterTypes[0].getName()));
}
assertTrue(m.getName().equals("m"));
System.out.println(m.toGenericString());
Class<?>[] parameterTypes = m.getParameterTypes();
assertTrue(parameterTypes.length == 1);
assertTrue(types.remove(parameterTypes[0].getName()));
}
assertTrue(types.isEmpty());