Skip to content

Commit f011d4d

Browse files
committed
[GR-42113] Fixes for serialization reflection registration.
PullRequest: graal/15226
2 parents 1a53aac + 820ced0 commit f011d4d

File tree

12 files changed

+347
-54
lines changed

12 files changed

+347
-54
lines changed

compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/java/LambdaUtils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,8 @@ public static String digest(String value) {
176176
throw new JVMCIError(ex);
177177
}
178178
}
179+
180+
public static String capturingClass(String className) {
181+
return className.split(LambdaUtils.SERIALIZATION_TEST_LAMBDA_CLASS_SPLIT_PATTERN)[0];
182+
}
179183
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
public final class MissingRegistrationUtils {
3636

37+
public static final String ERROR_EMPHASIS_INDENT = " ";
38+
3739
public static boolean throwMissingRegistrationErrors() {
3840
return SubstrateOptions.ThrowMissingRegistrationErrors.hasBeenSet();
3941
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ
257257
/** Is the class a proxy class according to {@link java.lang.reflect.Proxy#isProxyClass}? */
258258
private static final int IS_PROXY_CLASS_BIT = 2;
259259

260+
private static final int IS_REGISTERED_FOR_SERIALIZATION = 3;
261+
260262
/**
261263
* The {@link Modifier modifiers} of this class.
262264
*/
@@ -436,7 +438,8 @@ public void setClassInitializationInfo(ClassInitializationInfo classInitializati
436438

437439
@Platforms(Platform.HOSTED_ONLY.class)
438440
public void setData(int layoutEncoding, int typeID, int monitorOffset, int optionalIdentityHashOffset, short typeCheckStart, short typeCheckRange, short typeCheckSlot,
439-
short[] typeCheckSlots, CFunctionPointer[] vtable, long referenceMapIndex, boolean isInstantiated, boolean canInstantiateAsInstance, boolean isProxyClass) {
441+
short[] typeCheckSlots, CFunctionPointer[] vtable, long referenceMapIndex, boolean isInstantiated, boolean canInstantiateAsInstance, boolean isProxyClass,
442+
boolean isRegisteredForSerialization) {
440443
assert this.vtable == null : "Initialization must be called only once";
441444
assert !(!isInstantiated && canInstantiateAsInstance);
442445
if (LayoutEncoding.isPureInstance(layoutEncoding)) {
@@ -462,7 +465,8 @@ public void setData(int layoutEncoding, int typeID, int monitorOffset, int optio
462465
this.referenceMapIndex = (int) referenceMapIndex;
463466
this.additionalFlags = NumUtil.safeToUByte(makeFlag(IS_INSTANTIATED_BIT, isInstantiated) |
464467
makeFlag(CAN_INSTANTIATE_AS_INSTANCE_BIT, canInstantiateAsInstance) |
465-
makeFlag(IS_PROXY_CLASS_BIT, isProxyClass));
468+
makeFlag(IS_PROXY_CLASS_BIT, isProxyClass) |
469+
makeFlag(IS_REGISTERED_FOR_SERIALIZATION, isRegisteredForSerialization));
466470
}
467471

468472
@Platforms(Platform.HOSTED_ONLY.class)
@@ -883,6 +887,10 @@ public boolean isLambdaFormHidden() {
883887
return isFlagSet(flags, IS_LAMBDA_FORM_HIDDEN_BIT);
884888
}
885889

890+
public boolean isRegisteredForSerialization() {
891+
return isFlagSet(additionalFlags, IS_REGISTERED_FOR_SERIALIZATION);
892+
}
893+
886894
@KeepOriginal
887895
private native boolean isLocalClass();
888896

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaIOSubstitutions.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,17 @@
2525
package com.oracle.svm.core.jdk;
2626

2727
import java.io.Closeable;
28+
import java.io.ObjectStreamClass;
29+
import java.io.Serializable;
2830
import java.lang.ref.ReferenceQueue;
31+
import java.lang.reflect.Proxy;
32+
import java.util.Arrays;
2933
import java.util.List;
3034
import java.util.concurrent.ConcurrentHashMap;
3135
import java.util.concurrent.ConcurrentMap;
36+
import java.util.stream.Collectors;
37+
38+
import org.graalvm.compiler.java.LambdaUtils;
3239

3340
import com.oracle.svm.core.annotate.Alias;
3441
import com.oracle.svm.core.annotate.RecomputeFieldValue;
@@ -38,6 +45,7 @@
3845
import com.oracle.svm.core.annotate.TargetElement;
3946
import com.oracle.svm.core.fieldvaluetransformer.NewInstanceFieldValueTransformer;
4047
import com.oracle.svm.core.hub.DynamicHub;
48+
import com.oracle.svm.core.reflect.serialize.MissingSerializationRegistrationUtils;
4149

4250
@TargetClass(java.io.FileDescriptor.class)
4351
final class Target_java_io_FileDescriptor {
@@ -53,16 +61,47 @@ final class Target_java_io_ObjectStreamClass {
5361
private static boolean hasStaticInitializer(Class<?> cl) {
5462
return DynamicHub.fromClass(cl).getClassInitializationInfo().hasInitializer();
5563
}
64+
65+
@Substitute
66+
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
67+
if (!(all || Serializable.class.isAssignableFrom(cl))) {
68+
return null;
69+
}
70+
71+
if (Serializable.class.isAssignableFrom(cl)) {
72+
if (!DynamicHub.fromClass(cl).isRegisteredForSerialization()) {
73+
boolean isLambda = cl.getTypeName().contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING);
74+
boolean isProxy = Proxy.isProxyClass(cl);
75+
if (isProxy || isLambda) {
76+
var interfaceList = Arrays.stream(cl.getInterfaces())
77+
.map(Class::getTypeName)
78+
.collect(Collectors.joining(", ", "[", "]"));
79+
if (isProxy) {
80+
MissingSerializationRegistrationUtils.missingSerializationRegistration(cl, "proxy type implementing interfaces: " + interfaceList);
81+
} else {
82+
MissingSerializationRegistrationUtils.missingSerializationRegistration(cl,
83+
"lambda declared in: " + LambdaUtils.capturingClass(cl.getTypeName()),
84+
"extending interfaces: " + interfaceList);
85+
}
86+
} else {
87+
MissingSerializationRegistrationUtils.missingSerializationRegistration(cl, "type " + cl.getTypeName());
88+
}
89+
}
90+
}
91+
92+
return Target_java_io_ObjectStreamClass_Caches.localDescs0.get(cl);
93+
}
94+
5695
}
5796

5897
@TargetClass(value = java.io.ObjectStreamClass.class, innerClass = "Caches")
5998
final class Target_java_io_ObjectStreamClass_Caches {
6099

61-
@TargetElement(onlyWith = JavaIOClassCachePresent.class, name = "localDescs") @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = NewInstanceFieldValueTransformer.class) static Target_java_io_ClassCache localDescs0;
100+
@TargetElement(onlyWith = JavaIOClassCachePresent.class, name = "localDescs") @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = NewInstanceFieldValueTransformer.class) static Target_java_io_ClassCache<ObjectStreamClass> localDescs0;
62101

63-
@TargetElement(onlyWith = JavaIOClassCachePresent.class, name = "reflectors") @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = NewInstanceFieldValueTransformer.class) static Target_java_io_ClassCache reflectors0;
102+
@TargetElement(onlyWith = JavaIOClassCachePresent.class, name = "reflectors") @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = NewInstanceFieldValueTransformer.class) static Target_java_io_ClassCache<?> reflectors0;
64103

