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
@ -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 METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE);
|
||||||
private static final String JAVA_LANG_OBJECT = "java/lang/Object";
|
private static final String JAVA_LANG_OBJECT = "java/lang/Object";
|
||||||
private static final String NAME_CTOR = "<init>";
|
private static final String NAME_CTOR = "<init>";
|
||||||
|
private static final String LAMBDA_INSTANCE_FIELD = "LAMBDA_INSTANCE$";
|
||||||
|
|
||||||
//Serialization support
|
//Serialization support
|
||||||
private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda";
|
private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda";
|
||||||
@ -206,32 +207,42 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
|||||||
@Override
|
@Override
|
||||||
CallSite buildCallSite() throws LambdaConversionException {
|
CallSite buildCallSite() throws LambdaConversionException {
|
||||||
final Class<?> innerClass = spinInnerClass();
|
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,
|
// In the case of a non-capturing lambda, we optimize linkage by pre-computing a single instance,
|
||||||
// unless we've suppressed eager initialization
|
// unless we've suppressed eager initialization
|
||||||
final Constructor<?>[] ctrs = AccessController.doPrivileged(
|
if (disableEagerInitialization) {
|
||||||
new PrivilegedAction<>() {
|
try {
|
||||||
@Override
|
return new ConstantCallSite(caller.findStaticGetter(innerClass, LAMBDA_INSTANCE_FIELD,
|
||||||
public Constructor<?>[] run() {
|
invokedType.returnType()));
|
||||||
Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
|
} catch (ReflectiveOperationException e) {
|
||||||
if (ctrs.length == 1) {
|
throw new LambdaConversionException(
|
||||||
// The lambda implementing inner class constructor is private, set
|
"Exception finding " + LAMBDA_INSTANCE_FIELD + " static field", e);
|
||||||
// it accessible (by us) before creating the constant sole instance
|
}
|
||||||
ctrs[0].setAccessible(true);
|
} else {
|
||||||
}
|
final Constructor<?>[] ctrs = AccessController.doPrivileged(
|
||||||
return ctrs;
|
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 {
|
try {
|
||||||
Object inst = ctrs[0].newInstance();
|
Object inst = ctrs[0].newInstance();
|
||||||
return new ConstantCallSite(MethodHandles.constant(samBase, inst));
|
return new ConstantCallSite(MethodHandles.constant(samBase, inst));
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
throw new LambdaConversionException("Exception instantiating lambda object", e);
|
throw new LambdaConversionException("Exception instantiating lambda object", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
@ -331,6 +342,10 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
|||||||
|
|
||||||
generateConstructor();
|
generateConstructor();
|
||||||
|
|
||||||
|
if (invokedType.parameterCount() == 0 && disableEagerInitialization) {
|
||||||
|
generateClassInitializer();
|
||||||
|
}
|
||||||
|
|
||||||
// Forward the SAM method
|
// Forward the SAM method
|
||||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
|
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
|
||||||
samMethodType.toMethodDescriptorString(), null, null);
|
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
|
* 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
|
* @bug 8003280
|
||||||
* @summary Add lambda tests
|
* @summary Add lambda tests
|
||||||
* Test bridge methods for certain SAM conversions
|
* Test bridge methods for certain SAM conversions
|
||||||
* Tests that jdk.internal.lambda.disableEagerInitialization=true creates a
|
* Test the set of generated methods
|
||||||
* get$Lambda method for non-capturing lambdas
|
|
||||||
* @compile LambdaTest6.java
|
* @compile LambdaTest6.java
|
||||||
* @run main LambdaTest6
|
* @run main LambdaTest6
|
||||||
* @run main/othervm -Djdk.internal.lambda.disableEagerInitialization=true LambdaTest6
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
@ -26,15 +26,11 @@
|
|||||||
* @bug 8003280
|
* @bug 8003280
|
||||||
* @summary Add lambda tests
|
* @summary Add lambda tests
|
||||||
* Test bridge methods in certain SAM conversion
|
* 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
|
* @compile BridgeMethod.java
|
||||||
* @run main BridgeMethod
|
* @run main BridgeMethod
|
||||||
* @run main/othervm -Djdk.internal.lambda.disableEagerInitialization=true BridgeMethod
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -72,35 +68,19 @@ public class BridgeMethod {
|
|||||||
return s;
|
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) {
|
public static void main(String[] args) {
|
||||||
L la = BridgeMethod::bar; //static reference
|
L la = BridgeMethod::bar; //static reference
|
||||||
la.m("hi");
|
la.m("hi");
|
||||||
Class<? extends L> c1 = la.getClass();
|
Class<? extends L> c1 = la.getClass();
|
||||||
Method[] methods = c1.getDeclaredMethods();
|
Method[] methods = c1.getDeclaredMethods();
|
||||||
assertTrue(matchingMethodNames(methods));
|
|
||||||
Set<String> types = setOfStringObject();
|
Set<String> types = setOfStringObject();
|
||||||
System.out.println("methods in SAM conversion of L:");
|
System.out.println("methods in SAM conversion of L:");
|
||||||
for(Method m : methods) {
|
for(Method m : methods) {
|
||||||
if (m.getName().equals("m")) {
|
assertTrue(m.getName().equals("m"));
|
||||||
System.out.println(m.toGenericString());
|
System.out.println(m.toGenericString());
|
||||||
Class[] parameterTypes = m.getParameterTypes();
|
Class[] parameterTypes = m.getParameterTypes();
|
||||||
assertTrue(parameterTypes.length == 1);
|
assertTrue(parameterTypes.length == 1);
|
||||||
assertTrue(types.remove(parameterTypes[0].getName()));
|
assertTrue(types.remove(parameterTypes[0].getName()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assertTrue(types.isEmpty() || (types.size() == 1 && types.contains("java.lang.String")));
|
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
|
//km.m("hi"); //will be uncommented when CR7028808 fixed
|
||||||
Class<? extends KM> c2 = km.getClass();
|
Class<? extends KM> c2 = km.getClass();
|
||||||
methods = c2.getDeclaredMethods();
|
methods = c2.getDeclaredMethods();
|
||||||
assertTrue(matchingMethodNames(methods));
|
|
||||||
types = setOfStringObject();
|
types = setOfStringObject();
|
||||||
System.out.println("methods in SAM conversion of KM:");
|
System.out.println("methods in SAM conversion of KM:");
|
||||||
for(Method m : methods) {
|
for(Method m : methods) {
|
||||||
if (m.getName().equals("m")) {
|
assertTrue(m.getName().equals("m"));
|
||||||
System.out.println(m.toGenericString());
|
System.out.println(m.toGenericString());
|
||||||
Class<?>[] parameterTypes = m.getParameterTypes();
|
Class<?>[] parameterTypes = m.getParameterTypes();
|
||||||
assertTrue(parameterTypes.length == 1);
|
assertTrue(parameterTypes.length == 1);
|
||||||
assertTrue(types.remove(parameterTypes[0].getName()));
|
assertTrue(types.remove(parameterTypes[0].getName()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assertTrue(types.isEmpty());
|
assertTrue(types.isEmpty());
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user