Skip to content

Commit c2ce091

Browse files
committed
Lambda functions serialization added
1 parent 0550f75 commit c2ce091

File tree

9 files changed

+164
-46
lines changed

9 files changed

+164
-46
lines changed

compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/LambdaUtils.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
public final class LambdaUtils {
5252
private static final Pattern LAMBDA_PATTERN = Pattern.compile("\\$\\$Lambda\\$\\d+[/\\.][^/]+;");
5353
private static final char[] HEX = "0123456789abcdef".toCharArray();
54+
public static final String SPLIT_BY_LAMBDA = "\\$\\$Lambda\\$";
55+
public static final String LAMBDA_FUNCTIONS_NAME_PATTERN = "$$Lambda$";
5456

5557
private static GraphBuilderConfiguration buildLambdaParserConfig(ClassInitializationPlugin cip) {
5658
GraphBuilderConfiguration.Plugins plugins = new GraphBuilderConfiguration.Plugins(new InvocationPlugins());
@@ -107,7 +109,7 @@ public static String findStableLambdaName(ClassInitializationPlugin cip, Provide
107109

108110
public static boolean isLambdaType(ResolvedJavaType type) {
109111
String typeName = type.getName();
110-
return type.isFinalFlagSet() && typeName.contains("/") && typeName.contains("$$Lambda$") && lambdaMatcher(type.getName()).find();
112+
return type.isFinalFlagSet() && typeName.contains("/") && typeName.contains(LAMBDA_FUNCTIONS_NAME_PATTERN) && lambdaMatcher(type.getName()).find();
111113
}
112114

113115
private static String createStableLambdaName(ResolvedJavaType lambdaType, List<ResolvedJavaMethod> targetMethods) {

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,40 @@ private static JNIObjectHandle shouldIncludeMethod(JNIEnvironment jni, JNIObject
976976
return result;
977977
}
978978

979+
/*
980+
* We have to find a class that captures a lambda function so it can be registered by the agent.
981+
* We have to get SerializedLambda instance first. After that we get lambda capturing class from
982+
* that instance using JNIHandleSet#getFieldId to get field id and JNIObjectHandle#invoke on to
983+
* get that field value. We get name of the capturing class and tell the agent to register it.
984+
*/
985+
private static boolean registerCapturingClass(JNIEnvironment jni, Breakpoint bp, InterceptedState state) {
986+
JNIObjectHandle serializedLambdaInstance = getObjectArgument(0);
987+
JNIFieldId getCapturingClassFieldId = NativeImageAgent.singleton().handles().getFieldId(jni, bp.clazz, "capturingClass", "Ljava/lang/Class;", false);
988+
JNIObjectHandle capturingClass = jniFunctions().getGetObjectField().invoke(jni, serializedLambdaInstance, getCapturingClassFieldId);
989+
990+
String capturingClassName = getClassNameOrNull(jni, capturingClass);
991+
992+
boolean validObjectStreamClassInstance = nullHandle().notEqual(capturingClass);
993+
if (clearException(jni)) {
994+
validObjectStreamClassInstance = false;
995+
}
996+
997+
if (tracer != null) {
998+
tracer.traceCall("serialization",
999+
"SerializedLambda.readResolve",
1000+
null,
1001+
null,
1002+
null,
1003+
validObjectStreamClassInstance,
1004+
state.getFullStackTraceOrNull(),
1005+
/*- String serializationTargetClass, String customTargetConstructorClass */
1006+
capturingClassName, null);
1007+
1008+
guarantee(!testException(jni));
1009+
}
1010+
return true;
1011+
}
1012+
9791013
private static boolean objectStreamClassConstructor(JNIEnvironment jni, Breakpoint bp, InterceptedState state) {
9801014

9811015
JNIObjectHandle serializeTargetClass = getObjectArgument(1);
@@ -987,11 +1021,6 @@ private static boolean objectStreamClassConstructor(JNIEnvironment jni, Breakpoi
9871021
validObjectStreamClassInstance = false;
9881022
}
9891023

990-
// Skip Lambda class serialization
991-
if (serializeTargetClassName.contains("$$Lambda$")) {
992-
return true;
993-
}
994-
9951024
List<String> transitiveSerializeTargets = new ArrayList<>();
9961025
transitiveSerializeTargets.add(serializeTargetClassName);
9971026

@@ -1000,8 +1029,10 @@ private static boolean objectStreamClassConstructor(JNIEnvironment jni, Breakpoi
10001029
* additional ObjectStreamClass instances (usually the super classes) are created
10011030
* recursively. Call ObjectStreamClass.getClassDataLayout0() can get all of them.
10021031
*/
1032+
10031033
JNIMethodId getClassDataLayout0MId = agent.handles().getJavaIoObjectStreamClassGetClassDataLayout0(jni, bp.clazz);
10041034
JNIObjectHandle dataLayoutArray = callObjectMethod(jni, objectStreamClassInstance, getClassDataLayout0MId);
1035+
10051036
if (!clearException(jni) && nullHandle().notEqual(dataLayoutArray)) {
10061037
int length = jniFunctions().getGetArrayLength().invoke(jni, dataLayoutArray);
10071038
// If only 1 element is got from getClassDataLayout0(). it is base ObjectStreamClass
@@ -1024,6 +1055,7 @@ private static boolean objectStreamClassConstructor(JNIEnvironment jni, Breakpoi
10241055
}
10251056
}
10261057
}
1058+
10271059
for (String className : transitiveSerializeTargets) {
10281060
if (tracer != null) {
10291061
tracer.traceCall("serialization",
@@ -1054,11 +1086,6 @@ private static boolean customTargetConstructorSerialization(JNIEnvironment jni,
10541086
JNIObjectHandle serializeTargetClass = getObjectArgument(1);
10551087
String serializeTargetClassName = getClassNameOrNull(jni, serializeTargetClass);
10561088

1057-
// Skip Lambda class serialization.
1058-
if (serializeTargetClassName.contains("$$Lambda$")) {
1059-
return true;
1060-
}
1061-
10621089
JNIObjectHandle customConstructorObj = getObjectArgument(2);
10631090
JNIObjectHandle customConstructorClass = jniFunctions().getGetObjectClass().invoke(jni, customConstructorObj);
10641091
JNIMethodId getDeclaringClassNameMethodID = agent.handles().getJavaLangReflectConstructorDeclaringClassName(jni, customConstructorClass);
@@ -1380,6 +1407,7 @@ private interface BreakpointHandler {
13801407
brk("java/lang/reflect/Proxy", "newProxyInstance",
13811408
"(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;", BreakpointInterceptor::newProxyInstance),
13821409

1410+
brk("java/lang/invoke/SerializedLambda", "readResolve", "()Ljava/lang/Object;", BreakpointInterceptor::registerCapturingClass),
13831411
brk("java/io/ObjectStreamClass", "<init>", "(Ljava/lang/Class;)V", BreakpointInterceptor::objectStreamClassConstructor),
13841412
brk(Package_jdk_internal_reflect.getQualifiedName().replace(".", "/") + "/ReflectionFactory",
13851413
"newConstructorForSerialization",

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Set;
3333
import java.util.concurrent.ConcurrentHashMap;
3434

35+
import org.graalvm.compiler.java.LambdaUtils;
3536
import org.graalvm.nativeimage.impl.ConfigurationCondition;
3637
import org.graalvm.nativeimage.impl.RuntimeSerializationSupport;
3738

@@ -53,8 +54,12 @@ public void removeAll(SerializationConfiguration other) {
5354
serializations.removeAll(other.serializations);
5455
}
5556

56-
public boolean contains(ConfigurationCondition condition, String serializationTargetClass, String customTargetConstructorClass) {
57-
return serializations.contains(createConfigurationType(condition, serializationTargetClass, customTargetConstructorClass));
57+
public boolean contains(ConfigurationCondition condition, String rawSerializationTargetClass, String customTargetConstructorClass) {
58+
String serializationTargetClass = rawSerializationTargetClass.contains(LambdaUtils.LAMBDA_FUNCTIONS_NAME_PATTERN)
59+
? rawSerializationTargetClass.split(LambdaUtils.SPLIT_BY_LAMBDA)[0] + LambdaUtils.LAMBDA_FUNCTIONS_NAME_PATTERN
60+
: rawSerializationTargetClass;
61+
return serializations.contains(createConfigurationType(condition, serializationTargetClass, customTargetConstructorClass,
62+
serializationTargetClass.contains(LambdaUtils.LAMBDA_FUNCTIONS_NAME_PATTERN)));
5863
}
5964

6065
@Override
@@ -85,18 +90,20 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition,
8590
}
8691

8792
@Override
88-
public void registerWithTargetConstructorClass(ConfigurationCondition condition, String className, String customTargetConstructorClassName) {
89-
serializations.add(createConfigurationType(condition, className, customTargetConstructorClassName));
93+
public void registerWithTargetConstructorClass(ConfigurationCondition condition, String rawClassName, String customTargetConstructorClassName) {
94+
String className = rawClassName.contains(LambdaUtils.LAMBDA_FUNCTIONS_NAME_PATTERN) ? rawClassName.split(LambdaUtils.SPLIT_BY_LAMBDA)[0] + LambdaUtils.LAMBDA_FUNCTIONS_NAME_PATTERN
95+
: rawClassName;
96+
serializations.add(createConfigurationType(condition, className, customTargetConstructorClassName, rawClassName.contains(LambdaUtils.LAMBDA_FUNCTIONS_NAME_PATTERN)));
9097
}
9198

9299
@Override
93100
public boolean isEmpty() {
94101
return serializations.isEmpty();
95102
}
96103

97-
private static SerializationConfigurationType createConfigurationType(ConfigurationCondition condition, String className, String customTargetConstructorClassName) {
104+
private static SerializationConfigurationType createConfigurationType(ConfigurationCondition condition, String className, String customTargetConstructorClassName, boolean isLambdaCapturingClass) {
98105
String convertedClassName = SignatureUtil.toInternalClassName(className);
99106
String convertedCustomTargetConstructorClassName = customTargetConstructorClassName == null ? null : SignatureUtil.toInternalClassName(customTargetConstructorClassName);
100-
return new SerializationConfigurationType(condition, convertedClassName, convertedCustomTargetConstructorClassName);
107+
return new SerializationConfigurationType(condition, convertedClassName, convertedCustomTargetConstructorClassName, isLambdaCapturingClass);
101108
}
102109
}

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Comparator;
2929
import java.util.Objects;
3030

31+
import org.graalvm.compiler.java.LambdaUtils;
3132
import org.graalvm.nativeimage.impl.ConfigurationCondition;
3233

3334
import com.oracle.svm.configure.json.JsonPrintable;
@@ -38,8 +39,9 @@ public class SerializationConfigurationType implements JsonPrintable, Comparable
3839
private final ConfigurationCondition condition;
3940
private final String qualifiedJavaName;
4041
private final String qualifiedCustomTargetConstructorJavaName;
42+
private final boolean isLambdaCapturingClass;
4143

42-
public SerializationConfigurationType(ConfigurationCondition condition, String qualifiedJavaName, String qualifiedCustomTargetConstructorJavaName) {
44+
public SerializationConfigurationType(ConfigurationCondition condition, String qualifiedJavaName, String qualifiedCustomTargetConstructorJavaName, boolean isLambdaCapturingClass) {
4345
assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation";
4446
assert !qualifiedJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]";
4547
assert qualifiedCustomTargetConstructorJavaName == null || qualifiedCustomTargetConstructorJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation";
@@ -49,13 +51,21 @@ public SerializationConfigurationType(ConfigurationCondition condition, String q
4951
Objects.requireNonNull(qualifiedJavaName);
5052
this.qualifiedJavaName = qualifiedJavaName;
5153
this.qualifiedCustomTargetConstructorJavaName = qualifiedCustomTargetConstructorJavaName;
54+
this.isLambdaCapturingClass = isLambdaCapturingClass;
5255
}
5356

5457
@Override
5558
public void printJson(JsonWriter writer) throws IOException {
5659
writer.append('{').indent().newline();
5760
ConfigurationConditionPrintable.printConditionAttribute(condition, writer);
58-
writer.quote(SerializationConfigurationParser.NAME_KEY).append(':').quote(qualifiedJavaName);
61+
62+
if (isLambdaCapturingClass) {
63+
String capturingClass = qualifiedJavaName.split(LambdaUtils.SPLIT_BY_LAMBDA)[0];
64+
writer.quote(SerializationConfigurationParser.LAMBDA_CAPTURING_CLASS_KEY).append(":").quote(capturingClass);
65+
} else {
66+
writer.quote(SerializationConfigurationParser.NAME_KEY).append(':').quote(qualifiedJavaName);
67+
}
68+
5969
if (qualifiedCustomTargetConstructorJavaName != null) {
6070
writer.append(',').newline();
6171
writer.quote(SerializationConfigurationParser.CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY).append(':')

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void processEntry(Map<String, ?> entry) {
5454
}
5555
String function = (String) entry.get("function");
5656
List<?> args = (List<?>) entry.get("args");
57-
if ("ObjectStreamClass.<init>".equals(function)) {
57+
if ("ObjectStreamClass.<init>".equals(function) || "SerializedLambda.readResolve".equals(function)) {
5858
expectSize(args, 2);
5959

6060
if (advisor.shouldIgnore(LazyValueUtils.lazyValue((String) args.get(0)), LazyValueUtils.lazyValue(null))) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,39 @@ protected static Map<String, Object> asMap(Object data, String errorMessage) {
7474
throw new JSONParserException(errorMessage);
7575
}
7676

77+
protected void checkExclusiveOptionsAttributes(Map<String, Object> map, String type, Collection<String> requiredAttrs, Collection<String> optionalAttrs) {
78+
Set<String> unseenRequired = new HashSet<>(requiredAttrs);
79+
for (String attribute : map.keySet()) {
80+
if (unseenRequired.contains(attribute)) {
81+
unseenRequired.removeAll(requiredAttrs);
82+
break;
83+
}
84+
}
85+
86+
checkRemainingAttributes(map, type, requiredAttrs, optionalAttrs, unseenRequired);
87+
}
88+
7789
protected void checkAttributes(Map<String, Object> map, String type, Collection<String> requiredAttrs, Collection<String> optionalAttrs) {
7890
Set<String> unseenRequired = new HashSet<>(requiredAttrs);
7991
unseenRequired.removeAll(map.keySet());
92+
checkRemainingAttributes(map, type, requiredAttrs, optionalAttrs, unseenRequired);
93+
}
94+
95+
protected void warnOrFail(String message) {
96+
if (strictConfiguration) {
97+
throw new JSONParserException(message);
98+
} else {
99+
// Checkstyle: stop
100+
System.err.println("Warning: " + message);
101+
// Checkstyle: resume
102+
}
103+
}
104+
105+
protected void checkAttributes(Map<String, Object> map, String type, Collection<String> requiredAttrs) {
106+
checkAttributes(map, type, requiredAttrs, Collections.emptyList());
107+
}
108+
109+
private void checkRemainingAttributes(Map<String, Object> map, String type, Collection<String> requiredAttrs, Collection<String> optionalAttrs, Set<String> unseenRequired) {
80110
if (!unseenRequired.isEmpty()) {
81111
throw new JSONParserException("Missing attribute(s) [" + String.join(", ", unseenRequired) + "] in " + type);
82112
}
@@ -96,20 +126,6 @@ protected void checkAttributes(Map<String, Object> map, String type, Collection<
96126
}
97127
}
98128

99-
protected void warnOrFail(String message) {
100-
if (strictConfiguration) {
101-
throw new JSONParserException(message);
102-
} else {
103-
// Checkstyle: stop
104-
System.err.println("Warning: " + message);
105-
// Checkstyle: resume
106-
}
107-
}
108-
109-
protected void checkAttributes(Map<String, Object> map, String type, Collection<String> requiredAttrs) {
110-
checkAttributes(map, type, requiredAttrs, Collections.emptyList());
111-
}
112-
113129
protected static String asString(Object value) {
114130
if (value instanceof String) {
115131
return (String) value;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.Collections;
3232
import java.util.Map;
3333

34+
import org.graalvm.compiler.java.LambdaUtils;
3435
import org.graalvm.nativeimage.impl.ConfigurationCondition;
3536
import org.graalvm.nativeimage.impl.RuntimeSerializationSupport;
3637

@@ -39,6 +40,7 @@
3940
public class SerializationConfigurationParser extends ConfigurationParser {
4041

4142
public static final String NAME_KEY = "name";
43+
public static final String LAMBDA_CAPTURING_CLASS_KEY = "lambdaCapturingClass";
4244
public static final String CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY = "customTargetConstructorClass";
4345

4446
private final RuntimeSerializationSupport serializationSupport;
@@ -58,9 +60,14 @@ public void parseAndRegister(Reader reader) throws IOException {
5860
}
5961

6062
private void parseSerializationDescriptorObject(Map<String, Object> data) {
61-
checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY));
63+
checkExclusiveOptionsAttributes(data, "serialization descriptor object", Collections.unmodifiableCollection(Arrays.asList(NAME_KEY, LAMBDA_CAPTURING_CLASS_KEY)),
64+
Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY));
65+
66+
assert !data.containsKey(NAME_KEY) || !data.containsKey(LAMBDA_CAPTURING_CLASS_KEY) : "Object in serialization-config.json cannot have both \"name\" and \"lambdaCapturingClass\" " +
67+
"populated at the same time.";
68+
6269
ConfigurationCondition unresolvedCondition = parseCondition(data);
63-
String targetSerializationClass = asString(data.get(NAME_KEY));
70+
String targetSerializationClass = data.containsKey(NAME_KEY) ? asString(data.get(NAME_KEY)) : asString(data.get(LAMBDA_CAPTURING_CLASS_KEY)) + LambdaUtils.LAMBDA_FUNCTIONS_NAME_PATTERN;
6471
Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY);
6572
String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null;
6673

0 commit comments

Comments
 (0)