65-
@TargetElement(onlyWith = JavaIOClassCacheAbsent.class) @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClass = ConcurrentHashMap.class) static ConcurrentMap<?, ?> localDescs;
104+
@TargetElement(onlyWith = JavaIOClassCacheAbsent.class) @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClass = ConcurrentHashMap.class) static ConcurrentMap<?, ObjectStreamClass> localDescs;
66105

67106
@TargetElement(onlyWith = JavaIOClassCacheAbsent.class) @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClass = ConcurrentHashMap.class) static ConcurrentMap<?, ?> reflectors;
68107

@@ -72,7 +111,9 @@ final class Target_java_io_ObjectStreamClass_Caches {
72111
}
73112

74113
@TargetClass(className = "java.io.ClassCache", onlyWith = JavaIOClassCachePresent.class)
75-
final class Target_java_io_ClassCache {
114+
final class Target_java_io_ClassCache<T> {
115+
@Alias
116+
native T get(Class<?> cl);
76117
}
77118

78119
/** Dummy class to have a class with the file's name. */
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2023, 2023, 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.core.reflect.serialize;
26+
27+
import java.io.Serial;
28+
29+
/**
30+
* Error thrown when types are not <a href=
31+
* "https://www.graalvm.org/latest/reference-manual/native-image/metadata/#serialization">registered</a>
32+
* for serialization or deserialization.
33+
* <p/>
34+
* The purpose of this exception is to easily discover unregistered elements and to assure that all
35+
* serialization or deserialization operations have expected behavior.
36+
*/
37+
public final class MissingSerializationRegistrationError extends Error {
38+
@Serial private static final long serialVersionUID = 2764341882856270641L;
39+
private final Class<?> culprit;
40+
41+
public MissingSerializationRegistrationError(String message, Class<?> cl) {
42+
super(message);
43+
this.culprit = cl;
44+
}
45+
46+
/**
47+
* Returns the class that was not registered for serialization or deserialization.
48+
*/
49+
public Class<?> getCulprit() {
50+
return culprit;
51+
}
52+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright (c) 2023, 2023, 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.core.reflect.serialize;
26+
27+
import static com.oracle.svm.core.MissingRegistrationUtils.ERROR_EMPHASIS_INDENT;
28+
29+
import java.io.ObjectInputStream;
30+
import java.io.ObjectOutputStream;
31+
import java.io.ObjectStreamClass;
32+
import java.util.Arrays;
33+
import java.util.Map;
34+
import java.util.Set;
35+
import java.util.stream.Collectors;
36+
37+
import com.oracle.svm.core.MissingRegistrationUtils;
38+
39+
public final class MissingSerializationRegistrationUtils {
40+
41+
public static void missingSerializationRegistration(Class<?> cl, String... msg) {
42+
report(new MissingSerializationRegistrationError(errorMessage(msg), cl));
43+
}
44+
45+
private static String errorMessage(String... type) {
46+
var typeStr = Arrays.stream(type).collect(Collectors.joining(System.lineSeparator(), ERROR_EMPHASIS_INDENT, ""));
47+
return """
48+
The program tried to serialize or deserialize
49+
50+
%s
51+
52+
without it being registered for serialization. Add this class to the serialization metadata to solve this problem.
53+
See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#serialization for help
54+
""".replaceAll("\n", System.lineSeparator())
55+
.formatted(typeStr);
56+
}
57+
58+
private static void report(MissingSerializationRegistrationError exception) {
59+
StackTraceElement responsibleClass = getResponsibleClass(exception);
60+
MissingRegistrationUtils.report(exception, responsibleClass);
61+
}
62+
63+
/*
64+
* This is a list of all public JDK methods that end up potentially throwing missing
65+
* registration errors. This should be implemented using wrapping substitutions once they are
66+
* available.
67+
*/
68+
private static final Map<String, Set<String>> serializationEntryPoints = Map.of(
69+
ObjectOutputStream.class.getTypeName(), Set.of("writeObject", "writeUnshared"),
70+
ObjectInputStream.class.getTypeName(), Set.of("readObject", "readUnshared"),
71+
ObjectStreamClass.class.getTypeName(), Set.of("lookup"),
72+
"sun.reflect.ReflectionFactory", Set.of("newConstructorForSerialization"),
73+
"jdk.internal.reflect.ReflectionFactory", Set.of("newConstructorForSerialization"));
74+
75+
private static StackTraceElement getResponsibleClass(MissingSerializationRegistrationError t) {
76+
StackTraceElement[] stackTrace = t.getStackTrace();
77+
boolean previous = false;
78+
StackTraceElement lastElem = null;
79+
for (StackTraceElement stackTraceElement : stackTrace) {
80+
if (previous) {
81+
previous = false;
82+
lastElem = stackTraceElement;
83+
}
84+
if (serializationEntryPoints.getOrDefault(stackTraceElement.getClassName(), Set.of())
85+
.contains(stackTraceElement.getMethodName())) {
86+
previous = true;
87+
}
88+
}
89+
return lastElem;
90+
}
91+
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationRegistry.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
public interface SerializationRegistry {
2929

30+
boolean isRegisteredForSerialization(Class<?> cl);
31+
3032
Object getSerializationConstructorAccessor(Class<?> serializationTargetClass, Class<?> targetConstructorClass);
3133

3234
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,13 @@
3131
import java.lang.reflect.Modifier;
3232
import java.util.Map;
3333
import java.util.Objects;
34+
import java.util.Set;
3435
import java.util.concurrent.ConcurrentHashMap;
3536

3637
import org.graalvm.compiler.java.LambdaUtils;
3738
import org.graalvm.nativeimage.Platform;
3839
import org.graalvm.nativeimage.Platforms;
3940

40-
import com.oracle.svm.core.util.VMError;
41-
4241
public class SerializationSupport implements SerializationRegistry {
4342

4443
/**
@@ -125,6 +124,19 @@ public Object addConstructorAccessor(Class<?> declaringClass, Class<?> targetCon
125124
return constructorAccessors.putIfAbsent(key, constructorAccessor);
126125
}
127126

127+
@Platforms(Platform.HOSTED_ONLY.class) private final Set<Class<?>> classes = ConcurrentHashMap.newKeySet();
128+
129+
@Platforms(Platform.HOSTED_ONLY.class)
130+
public void registerSerializationTargetClass(Class<?> serializationTargetClass) {
131+
classes.add(serializationTargetClass);
132+
}
133+
134+
@Override
135+
@Platforms(Platform.HOSTED_ONLY.class)
136+
public boolean isRegisteredForSerialization(Class<?> cl) {
137+
return classes.contains(cl);
138+
}
139+
128140
@Override
129141
public Object getSerializationConstructorAccessor(Class<?> rawDeclaringClass, Class<?> rawTargetConstructorClass) {
130142
Class<?> declaringClass = rawDeclaringClass;
@@ -140,9 +152,9 @@ public Object getSerializationConstructorAccessor(Class<?> rawDeclaringClass, Cl
140152
return constructorAccessor;
141153
} else {
142154
String targetConstructorClassName = targetConstructorClass.getName();
143-
throw VMError.unsupportedFeature("SerializationConstructorAccessor class not found for declaringClass: " + declaringClass.getName() +
144-
" (targetConstructorClass: " + targetConstructorClassName + "). Usually adding " + declaringClass.getName() +
145-
" to serialization-config.json fixes the problem.");
155+
MissingSerializationRegistrationUtils.missingSerializationRegistration(declaringClass,
156+
"type " + declaringClass.getName() + " with target constructor class: " + targetConstructorClassName);
157+
return null;
146158
}
147159
}
148160
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
import com.oracle.svm.core.meta.MethodPointer;
9090
import com.oracle.svm.core.reflect.SubstrateConstructorAccessor;
9191
import com.oracle.svm.core.reflect.SubstrateMethodAccessor;
92+
import com.oracle.svm.core.reflect.serialize.SerializationRegistry;
9293
import com.oracle.svm.core.util.VMError;
9394
import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl;
9495
import com.oracle.svm.hosted.HostedConfiguration;
@@ -1071,8 +1072,10 @@ private void buildHubs() {
10711072
boolean isProxyClass = ImageSingletons.lookup(DynamicProxyRegistry.class).isProxyClass(type.getJavaClass());
10721073

10731074
DynamicHub hub = type.getHub();
1075+
SerializationRegistry s = ImageSingletons.lookup(SerializationRegistry.class);
10741076
hub.setData(layoutHelper, type.getTypeID(), monitorOffset, optionalIdHashOffset, type.getTypeCheckStart(), type.getTypeCheckRange(),
1075-
type.getTypeCheckSlot(), type.getTypeCheckSlots(), vtable, referenceMapIndex, type.isInstantiated(), canInstantiateAsInstance, isProxyClass);
1077+
type.getTypeCheckSlot(), type.getTypeCheckSlots(), vtable, referenceMapIndex, type.isInstantiated(), canInstantiateAsInstance, isProxyClass,
1078+
s.isRegisteredForSerialization(type.getJavaClass()));
10761079
}
10771080
}
10781081

0 commit comments

Comments
 (0)