Skip to content

Commit b455f23

Browse files
committed
Support for lambda class serialization in Native Image
1 parent 47d1fb1 commit b455f23

File tree

20 files changed

+676
-149
lines changed

20 files changed

+676
-149
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 LAMBDA_SPLIT_PATTERN = "\\$\\$Lambda\\$";
55+
public static final String LAMBDA_CLASS_NAME_SUBSTRING = "$$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_CLASS_NAME_SUBSTRING) && lambdaMatcher(type.getName()).find();
111113
}
112114

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

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -50,4 +50,5 @@ public interface RuntimeSerializationSupport {
5050

5151
void registerWithTargetConstructorClass(ConfigurationCondition condition, String className, String customTargetConstructorClassName);
5252

53+
void registerLambdaCapturingClass(ConfigurationCondition condition, String lambdaCapturingClassName);
5354
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/CallTreePrinter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import jdk.vm.ci.meta.JavaKind;
6767
import jdk.vm.ci.meta.ResolvedJavaMethod;
6868
import jdk.vm.ci.meta.ResolvedJavaType;
69+
import org.graalvm.compiler.java.LambdaUtils;
6970

7071
public final class CallTreePrinter {
7172

@@ -311,7 +312,7 @@ public Set<String> classesSet(boolean packageNameOnly) {
311312
String name = method.getDeclaringClass().toJavaName(true);
312313
if (packageNameOnly) {
313314
name = packagePrefix(name);
314-
if (name.contains("$$Lambda$")) {
315+
if (name.contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)) {
315316
/* Also strip synthetic package names added for lambdas. */
316317
name = packagePrefix(name);
317318
}

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

Lines changed: 108 additions & 107 deletions
Large diffs are not rendered by default.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
8181
private JNIFieldId javaUtilResourceBundleParentField;
8282
private JNIMethodId javaUtilResourceBundleGetLocale;
8383

84+
final JNIFieldId javaLangInvokeSerializedLambdaCapturingClass;
85+
8486
NativeImageAgentJNIHandleSet(JNIEnvironment env) {
8587
super(env);
8688
javaLangClass = newClassGlobalRef(env, "java/lang/Class");
@@ -113,6 +115,9 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
113115
javaLangIllegalAccessError = newClassGlobalRef(env, "java/lang/IllegalAccessError");
114116
javaLangInvokeWrongMethodTypeException = newClassGlobalRef(env, "java/lang/invoke/WrongMethodTypeException");
115117
javaLangIllegalArgumentException = newClassGlobalRef(env, "java/lang/IllegalArgumentException");
118+
119+
JNIObjectHandle serializedLambda = findClass(env, "java/lang/invoke/SerializedLambda");
120+
javaLangInvokeSerializedLambdaCapturingClass = getFieldId(env, serializedLambda, "capturingClass", "Ljava/lang/Class;", false);
116121
}
117122

118123
JNIMethodId getJavaLangReflectExecutableGetParameterTypes(JNIEnvironment env) {

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

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

35+
import com.oracle.svm.configure.json.JsonPrintable;
36+
import org.graalvm.compiler.java.LambdaUtils;
3537
import org.graalvm.nativeimage.impl.ConfigurationCondition;
3638
import org.graalvm.nativeimage.impl.RuntimeSerializationSupport;
3739

@@ -41,35 +43,59 @@
4143
public class SerializationConfiguration implements ConfigurationBase, RuntimeSerializationSupport {
4244

4345
private final Set<SerializationConfigurationType> serializations = ConcurrentHashMap.newKeySet();
46+
private final Set<SerializationConfigurationLambdaCapturingType> lambdaSerializationCapturingTypes = ConcurrentHashMap.newKeySet();
4447

4548
public SerializationConfiguration() {
4649
}
4750

4851
public SerializationConfiguration(SerializationConfiguration other) {
4952
serializations.addAll(other.serializations);
53+
lambdaSerializationCapturingTypes.addAll(other.lambdaSerializationCapturingTypes);
5054
}
5155

5256
public void removeAll(SerializationConfiguration other) {
5357
serializations.removeAll(other.serializations);
58+
lambdaSerializationCapturingTypes.removeAll(other.lambdaSerializationCapturingTypes);
5459
}
5560

5661
public boolean contains(ConfigurationCondition condition, String serializationTargetClass, String customTargetConstructorClass) {
57-
return serializations.contains(createConfigurationType(condition, serializationTargetClass, customTargetConstructorClass));
62+
return serializations.contains(createConfigurationType(condition, serializationTargetClass, customTargetConstructorClass)) ||
63+
lambdaSerializationCapturingTypes.contains(createLambdaCapturingClassConfigurationType(condition, serializationTargetClass));
5864
}
5965

6066
@Override
6167
public void printJson(JsonWriter writer) throws IOException {
62-
writer.append('[').indent();
68+
writer.append('{').indent().newline();
69+
List<SerializationConfigurationType> listOfCapturedClasses = new ArrayList<>(serializations);
70+
Collections.sort(listOfCapturedClasses);
71+
printSerializationClasses(writer, "types", listOfCapturedClasses);
72+
writer.append(",").newline();
73+
List<SerializationConfigurationLambdaCapturingType> listOfCapturingClasses = new ArrayList<>(lambdaSerializationCapturingTypes);
74+
listOfCapturingClasses.sort(new SerializationConfigurationLambdaCapturingType.SerializationConfigurationLambdaCapturingTypesComparator());
75+
printSerializationClasses(writer, "lambdaCapturingTypes", listOfCapturingClasses);
76+
writer.unindent().newline();
77+
writer.append('}');
78+
}
79+
80+
private static void printSerializationClasses(JsonWriter writer, String types, List<? extends JsonPrintable> serializationConfigurationTypes) throws IOException {
81+
writer.quote(types).append(":");
82+
writer.append('[');
83+
writer.indent();
84+
85+
printSerializationTypes(serializationConfigurationTypes, writer);
86+
87+
writer.unindent().newline();
88+
writer.append("]");
89+
}
90+
91+
private static void printSerializationTypes(List<? extends JsonPrintable> serializationConfigurationTypes, JsonWriter writer) throws IOException {
6392
String prefix = "";
64-
List<SerializationConfigurationType> list = new ArrayList<>(serializations);
65-
Collections.sort(list);
66-
for (SerializationConfigurationType type : list) {
93+
94+
for (JsonPrintable type : serializationConfigurationTypes) {
6795
writer.append(prefix).newline();
6896
type.printJson(writer);
6997
prefix = ",";
7098
}
71-
writer.unindent().newline();
72-
writer.append(']');
7399
}
74100

75101
@Override
@@ -94,14 +120,24 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition,
94120
serializations.add(createConfigurationType(condition, className, customTargetConstructorClassName));
95121
}
96122

123+
@Override
124+
public void registerLambdaCapturingClass(ConfigurationCondition condition, String lambdaCapturingClassName) {
125+
lambdaSerializationCapturingTypes.add(createLambdaCapturingClassConfigurationType(condition, lambdaCapturingClassName.split(LambdaUtils.LAMBDA_SPLIT_PATTERN)[0]));
126+
}
127+
97128
@Override
98129
public boolean isEmpty() {
99-
return serializations.isEmpty();
130+
return serializations.isEmpty() && lambdaSerializationCapturingTypes.isEmpty();
100131
}
101132

102133
private static SerializationConfigurationType createConfigurationType(ConfigurationCondition condition, String className, String customTargetConstructorClassName) {
103134
String convertedClassName = SignatureUtil.toInternalClassName(className);
104135
String convertedCustomTargetConstructorClassName = customTargetConstructorClassName == null ? null : SignatureUtil.toInternalClassName(customTargetConstructorClassName);
105136
return new SerializationConfigurationType(condition, convertedClassName, convertedCustomTargetConstructorClassName);
106137
}
138+
139+
private static SerializationConfigurationLambdaCapturingType createLambdaCapturingClassConfigurationType(ConfigurationCondition condition, String className) {
140+
String convertedClassName = SignatureUtil.toInternalClassName(className);
141+
return new SerializationConfigurationLambdaCapturingType(condition, convertedClassName);
142+
}
107143
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.configure.config;
26+
27+
import java.io.IOException;
28+
import java.util.Comparator;
29+
import java.util.Objects;
30+
31+
import com.oracle.svm.configure.json.JsonPrintable;
32+
import org.graalvm.nativeimage.impl.ConfigurationCondition;
33+
34+
import com.oracle.svm.configure.json.JsonWriter;
35+
import com.oracle.svm.core.configure.SerializationConfigurationParser;
36+
37+
public class SerializationConfigurationLambdaCapturingType implements JsonPrintable {
38+
private final ConfigurationCondition condition;
39+
private final String qualifiedJavaName;
40+
41+
public SerializationConfigurationLambdaCapturingType(ConfigurationCondition condition, String qualifiedJavaName) {
42+
assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not the internal representation";
43+
Objects.requireNonNull(condition);
44+
this.condition = condition;
45+
Objects.requireNonNull(qualifiedJavaName);
46+
this.qualifiedJavaName = qualifiedJavaName;
47+
}
48+
49+
@Override
50+
public void printJson(JsonWriter writer) throws IOException {
51+
writer.append('{').indent().newline();
52+
ConfigurationConditionPrintable.printConditionAttribute(condition, writer);
53+
54+
writer.quote(SerializationConfigurationParser.NAME_KEY).append(":").quote(qualifiedJavaName);
55+
writer.unindent().newline().append('}');
56+
}
57+
58+
@Override
59+
public boolean equals(Object o) {
60+
if (this == o) {
61+
return true;
62+
}
63+
if (o == null || getClass() != o.getClass()) {
64+
return false;
65+
}
66+
SerializationConfigurationLambdaCapturingType that = (SerializationConfigurationLambdaCapturingType) o;
67+
return condition.equals(that.condition) &&
68+
qualifiedJavaName.equals(that.qualifiedJavaName);
69+
}
70+
71+
@Override
72+
public int hashCode() {
73+
return Objects.hash(condition, qualifiedJavaName);
74+
}
75+
76+
public static final class SerializationConfigurationLambdaCapturingTypesComparator implements Comparator<SerializationConfigurationLambdaCapturingType> {
77+
78+
@Override
79+
public int compare(SerializationConfigurationLambdaCapturingType o1, SerializationConfigurationLambdaCapturingType o2) {
80+
int compareName = o1.qualifiedJavaName.compareTo(o2.qualifiedJavaName);
81+
if (compareName != 0) {
82+
return compareName;
83+
}
84+
return o1.condition.compareTo(o2.condition);
85+
}
86+
}
87+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class SerializationConfigurationType implements JsonPrintable, Comparable
4040
private final String qualifiedCustomTargetConstructorJavaName;
4141

4242
public SerializationConfigurationType(ConfigurationCondition condition, String qualifiedJavaName, String qualifiedCustomTargetConstructorJavaName) {
43-
assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation";
43+
assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not the internal representation";
4444
assert !qualifiedJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]";
4545
assert qualifiedCustomTargetConstructorJavaName == null || qualifiedCustomTargetConstructorJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation";
4646
assert qualifiedCustomTargetConstructorJavaName == null || !qualifiedCustomTargetConstructorJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]";

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.List;
2929
import java.util.Map;
3030

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

3334
import com.oracle.svm.configure.config.SerializationConfiguration;
@@ -61,7 +62,21 @@ void processEntry(Map<String, ?> entry) {
6162
return;
6263
}
6364

64-
serializationConfiguration.registerWithTargetConstructorClass(condition, (String) args.get(0), (String) args.get(1));
65+
String className = (String) args.get(0);
66+
67+
if (className.contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)) {
68+
serializationConfiguration.registerLambdaCapturingClass(condition, className);
69+
} else {
70+
serializationConfiguration.registerWithTargetConstructorClass(condition, className, (String) args.get(1));
71+
}
72+
} else if ("SerializedLambda.readResolve".equals(function)) {
73+
expectSize(args, 1);
74+
75+
if (advisor.shouldIgnore(LazyValueUtils.lazyValue((String) args.get(0)), LazyValueUtils.lazyValue(null))) {
76+
return;
77+
}
78+
79+
serializationConfiguration.registerLambdaCapturingClass(condition, (String) args.get(0));
6580
}
6681
}
6782
}

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

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@
2929
import java.io.Reader;
3030
import java.util.Arrays;
3131
import java.util.Collections;
32+
import java.util.List;
3233
import java.util.Map;
3334

35+
import com.oracle.svm.core.util.json.JSONParserException;
3436
import org.graalvm.nativeimage.impl.ConfigurationCondition;
3537
import org.graalvm.nativeimage.impl.RuntimeSerializationSupport;
3638

@@ -40,6 +42,8 @@ public class SerializationConfigurationParser extends ConfigurationParser {
4042

4143
public static final String NAME_KEY = "name";
4244
public static final String CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY = "customTargetConstructorClass";
45+
private static final String SERIALIZATION_TYPES_KEY = "types";
46+
private static final String LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY = "lambdaCapturingTypes";
4347

4448
private final RuntimeSerializationSupport serializationSupport;
4549

@@ -52,18 +56,52 @@ public SerializationConfigurationParser(RuntimeSerializationSupport serializatio
5256
public void parseAndRegister(Reader reader) throws IOException {
5357
JSONParser parser = new JSONParser(reader);
5458
Object json = parser.parse();
55-
for (Object serializationKey : asList(json, "first level of document must be an array of serialization lists")) {
56-
parseSerializationDescriptorObject(asMap(serializationKey, "second level of document must be serialization descriptor objects"));
59+
if (json instanceof List) {
60+
parseOldConfiguration(asList(json, "first level of document must be an array of serialization lists"));
61+
} else if (json instanceof Map) {
62+
parseNewConfiguration(asMap(json, "first level of document must be a map of serialization types"));
63+
} else {
64+
throw new JSONParserException("first level of document must either be an array of serialization lists or a map of serialization types");
5765
}
5866
}
5967

60-
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));
68+
private void parseOldConfiguration(List<Object> listOfSerializationConfigurationObjects) {
69+
parseSerializationTypes(asList(listOfSerializationConfigurationObjects, "second level of document must be serialization descriptor objects"), false);
70+
}
71+
72+
private void parseNewConfiguration(Map<String, Object> listOfSerializationConfigurationObjects) {
73+
if (!listOfSerializationConfigurationObjects.containsKey(SERIALIZATION_TYPES_KEY) || !listOfSerializationConfigurationObjects.containsKey(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY)) {
74+
throw new JSONParserException("second level of document must be arrays of serialization descriptor objects");
75+
}
76+
77+
parseSerializationTypes(asList(listOfSerializationConfigurationObjects.get(SERIALIZATION_TYPES_KEY), "types must be an array of serialization descriptor objects"), false);
78+
parseSerializationTypes(
79+
asList(listOfSerializationConfigurationObjects.get(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY), "lambdaCapturingTypes must be an array of serialization descriptor objects"),
80+
true);
81+
}
82+
83+
private void parseSerializationTypes(List<Object> listOfSerializationTypes, boolean lambdaCapturingTypes) {
84+
for (Object serializationType : listOfSerializationTypes) {
85+
parseSerializationDescriptorObject(asMap(serializationType, "third level of document must be serialization descriptor objects"), lambdaCapturingTypes);
86+
}
87+
}
88+
89+
private void parseSerializationDescriptorObject(Map<String, Object> data, boolean lambdaCapturingType) {
90+
if (lambdaCapturingType) {
91+
checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Collections.singleton(CONDITIONAL_KEY));
92+
} else {
93+
checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY));
94+
}
95+
6296
ConfigurationCondition unresolvedCondition = parseCondition(data);
6397
String targetSerializationClass = asString(data.get(NAME_KEY));
64-
Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY);
65-
String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null;
6698

67-
serializationSupport.registerWithTargetConstructorClass(unresolvedCondition, targetSerializationClass, customTargetConstructorClass);
99+
if (lambdaCapturingType) {
100+
serializationSupport.registerLambdaCapturingClass(unresolvedCondition, targetSerializationClass);
101+
} else {
102+
Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY);
103+
String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null;
104+
serializationSupport.registerWithTargetConstructorClass(unresolvedCondition, targetSerializationClass, customTargetConstructorClass);
105+
}
68106
}
69107
}

0 commit comments

Comments
 (0)