Skip to content

Commit e42caa9

Browse files
committed
Support JDK serialization
1 parent d5d194b commit e42caa9

File tree

30 files changed

+1302
-48
lines changed

30 files changed

+1302
-48
lines changed

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,21 @@ public static void register(boolean finalIsWritable, Field... fields) {
107107
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(finalIsWritable, false, fields);
108108
}
109109

110+
/**
111+
* Makes the provided fields available for reflection at run time. The fields will be returned
112+
* by {@link Class#getField}, {@link Class#getFields},and all the other methods on {@link Class}
113+
* that return a single or a list of fields.
114+
*
115+
* @param finalIsWritable for all of the passed fields which are marked {@code final}, indicates
116+
* whether it should be possible to change their value using reflection.
117+
* @param allowUnsafeAccess for all of the passed fields, indicates whether it should be
118+
* possible to access by unsafe operations.
119+
* @since 21.0
120+
*/
121+
public static void register(boolean finalIsWritable, boolean allowUnsafeAccess, Field... fields) {
122+
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(finalIsWritable, allowUnsafeAccess, fields);
123+
}
124+
110125
/**
111126
* Makes the provided classes available for reflective instantiation by
112127
* {@link Class#newInstance}. This is equivalent to registering the nullary constructors of the

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

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import static com.oracle.svm.core.util.VMError.guarantee;
2828
import static com.oracle.svm.jni.JNIObjectHandles.nullHandle;
29+
import static com.oracle.svm.jvmtiagentbase.Support.callObjectMethod;
2930
import static com.oracle.svm.jvmtiagentbase.Support.check;
3031
import static com.oracle.svm.jvmtiagentbase.Support.checkJni;
3132
import static com.oracle.svm.jvmtiagentbase.Support.checkNoException;
@@ -39,11 +40,13 @@
3940
import static com.oracle.svm.jvmtiagentbase.Support.getClassNameOr;
4041
import static com.oracle.svm.jvmtiagentbase.Support.getClassNameOrNull;
4142
import static com.oracle.svm.jvmtiagentbase.Support.getDirectCallerClass;
43+
import static com.oracle.svm.jvmtiagentbase.Support.getObjectField;
4244
import static com.oracle.svm.jvmtiagentbase.Support.getMethodDeclaringClass;
4345
import static com.oracle.svm.jvmtiagentbase.Support.getObjectArgument;
4446
import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions;
4547
import static com.oracle.svm.jvmtiagentbase.Support.jvmtiEnv;
4648
import static com.oracle.svm.jvmtiagentbase.Support.jvmtiFunctions;
49+
import static com.oracle.svm.jvmtiagentbase.Support.newObjectL;
4750
import static com.oracle.svm.jvmtiagentbase.Support.testException;
4851
import static com.oracle.svm.jvmtiagentbase.Support.toCString;
4952
import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_BREAKPOINT;
@@ -61,6 +64,8 @@
6164
import java.util.concurrent.ConcurrentMap;
6265
import java.util.concurrent.locks.ReentrantLock;
6366

67+
import com.oracle.svm.util.SerializationChecksumCalculator;
68+
import com.oracle.svm.jni.nativeapi.JNIFieldId;
6469
import org.graalvm.compiler.core.common.NumUtil;
6570
import org.graalvm.nativeimage.StackValue;
6671
import org.graalvm.nativeimage.UnmanagedMemory;
@@ -76,6 +81,7 @@
7681
import org.graalvm.nativeimage.c.type.CTypeConversion;
7782
import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder;
7883
import org.graalvm.nativeimage.c.type.WordPointer;
84+
import org.graalvm.word.WordBase;
7985
import org.graalvm.word.WordFactory;
8086

8187
import com.oracle.svm.core.c.function.CEntryPointOptions;
@@ -831,6 +837,121 @@ private static boolean resolveMemberName(JNIEnvironment jni, Breakpoint bp) {
831837
return true;
832838
}
833839

840+
static class CheckSumCalculator extends SerializationChecksumCalculator.JVMCIAgentCalculator {
841+
private JNIEnvironment jni;
842+
private Breakpoint bp;
843+
844+
CheckSumCalculator(JNIEnvironment jni, Breakpoint bp) {
845+
this.jni = jni;
846+
this.bp = bp;
847+
}
848+
849+
@Override
850+
protected WordBase getSuperClass(WordBase clazz) {
851+
return jniFunctions().getGetSuperclass().invoke(jni, (JNIObjectHandle) clazz);
852+
}
853+
854+
@Override
855+
public Long calculateFromComputeDefaultSUID(WordBase clazz) {
856+
JNIMethodId computeDefaultSUIDMId = agent.handles().getJavaIoObjectStreamClassComputeDefaultSUID(jni, bp.clazz);
857+
JNIValue args = StackValue.get(1, JNIValue.class);
858+
args.setObject((JNIObjectHandle) clazz);
859+
return jniFunctions().getCallStaticLongMethodA().invoke(jni, bp.clazz, computeDefaultSUIDMId, args);
860+
}
861+
862+
@Override
863+
protected boolean isClassAbstract(WordBase clazz) {
864+
CIntPointer modifiers = StackValue.get(CIntPointer.class);
865+
if (jvmtiFunctions().GetClassModifiers().invoke(jvmtiEnv(), (JNIObjectHandle) clazz, modifiers) != JvmtiError.JVMTI_ERROR_NONE) {
866+
return false;
867+
}
868+
// Checkstyle: allow reflection
869+
return (modifiers.read() & java.lang.reflect.Modifier.ABSTRACT) != 0;
870+
}
871+
872+
@Override
873+
public String getClassName(WordBase clazz) {
874+
return getClassNameOrNull(jni, (JNIObjectHandle) clazz);
875+
}
876+
}
877+
878+
private static boolean objectStreamClassConstructor(JNIEnvironment jni, Breakpoint bp) {
879+
JNIObjectHandle serializeTargetClass = getObjectArgument(1);
880+
String serializeTargetClassName = getClassNameOrNull(jni, serializeTargetClass);
881+
long checksum = 0;
882+
List<SerializationInfo> traceCandidates = new ArrayList<>();
883+
CheckSumCalculator checkSumCalculator = new CheckSumCalculator(jni, bp);
884+
JNIObjectHandle objectStreamClassInstance = newObjectL(jni, bp.clazz, bp.method, serializeTargetClass);
885+
Object result = nullHandle().notEqual(objectStreamClassInstance);
886+
if (clearException(jni)) {
887+
result = false;
888+
}
889+
// Skip Lambda class serialization
890+
if (serializeTargetClassName.contains("$$Lambda$")) {
891+
return true;
892+
}
893+
if (result.equals(true)) {
894+
checksum = checkSumCalculator.calculateChecksum(getConsClassName(jni, bp.clazz, objectStreamClassInstance), serializeTargetClassName, serializeTargetClass);
895+
}
896+
traceCandidates.add(new SerializationInfo(serializeTargetClassName, checksum));
897+
898+
/**
899+
* When the ObjectStreamClass instance is created for the given serializeTargetClass, some
900+
* additional ObjectStreamClass instances (usually the super classes) are created
901+
* recursively. Call ObjectStreamClass.getClassDataLayout0() can get all of them.
902+
*/
903+
JNIMethodId getClassDataLayout0MId = agent.handles().getJavaIoObjectStreamClassGetClassDataLayout0(jni, bp.clazz);
904+
JNIObjectHandle dataLayoutArray = callObjectMethod(jni, objectStreamClassInstance, getClassDataLayout0MId);
905+
if (!clearException(jni) && nullHandle().notEqual(dataLayoutArray)) {
906+
int length = jniFunctions().getGetArrayLength().invoke(jni, dataLayoutArray);
907+
// If only 1 element is got from getClassDataLayout0(). it is base ObjectStreamClass
908+
// instance itself.
909+
if (!clearException(jni) && length > 1) {
910+
JNIFieldId hasDataFId = agent.handles().getJavaIOObjectStreamClassClassDataSlotHasData(jni);
911+
JNIFieldId descFId = agent.handles().getJavaIOObjectStreamClassClassDataSlotDesc(jni);
912+
JNIMethodId javaIoObjectStreamClassForClassMId = agent.handles().getJavaIoObjectStreamClassForClass(jni, bp.clazz);
913+
for (int i = 0; i < length; i++) {
914+
JNIObjectHandle classDataSlot = jniFunctions().getGetObjectArrayElement().invoke(jni, dataLayoutArray, i);
915+
boolean hasData = jniFunctions().getGetBooleanField().invoke(jni, classDataSlot, hasDataFId);
916+
if (hasData) {
917+
JNIObjectHandle oscInstanceInSlot = jniFunctions().getGetObjectField().invoke(jni, classDataSlot, descFId);
918+
if (!jniFunctions().getIsSameObject().invoke(jni, oscInstanceInSlot, objectStreamClassInstance)) {
919+
JNIObjectHandle oscClazz = callObjectMethod(jni, oscInstanceInSlot, javaIoObjectStreamClassForClassMId);
920+
String oscClassName = getClassNameOrNull(jni, oscClazz);
921+
traceCandidates.add(new SerializationInfo(oscClassName,
922+
checkSumCalculator.calculateChecksum(getConsClassName(jni,
923+
bp.clazz, oscInstanceInSlot), oscClassName, oscClazz)));
924+
}
925+
}
926+
}
927+
}
928+
}
929+
for (SerializationInfo serializationInfo : traceCandidates) {
930+
if (traceWriter != null) {
931+
traceWriter.traceCall("serialization",
932+
"ObjectStreamClass.<init>",
933+
null,
934+
null,
935+
null,
936+
result,
937+
// serializeTargetClassName, checksum);
938+
serializationInfo.className, serializationInfo.checksum);
939+
guarantee(!testException(jni));
940+
}
941+
}
942+
return true;
943+
}
944+
945+
private static String getConsClassName(JNIEnvironment jni, JNIObjectHandle objectStreamClassClazz, JNIObjectHandle objectStreamClassInstance) {
946+
JNIObjectHandle cons = getObjectField(jni, objectStreamClassClazz, objectStreamClassInstance, "cons", "Ljava/lang/reflect/Constructor;");
947+
String targetConstructorClassName = "";
948+
if (nullHandle().notEqual(cons)) {
949+
// Compute hashcode from the first unserializable superclass
950+
targetConstructorClassName = getClassNameOrNull(jni, callObjectMethod(jni, cons, agent.handles().javaLangReflectMemberGetDeclaringClass));
951+
}
952+
return targetConstructorClassName;
953+
}
954+
834955
@CEntryPoint
835956
@CEntryPointOptions(prologue = AgentIsolate.Prologue.class)
836957
private static void onBreakpoint(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni,
@@ -1135,6 +1256,7 @@ private interface BreakpointHandler {
11351256
brk("java/lang/reflect/Proxy", "newProxyInstance",
11361257
"(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;", BreakpointInterceptor::newProxyInstance),
11371258

1259+
brk("java/io/ObjectStreamClass", "<init>", "(Ljava/lang/Class;)V", BreakpointInterceptor::objectStreamClassConstructor),
11381260
optionalBrk("java/util/ResourceBundle",
11391261
"getBundleImpl",
11401262
"(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;",
@@ -1264,6 +1386,16 @@ public int hashCode() {
12641386
}
12651387
}
12661388

1389+
private static final class SerializationInfo {
1390+
private String className;
1391+
private long checksum;
1392+
1393+
SerializationInfo(String serializeTargetClassName, long checksum) {
1394+
this.className = serializeTargetClassName;
1395+
this.checksum = checksum;
1396+
}
1397+
}
1398+
12671399
private BreakpointInterceptor() {
12681400
}
12691401
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
231231
// They should use the same filter sets, however.
232232
AccessAdvisor advisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter);
233233
TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler),
234-
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler));
234+
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler));
235235
traceWriter = new TraceProcessorWriterAdapter(processor);
236236
} catch (Throwable t) {
237237
System.err.println(MESSAGE_PREFIX + t);
@@ -424,6 +424,7 @@ private void writeConfigurationFiles() {
424424
allConfigFiles.put(ConfigurationFiles.JNI_NAME, p.getJniConfiguration());
425425
allConfigFiles.put(ConfigurationFiles.DYNAMIC_PROXY_NAME, p.getProxyConfiguration());
426426
allConfigFiles.put(ConfigurationFiles.RESOURCES_NAME, p.getResourceConfiguration());
427+
allConfigFiles.put(ConfigurationFiles.SERIALIZATION_NAME, p.getSerializationConfiguration());
427428

428429
for (Map.Entry<String, JsonPrintable> configFile : allConfigFiles.entrySet()) {
429430
Path tempPath = tempDirectory.resolve(configFile.getKey());

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
*/
2525
package com.oracle.svm.agent;
2626

27+
import static com.oracle.svm.jni.JNIObjectHandles.nullHandle;
28+
29+
import com.oracle.svm.jni.nativeapi.JNIFieldId;
2730
import com.oracle.svm.jni.nativeapi.JNIEnvironment;
2831
import com.oracle.svm.jni.nativeapi.JNIMethodId;
2932
import com.oracle.svm.jni.nativeapi.JNIObjectHandle;
@@ -33,6 +36,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
3336

3437
final JNIObjectHandle javaLangClass;
3538
final JNIMethodId javaLangClassForName3;
39+
final JNIMethodId javaUtilEnumerationNextElement;
3640
final JNIMethodId javaLangClassGetDeclaredMethod;
3741
final JNIMethodId javaLangClassGetDeclaredConstructor;
3842
final JNIMethodId javaLangClassGetDeclaredField;
@@ -53,6 +57,15 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
5357
final JNIMethodId javaLangInvokeMemberNameIsField;
5458

5559
private JNIMethodId javaUtilResourceBundleGetBundleImplSLCC;
60+
61+
// Lazily look for serialization classes
62+
private JNIMethodId javaIoObjectStreamClassComputeDefaultSUID;
63+
private JNIMethodId javaIoObjectStreamClassForClass;
64+
private JNIMethodId javaIoObjectStreamClassGetClassDataLayout0;
65+
private JNIObjectHandle javaIOObjectStreamClassClassDataSlot;
66+
private JNIFieldId javaIOObjectStreamClassClassDataSlotDesc;
67+
private JNIFieldId javaIOObjectStreamClassClassDataSlotHasData;
68+
5669
private boolean queriedJavaUtilResourceBundleGetBundleImplSLCC;
5770

5871
NativeImageAgentJNIHandleSet(JNIEnvironment env) {
@@ -70,6 +83,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
7083

7184
JNIObjectHandle javaUtilEnumeration = findClass(env, "java/util/Enumeration");
7285
javaUtilEnumerationHasMoreElements = getMethodId(env, javaUtilEnumeration, "hasMoreElements", "()Z", false);
86+
javaUtilEnumerationNextElement = getMethodId(env, javaUtilEnumeration, "nextElement", "()Ljava/lang/Object;", false);
7387

7488
javaLangClassLoader = newClassGlobalRef(env, "java/lang/ClassLoader");
7589

@@ -91,4 +105,46 @@ JNIMethodId tryGetJavaUtilResourceBundleGetBundleImplSLCC(JNIEnvironment env) {
91105
}
92106
return javaUtilResourceBundleGetBundleImplSLCC;
93107
}
108+
109+
JNIMethodId getJavaIoObjectStreamClassComputeDefaultSUID(JNIEnvironment env, JNIObjectHandle javaIoObjectStreamClass) {
110+
if (javaIoObjectStreamClassComputeDefaultSUID.equal(nullHandle())) {
111+
javaIoObjectStreamClassComputeDefaultSUID = getMethodId(env, javaIoObjectStreamClass, "computeDefaultSUID", "(Ljava/lang/Class;)J", true);
112+
}
113+
return javaIoObjectStreamClassComputeDefaultSUID;
114+
}
115+
116+
JNIMethodId getJavaIoObjectStreamClassForClass(JNIEnvironment env, JNIObjectHandle javaIoObjectStreamClass) {
117+
if (javaIoObjectStreamClassForClass.equal(nullHandle())) {
118+
javaIoObjectStreamClassForClass = getMethodId(env, javaIoObjectStreamClass, "forClass", "()Ljava/lang/Class;", false);
119+
}
120+
return javaIoObjectStreamClassForClass;
121+
}
122+
123+
JNIMethodId getJavaIoObjectStreamClassGetClassDataLayout0(JNIEnvironment env, JNIObjectHandle javaIoObjectStreamClass) {
124+
if (javaIoObjectStreamClassGetClassDataLayout0.equal(nullHandle())) {
125+
javaIoObjectStreamClassGetClassDataLayout0 = getMethodId(env, javaIoObjectStreamClass, "getClassDataLayout0", "()[Ljava/io/ObjectStreamClass$ClassDataSlot;", false);
126+
}
127+
return javaIoObjectStreamClassGetClassDataLayout0;
128+
}
129+
130+
JNIObjectHandle getJavaIOObjectStreamClassClassDataSlot(JNIEnvironment env) {
131+
if (javaIOObjectStreamClassClassDataSlot.equal(nullHandle())) {
132+
javaIOObjectStreamClassClassDataSlot = newClassGlobalRef(env, "java/io/ObjectStreamClass$ClassDataSlot");
133+
}
134+
return javaIOObjectStreamClassClassDataSlot;
135+
}
136+
137+
JNIFieldId getJavaIOObjectStreamClassClassDataSlotDesc(JNIEnvironment env) {
138+
if (javaIOObjectStreamClassClassDataSlotDesc.equal(nullHandle())) {
139+
javaIOObjectStreamClassClassDataSlotDesc = getFieldId(env, getJavaIOObjectStreamClassClassDataSlot(env), "desc", "Ljava/io/ObjectStreamClass;", false);
140+
}
141+
return javaIOObjectStreamClassClassDataSlotDesc;
142+
}
143+
144+
JNIFieldId getJavaIOObjectStreamClassClassDataSlotHasData(JNIEnvironment env) {
145+
if (javaIOObjectStreamClassClassDataSlotHasData.equal(nullHandle())) {
146+
javaIOObjectStreamClassClassDataSlotHasData = getFieldId(env, getJavaIOObjectStreamClassClassDataSlot(env), "hasData", "Z", false);
147+
}
148+
return javaIOObjectStreamClassClassDataSlotHasData;
149+
}
94150
}

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ private static void generate(Iterator<String> argsIter, boolean acceptTraceFileA
189189
set.getResourceConfigPaths().add(requirePathUri(current, value));
190190
break;
191191

192+
case "--serialization-input":
193+
set = inputSet; // fall through
194+
case "--serialization-output":
195+
set.getSerializationConfigPaths().add(requirePathUri(current, value));
196+
break;
197+
192198
case "--trace-input":
193199
traceInputs.add(requirePathUri(current, value));
194200
break;
@@ -249,7 +255,8 @@ private static void generate(Iterator<String> argsIter, boolean acceptTraceFileA
249255
TraceProcessor p;
250256
try {
251257
p = new TraceProcessor(advisor, inputSet.loadJniConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadReflectConfig(ConfigurationSet.FAIL_ON_EXCEPTION),
252-
inputSet.loadProxyConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION));
258+
inputSet.loadProxyConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION),
259+
inputSet.loadSerializationConfig(ConfigurationSet.FAIL_ON_EXCEPTION));
253260
} catch (IOException e) {
254261
throw e;
255262
} catch (Throwable t) {
@@ -287,6 +294,11 @@ private static void generate(Iterator<String> argsIter, boolean acceptTraceFileA
287294
p.getResourceConfiguration().printJson(writer);
288295
}
289296
}
297+
for (URI uri : outputSet.getSerializationConfigPaths()) {
298+
try (JsonWriter writer = new JsonWriter(Paths.get(uri))) {
299+
p.getSerializationConfiguration().printJson(writer);
300+
}
301+
}
290302
}
291303

292304
private static void generateFilterRules(Iterator<String> argsIter) throws IOException {

0 commit comments

Comments
 (0)