8242451: ensure semantics of non-capturing lambdas are preserved independent of execution mode
Reviewed-by: mchung
This commit is contained in:
parent
dc1ef58351
commit
1b79326c05
src/java.base/share/classes/java/lang/invoke
test
jdk/java/lang/invoke/lambda
langtools/tools/javac/lambda
@ -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
|
||||
*/
|
||||
|
91
test/jdk/java/lang/invoke/lambda/LambdaEagerInitTest.java
Normal file
91
test/jdk/java/lang/invoke/lambda/LambdaEagerInitTest.java
Normal file
@ -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());
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user