diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java index 888fe10a30861..65cf812844398 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java @@ -107,6 +107,21 @@ public static void register(boolean finalIsWritable, Field... fields) { ImageSingletons.lookup(RuntimeReflectionSupport.class).register(finalIsWritable, false, fields); } + /** + * Makes the provided fields available for reflection at run time. The fields will be returned + * by {@link Class#getField}, {@link Class#getFields},and all the other methods on {@link Class} + * that return a single or a list of fields. + * + * @param finalIsWritable for all of the passed fields which are marked {@code final}, indicates + * whether it should be possible to change their value using reflection. + * @param allowUnsafeAccess for all of the passed fields, indicates whether it should be + * possible to access by unsafe operations. + * @since 21.0 + */ + public static void register(boolean finalIsWritable, boolean allowUnsafeAccess, Field... fields) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(finalIsWritable, allowUnsafeAccess, fields); + } + /** * Makes the provided classes available for reflective instantiation by * {@link Class#newInstance}. This is equivalent to registering the nullary constructors of the diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 88ab1940a82a2..41f887f627f1c 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -26,6 +26,7 @@ import static com.oracle.svm.core.util.VMError.guarantee; import static com.oracle.svm.jni.JNIObjectHandles.nullHandle; +import static com.oracle.svm.jvmtiagentbase.Support.callObjectMethod; import static com.oracle.svm.jvmtiagentbase.Support.check; import static com.oracle.svm.jvmtiagentbase.Support.checkJni; import static com.oracle.svm.jvmtiagentbase.Support.checkNoException; @@ -39,11 +40,13 @@ import static com.oracle.svm.jvmtiagentbase.Support.getClassNameOr; import static com.oracle.svm.jvmtiagentbase.Support.getClassNameOrNull; import static com.oracle.svm.jvmtiagentbase.Support.getDirectCallerClass; +import static com.oracle.svm.jvmtiagentbase.Support.getObjectField; import static com.oracle.svm.jvmtiagentbase.Support.getMethodDeclaringClass; import static com.oracle.svm.jvmtiagentbase.Support.getObjectArgument; import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions; import static com.oracle.svm.jvmtiagentbase.Support.jvmtiEnv; import static com.oracle.svm.jvmtiagentbase.Support.jvmtiFunctions; +import static com.oracle.svm.jvmtiagentbase.Support.newObjectL; import static com.oracle.svm.jvmtiagentbase.Support.testException; import static com.oracle.svm.jvmtiagentbase.Support.toCString; import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_BREAKPOINT; @@ -61,6 +64,8 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantLock; +import com.oracle.svm.util.SerializationChecksumCalculator; +import com.oracle.svm.jni.nativeapi.JNIFieldId; import org.graalvm.compiler.core.common.NumUtil; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.UnmanagedMemory; @@ -76,6 +81,7 @@ import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.WordBase; import org.graalvm.word.WordFactory; import com.oracle.svm.core.c.function.CEntryPointOptions; @@ -831,6 +837,121 @@ private static boolean resolveMemberName(JNIEnvironment jni, Breakpoint bp) { return true; } + static class CheckSumCalculator extends SerializationChecksumCalculator.JVMCIAgentCalculator { + private JNIEnvironment jni; + private Breakpoint bp; + + CheckSumCalculator(JNIEnvironment jni, Breakpoint bp) { + this.jni = jni; + this.bp = bp; + } + + @Override + protected WordBase getSuperClass(WordBase clazz) { + return jniFunctions().getGetSuperclass().invoke(jni, (JNIObjectHandle) clazz); + } + + @Override + public Long calculateFromComputeDefaultSUID(WordBase clazz) { + JNIMethodId computeDefaultSUIDMId = agent.handles().getJavaIoObjectStreamClassComputeDefaultSUID(jni, bp.clazz); + JNIValue args = StackValue.get(1, JNIValue.class); + args.setObject((JNIObjectHandle) clazz); + return jniFunctions().getCallStaticLongMethodA().invoke(jni, bp.clazz, computeDefaultSUIDMId, args); + } + + @Override + protected boolean isClassAbstract(WordBase clazz) { + CIntPointer modifiers = StackValue.get(CIntPointer.class); + if (jvmtiFunctions().GetClassModifiers().invoke(jvmtiEnv(), (JNIObjectHandle) clazz, modifiers) != JvmtiError.JVMTI_ERROR_NONE) { + return false; + } + // Checkstyle: allow reflection + return (modifiers.read() & java.lang.reflect.Modifier.ABSTRACT) != 0; + } + + @Override + public String getClassName(WordBase clazz) { + return getClassNameOrNull(jni, (JNIObjectHandle) clazz); + } + } + + private static boolean objectStreamClassConstructor(JNIEnvironment jni, Breakpoint bp) { + JNIObjectHandle serializeTargetClass = getObjectArgument(1); + String serializeTargetClassName = getClassNameOrNull(jni, serializeTargetClass); + long checksum = 0; + List traceCandidates = new ArrayList<>(); + CheckSumCalculator checkSumCalculator = new CheckSumCalculator(jni, bp); + JNIObjectHandle objectStreamClassInstance = newObjectL(jni, bp.clazz, bp.method, serializeTargetClass); + Object result = nullHandle().notEqual(objectStreamClassInstance); + if (clearException(jni)) { + result = false; + } + // Skip Lambda class serialization + if (serializeTargetClassName.contains("$$Lambda$")) { + return true; + } + if (result.equals(true)) { + checksum = checkSumCalculator.calculateChecksum(getConsClassName(jni, bp.clazz, objectStreamClassInstance), serializeTargetClassName, serializeTargetClass); + } + traceCandidates.add(new SerializationInfo(serializeTargetClassName, checksum)); + + /** + * When the ObjectStreamClass instance is created for the given serializeTargetClass, some + * additional ObjectStreamClass instances (usually the super classes) are created + * recursively. Call ObjectStreamClass.getClassDataLayout0() can get all of them. + */ + JNIMethodId getClassDataLayout0MId = agent.handles().getJavaIoObjectStreamClassGetClassDataLayout0(jni, bp.clazz); + JNIObjectHandle dataLayoutArray = callObjectMethod(jni, objectStreamClassInstance, getClassDataLayout0MId); + if (!clearException(jni) && nullHandle().notEqual(dataLayoutArray)) { + int length = jniFunctions().getGetArrayLength().invoke(jni, dataLayoutArray); + // If only 1 element is got from getClassDataLayout0(). it is base ObjectStreamClass + // instance itself. + if (!clearException(jni) && length > 1) { + JNIFieldId hasDataFId = agent.handles().getJavaIOObjectStreamClassClassDataSlotHasData(jni); + JNIFieldId descFId = agent.handles().getJavaIOObjectStreamClassClassDataSlotDesc(jni); + JNIMethodId javaIoObjectStreamClassForClassMId = agent.handles().getJavaIoObjectStreamClassForClass(jni, bp.clazz); + for (int i = 0; i < length; i++) { + JNIObjectHandle classDataSlot = jniFunctions().getGetObjectArrayElement().invoke(jni, dataLayoutArray, i); + boolean hasData = jniFunctions().getGetBooleanField().invoke(jni, classDataSlot, hasDataFId); + if (hasData) { + JNIObjectHandle oscInstanceInSlot = jniFunctions().getGetObjectField().invoke(jni, classDataSlot, descFId); + if (!jniFunctions().getIsSameObject().invoke(jni, oscInstanceInSlot, objectStreamClassInstance)) { + JNIObjectHandle oscClazz = callObjectMethod(jni, oscInstanceInSlot, javaIoObjectStreamClassForClassMId); + String oscClassName = getClassNameOrNull(jni, oscClazz); + traceCandidates.add(new SerializationInfo(oscClassName, + checkSumCalculator.calculateChecksum(getConsClassName(jni, + bp.clazz, oscInstanceInSlot), oscClassName, oscClazz))); + } + } + } + } + } + for (SerializationInfo serializationInfo : traceCandidates) { + if (traceWriter != null) { + traceWriter.traceCall("serialization", + "ObjectStreamClass.", + null, + null, + null, + result, + // serializeTargetClassName, checksum); + serializationInfo.className, serializationInfo.checksum); + guarantee(!testException(jni)); + } + } + return true; + } + + private static String getConsClassName(JNIEnvironment jni, JNIObjectHandle objectStreamClassClazz, JNIObjectHandle objectStreamClassInstance) { + JNIObjectHandle cons = getObjectField(jni, objectStreamClassClazz, objectStreamClassInstance, "cons", "Ljava/lang/reflect/Constructor;"); + String targetConstructorClassName = ""; + if (nullHandle().notEqual(cons)) { + // Compute hashcode from the first unserializable superclass + targetConstructorClassName = getClassNameOrNull(jni, callObjectMethod(jni, cons, agent.handles().javaLangReflectMemberGetDeclaringClass)); + } + return targetConstructorClassName; + } + @CEntryPoint @CEntryPointOptions(prologue = AgentIsolate.Prologue.class) private static void onBreakpoint(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni, @@ -1135,6 +1256,7 @@ private interface BreakpointHandler { brk("java/lang/reflect/Proxy", "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;", BreakpointInterceptor::newProxyInstance), + brk("java/io/ObjectStreamClass", "", "(Ljava/lang/Class;)V", BreakpointInterceptor::objectStreamClassConstructor), optionalBrk("java/util/ResourceBundle", "getBundleImpl", "(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;", @@ -1264,6 +1386,16 @@ public int hashCode() { } } + private static final class SerializationInfo { + private String className; + private long checksum; + + SerializationInfo(String serializeTargetClassName, long checksum) { + this.className = serializeTargetClassName; + this.checksum = checksum; + } + } + private BreakpointInterceptor() { } } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index 2879e36acda65..c6f7168b3ee3e 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -231,7 +231,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c // They should use the same filter sets, however. AccessAdvisor advisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter); TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler), - mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler)); + mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler)); traceWriter = new TraceProcessorWriterAdapter(processor); } catch (Throwable t) { System.err.println(MESSAGE_PREFIX + t); @@ -424,6 +424,7 @@ private void writeConfigurationFiles() { allConfigFiles.put(ConfigurationFiles.JNI_NAME, p.getJniConfiguration()); allConfigFiles.put(ConfigurationFiles.DYNAMIC_PROXY_NAME, p.getProxyConfiguration()); allConfigFiles.put(ConfigurationFiles.RESOURCES_NAME, p.getResourceConfiguration()); + allConfigFiles.put(ConfigurationFiles.SERIALIZATION_NAME, p.getSerializationConfiguration()); for (Map.Entry configFile : allConfigFiles.entrySet()) { Path tempPath = tempDirectory.resolve(configFile.getKey()); diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index 08f5cd3f01453..041bf442384fa 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -24,6 +24,9 @@ */ package com.oracle.svm.agent; +import static com.oracle.svm.jni.JNIObjectHandles.nullHandle; + +import com.oracle.svm.jni.nativeapi.JNIFieldId; import com.oracle.svm.jni.nativeapi.JNIEnvironment; import com.oracle.svm.jni.nativeapi.JNIMethodId; import com.oracle.svm.jni.nativeapi.JNIObjectHandle; @@ -33,6 +36,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIObjectHandle javaLangClass; final JNIMethodId javaLangClassForName3; + final JNIMethodId javaUtilEnumerationNextElement; final JNIMethodId javaLangClassGetDeclaredMethod; final JNIMethodId javaLangClassGetDeclaredConstructor; final JNIMethodId javaLangClassGetDeclaredField; @@ -53,6 +57,15 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangInvokeMemberNameIsField; private JNIMethodId javaUtilResourceBundleGetBundleImplSLCC; + + // Lazily look for serialization classes + private JNIMethodId javaIoObjectStreamClassComputeDefaultSUID; + private JNIMethodId javaIoObjectStreamClassForClass; + private JNIMethodId javaIoObjectStreamClassGetClassDataLayout0; + private JNIObjectHandle javaIOObjectStreamClassClassDataSlot; + private JNIFieldId javaIOObjectStreamClassClassDataSlotDesc; + private JNIFieldId javaIOObjectStreamClassClassDataSlotHasData; + private boolean queriedJavaUtilResourceBundleGetBundleImplSLCC; NativeImageAgentJNIHandleSet(JNIEnvironment env) { @@ -70,6 +83,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { JNIObjectHandle javaUtilEnumeration = findClass(env, "java/util/Enumeration"); javaUtilEnumerationHasMoreElements = getMethodId(env, javaUtilEnumeration, "hasMoreElements", "()Z", false); + javaUtilEnumerationNextElement = getMethodId(env, javaUtilEnumeration, "nextElement", "()Ljava/lang/Object;", false); javaLangClassLoader = newClassGlobalRef(env, "java/lang/ClassLoader"); @@ -91,4 +105,46 @@ JNIMethodId tryGetJavaUtilResourceBundleGetBundleImplSLCC(JNIEnvironment env) { } return javaUtilResourceBundleGetBundleImplSLCC; } + + JNIMethodId getJavaIoObjectStreamClassComputeDefaultSUID(JNIEnvironment env, JNIObjectHandle javaIoObjectStreamClass) { + if (javaIoObjectStreamClassComputeDefaultSUID.equal(nullHandle())) { + javaIoObjectStreamClassComputeDefaultSUID = getMethodId(env, javaIoObjectStreamClass, "computeDefaultSUID", "(Ljava/lang/Class;)J", true); + } + return javaIoObjectStreamClassComputeDefaultSUID; + } + + JNIMethodId getJavaIoObjectStreamClassForClass(JNIEnvironment env, JNIObjectHandle javaIoObjectStreamClass) { + if (javaIoObjectStreamClassForClass.equal(nullHandle())) { + javaIoObjectStreamClassForClass = getMethodId(env, javaIoObjectStreamClass, "forClass", "()Ljava/lang/Class;", false); + } + return javaIoObjectStreamClassForClass; + } + + JNIMethodId getJavaIoObjectStreamClassGetClassDataLayout0(JNIEnvironment env, JNIObjectHandle javaIoObjectStreamClass) { + if (javaIoObjectStreamClassGetClassDataLayout0.equal(nullHandle())) { + javaIoObjectStreamClassGetClassDataLayout0 = getMethodId(env, javaIoObjectStreamClass, "getClassDataLayout0", "()[Ljava/io/ObjectStreamClass$ClassDataSlot;", false); + } + return javaIoObjectStreamClassGetClassDataLayout0; + } + + JNIObjectHandle getJavaIOObjectStreamClassClassDataSlot(JNIEnvironment env) { + if (javaIOObjectStreamClassClassDataSlot.equal(nullHandle())) { + javaIOObjectStreamClassClassDataSlot = newClassGlobalRef(env, "java/io/ObjectStreamClass$ClassDataSlot"); + } + return javaIOObjectStreamClassClassDataSlot; + } + + JNIFieldId getJavaIOObjectStreamClassClassDataSlotDesc(JNIEnvironment env) { + if (javaIOObjectStreamClassClassDataSlotDesc.equal(nullHandle())) { + javaIOObjectStreamClassClassDataSlotDesc = getFieldId(env, getJavaIOObjectStreamClassClassDataSlot(env), "desc", "Ljava/io/ObjectStreamClass;", false); + } + return javaIOObjectStreamClassClassDataSlotDesc; + } + + JNIFieldId getJavaIOObjectStreamClassClassDataSlotHasData(JNIEnvironment env) { + if (javaIOObjectStreamClassClassDataSlotHasData.equal(nullHandle())) { + javaIOObjectStreamClassClassDataSlotHasData = getFieldId(env, getJavaIOObjectStreamClassClassDataSlot(env), "hasData", "Z", false); + } + return javaIOObjectStreamClassClassDataSlotHasData; + } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java index af33cafe6704c..3cc8235311381 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java @@ -189,6 +189,12 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA set.getResourceConfigPaths().add(requirePathUri(current, value)); break; + case "--serialization-input": + set = inputSet; // fall through + case "--serialization-output": + set.getSerializationConfigPaths().add(requirePathUri(current, value)); + break; + case "--trace-input": traceInputs.add(requirePathUri(current, value)); break; @@ -249,7 +255,8 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA TraceProcessor p; try { p = new TraceProcessor(advisor, inputSet.loadJniConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadReflectConfig(ConfigurationSet.FAIL_ON_EXCEPTION), - inputSet.loadProxyConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION)); + inputSet.loadProxyConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION), + inputSet.loadSerializationConfig(ConfigurationSet.FAIL_ON_EXCEPTION)); } catch (IOException e) { throw e; } catch (Throwable t) { @@ -287,6 +294,11 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA p.getResourceConfiguration().printJson(writer); } } + for (URI uri : outputSet.getSerializationConfigPaths()) { + try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { + p.getSerializationConfiguration().printJson(writer); + } + } } private static void generateFilterRules(Iterator argsIter) throws IOException { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java index 4bbe8bc0f2765..c07e6d1a4327b 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java @@ -41,6 +41,7 @@ import com.oracle.svm.core.configure.ProxyConfigurationParser; import com.oracle.svm.core.configure.ReflectionConfigurationParser; import com.oracle.svm.core.configure.ResourceConfigurationParser; +import com.oracle.svm.core.configure.SerializationConfigurationParser; public class ConfigurationSet { public static final Function FAIL_ON_EXCEPTION = e -> e; @@ -49,16 +50,18 @@ public class ConfigurationSet { private final Set reflectConfigPaths = new LinkedHashSet<>(); private final Set proxyConfigPaths = new LinkedHashSet<>(); private final Set resourceConfigPaths = new LinkedHashSet<>(); + private final Set serializationConfigPaths = new LinkedHashSet<>(); public void addDirectory(Path path) { jniConfigPaths.add(path.resolve(ConfigurationFiles.JNI_NAME).toUri()); reflectConfigPaths.add(path.resolve(ConfigurationFiles.REFLECTION_NAME).toUri()); proxyConfigPaths.add(path.resolve(ConfigurationFiles.DYNAMIC_PROXY_NAME).toUri()); resourceConfigPaths.add(path.resolve(ConfigurationFiles.RESOURCES_NAME).toUri()); + serializationConfigPaths.add(path.resolve(ConfigurationFiles.SERIALIZATION_NAME).toUri()); } public boolean isEmpty() { - return jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && resourceConfigPaths.isEmpty(); + return jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && resourceConfigPaths.isEmpty() && serializationConfigPaths.isEmpty(); } public Set getJniConfigPaths() { @@ -77,6 +80,10 @@ public Set getResourceConfigPaths() { return resourceConfigPaths; } + public Set getSerializationConfigPaths() { + return serializationConfigPaths; + } + public TypeConfiguration loadJniConfig(Function exceptionHandler) throws Exception { return loadTypeConfig(jniConfigPaths, exceptionHandler); } @@ -97,6 +104,13 @@ public ResourceConfiguration loadResourceConfig(Function return resourceConfiguration; } + public SerializationConfiguration loadSerializationConfig(Function exceptionHandler) throws Exception { + SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); + loadConfig(serializationConfigPaths, new SerializationConfigurationParser((targetSerializationClass, checksum) -> serializationConfiguration.add(targetSerializationClass, checksum)), + exceptionHandler); + return serializationConfiguration; + } + private static TypeConfiguration loadTypeConfig(Collection uris, Function exceptionHandler) throws Exception { TypeConfiguration configuration = new TypeConfiguration(); loadConfig(uris, new ReflectionConfigurationParser<>(new ParserConfigurationAdapter(configuration)), exceptionHandler); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java new file mode 100644 index 0000000000000..f0647c33f6388 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Alibaba Group Holding Limited. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.configure.config; + +import com.oracle.svm.configure.json.JsonPrintable; +import com.oracle.svm.configure.json.JsonWriter; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class SerializationConfiguration implements JsonPrintable { + + private final ConcurrentHashMap> serializations = new ConcurrentHashMap<>(); + + public void add(String serializationTargetClass, Long checksum) { + Set ret = serializations.get(serializationTargetClass); + if (ret == null) { + ret = new HashSet<>(); + serializations.put(serializationTargetClass, ret); + } + ret.add(checksum); + } + + public boolean contains(String serializationTargetClass, Long checksum) { + return serializations.contains(serializationTargetClass) && serializations.get(serializationTargetClass).contains(checksum); + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.append('[').indent(); + String prefix = ""; + for (Map.Entry> entry : serializations.entrySet()) { + for (Long value : entry.getValue()) { + writer.append(prefix); + writer.newline().append('{'); + writer.newline(); + writer.quote("name").append(":").quote(entry.getKey()); + if (value != null) { + writer.append(",").newline(); + writer.quote("checksum").append(':').append(value.toString()).newline(); + } else { + writer.newline(); + } + writer.append('}'); + prefix = ","; + } + } + writer.unindent().newline(); + writer.append(']').newline(); + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java index 877fee63b8389..8e7966ce9a083 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java @@ -78,6 +78,7 @@ public final class AccessAdvisor { internalCallerFilter.addOrGetChildren("org.graalvm.compiler.**", RuleNode.Inclusion.Exclude); internalCallerFilter.addOrGetChildren("org.graalvm.libgraal.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.removeRedundantNodes(); accessWithoutCallerFilter = RuleNode.createRoot(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java new file mode 100644 index 0000000000000..af2d385be29d2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Alibaba Group Holding Limited. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.configure.trace; + +import com.oracle.svm.configure.config.SerializationConfiguration; + +import java.util.List; +import java.util.Map; + +public class SerializationProcessor extends AbstractProcessor { + private final SerializationConfiguration serializationConfiguration; + + public SerializationProcessor(SerializationConfiguration serializationConfiguration) { + this.serializationConfiguration = serializationConfiguration; + } + + public SerializationConfiguration getSerializationConfiguration() { + return serializationConfiguration; + } + + @Override + void processEntry(Map entry) { + boolean invalidResult = Boolean.FALSE.equals(entry.get("result")); + if (invalidResult) { + return; + } + String function = (String) entry.get("function"); + List args = (List) entry.get("args"); + if ("ObjectStreamClass.".equals(function)) { + expectSize(args, 2); + serializationConfiguration.add((String) args.get(0), (Long) args.get(1)); + } + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java index 090f59b6dfafd..1732476a20f4a 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java @@ -31,6 +31,7 @@ import com.oracle.svm.configure.config.ProxyConfiguration; import com.oracle.svm.configure.config.ResourceConfiguration; +import com.oracle.svm.configure.config.SerializationConfiguration; import com.oracle.svm.configure.config.TypeConfiguration; import com.oracle.svm.core.util.json.JSONParser; @@ -38,12 +39,14 @@ public class TraceProcessor extends AbstractProcessor { private final AccessAdvisor advisor; private final JniProcessor jniProcessor; private final ReflectionProcessor reflectionProcessor; + private final SerializationProcessor serializationProcessor; public TraceProcessor(AccessAdvisor accessAdvisor, TypeConfiguration jniConfiguration, TypeConfiguration reflectionConfiguration, - ProxyConfiguration proxyConfiguration, ResourceConfiguration resourceConfiguration) { + ProxyConfiguration proxyConfiguration, ResourceConfiguration resourceConfiguration, SerializationConfiguration serializationConfiguration) { advisor = accessAdvisor; jniProcessor = new JniProcessor(this.advisor, jniConfiguration, reflectionConfiguration); reflectionProcessor = new ReflectionProcessor(this.advisor, reflectionConfiguration, proxyConfiguration, resourceConfiguration); + serializationProcessor = new SerializationProcessor(serializationConfiguration); } public TypeConfiguration getJniConfiguration() { @@ -62,6 +65,10 @@ public ResourceConfiguration getResourceConfiguration() { return reflectionProcessor.getResourceConfiguration(); } + public SerializationConfiguration getSerializationConfiguration() { + return serializationProcessor.getSerializationConfiguration(); + } + @SuppressWarnings("unchecked") public void process(Reader reader) throws IOException { setInLivePhase(false); @@ -98,6 +105,9 @@ public void processEntry(Map entry) { case "reflect": reflectionProcessor.processEntry(entry); break; + case "serialization": + serializationProcessor.processEntry(entry); + break; default: logWarning("Unknown tracer, ignoring: " + tracer); break; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java index 94b9d2c4aca2c..c720043bbafb2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java @@ -55,9 +55,15 @@ public final class ClassInitializationInfo { /** * Singleton for classes that are already initialized during image building and do not need - * class initialization at runtime. + * class initialization at runtime, but have {@code } methods. */ - public static final ClassInitializationInfo INITIALIZED_INFO_SINGLETON = new ClassInitializationInfo(InitState.FullyInitialized); + public static final ClassInitializationInfo INITIALIZED_INFO_SINGLETON = new ClassInitializationInfo(InitState.FullyInitialized, true); + + /** + * Singleton for classes that are already initialized during image building and do not need + * class initialization at runtime, and don't have {@code } methods. + */ + public static final ClassInitializationInfo NO_INITIALIZER_INFO_SINGLETON = new ClassInitializationInfo(InitState.FullyInitialized, false); /** Singleton for classes that failed to link during image building. */ public static final ClassInitializationInfo FAILED_INFO_SINGLETON = new ClassInitializationInfo(InitState.InitializationError); @@ -124,11 +130,24 @@ public static class ClassInitializerFunctionPointerHolder { */ private Condition initCondition; + /** + * Indicates if the class has a {@code } method, no matter if it should be initialized + * at native image's build time or run time. + */ + private boolean hasInitializer; + + @Platforms(Platform.HOSTED_ONLY.class) + private ClassInitializationInfo(InitState initState, boolean hasInitializer) { + this(initState); + this.hasInitializer = hasInitializer; + } + @Platforms(Platform.HOSTED_ONLY.class) private ClassInitializationInfo(InitState initState) { this.classInitializer = null; this.initState = initState; this.initLock = initState == InitState.FullyInitialized ? null : new ReentrantLock(); + this.hasInitializer = true; } @Platforms(Platform.HOSTED_ONLY.class) @@ -136,6 +155,11 @@ public ClassInitializationInfo(CFunctionPointer classInitializer) { this.classInitializer = classInitializer == null || classInitializer.isNull() ? null : new ClassInitializerFunctionPointerHolder(classInitializer); this.initState = InitState.Linked; this.initLock = new ReentrantLock(); + this.hasInitializer = classInitializer != null; + } + + public boolean hasInitializer() { + return hasInitializer; } public boolean isInitialized() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index 67f8d6978bef2..ab9b894ddcfcf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -54,6 +54,7 @@ public final class ConfigurationFiles { public static final String RESOURCES_NAME = "resource" + SUFFIX; public static final String JNI_NAME = "jni" + SUFFIX; public static final String REFLECTION_NAME = "reflect" + SUFFIX; + public static final String SERIALIZATION_NAME = "serialization" + SUFFIX; public static final class Options { @Option(help = "Directories directly containing configuration files for dynamic features at runtime.", type = OptionType.User)// @@ -72,6 +73,11 @@ public static final class Options { @Option(help = "Resources describing program elements to be made available for reflection (see ProxyConfigurationFiles).", type = OptionType.User)// public static final HostedOptionKey DynamicProxyConfigurationResources = new HostedOptionKey<>(null); + @Option(help = "file:doc-files/SerializationConfigurationFilesHelp.txt", type = OptionType.User)// + public static final HostedOptionKey SerializationConfigurationFiles = new HostedOptionKey<>(null); + @Option(help = "Resources describing program elements to be made available for serialization (see SerializationConfigurationFiles).", type = OptionType.User)// + public static final HostedOptionKey SerializationConfigurationResources = new HostedOptionKey<>(null); + @Option(help = "Files describing Java resources to be included in the image.", type = OptionType.User)// public static final HostedOptionKey ResourceConfigurationFiles = new HostedOptionKey<>(new String[0]); @Option(help = "Resources describing Java resources to be included in the image.", type = OptionType.User)// diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java index 503228648b908..2d294af067a1b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java @@ -71,4 +71,15 @@ protected static boolean asBoolean(Object value, String propertyName) { } throw new JSONParserException("Invalid boolean value '" + value + "' for element '" + propertyName + "'"); } + + protected static long asLong(Object value, String propertyName) { + if (value instanceof Long) { + return (long) value; + } + if (value instanceof Integer) { + int intValue = (int) value; + return intValue; + } + throw new JSONParserException("Invalid long value '" + value + "' for element '" + propertyName + "'"); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java new file mode 100644 index 0000000000000..79010cb6fab53 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Alibaba Group Holding Limited. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.configure; + +import com.oracle.svm.core.util.json.JSONParser; + +import java.io.IOException; +import java.io.Reader; +import java.util.Map; + +public class SerializationConfigurationParser extends ConfigurationParser { + private final SerializationParserFunction consumer; + + public SerializationConfigurationParser(SerializationParserFunction consumer) { + this.consumer = consumer; + } + + @Override + public void parseAndRegister(Reader reader) throws IOException { + JSONParser parser = new JSONParser(reader); + Object json = parser.parse(); + for (Object serializationKey : asList(json, "first level of document must be an array of serialization lists")) { + Map data = asMap(serializationKey, "second level of document must be serialization descriptor objects "); + String targetSerializationClass = asString(data.get("name")); + Object checksumValue = data.get("checksum"); + Long checksum = checksumValue == null ? null : asLong(checksumValue, "checksum"); + consumer.accept(targetSerializationClass, checksum); + } + } + + @FunctionalInterface + public interface SerializationParserFunction { + void accept(String targetSerializationClass, Long checksum); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt new file mode 100644 index 0000000000000..215f3f294be81 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt @@ -0,0 +1,13 @@ +One or several (comma-separated) paths to JSON files that specify lists of serialization configurations. +The structure is an array of elements specifying the target serialization/deserialization class name and a checksum automatically calculated from its class contents and hierarchy by native-image-agent. +The checksum is used to verify the serialization target class is consistency at configuration collecting time, image build time and image run time. +Checksum is optional, there will be no verification when the checksum is absent. + +Example: + + [ + { + "name":"java.util.ArrayList", + "checksum":1848188320 + } + ] \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaIOSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaIOSubstitutions.java index f9437d1d3b81e..66d00bc15a7e5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaIOSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaIOSubstitutions.java @@ -25,8 +25,11 @@ package com.oracle.svm.core.jdk; import java.io.Closeable; +import java.lang.ref.ReferenceQueue; import java.util.LinkedHashSet; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantLock; import com.oracle.svm.core.annotate.Alias; @@ -35,7 +38,7 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.hub.DynamicHub; @TargetClass(java.io.FileDescriptor.class) final class Target_java_io_FileDescriptor { @@ -47,31 +50,40 @@ final class Target_java_io_FileDescriptor { @TargetClass(java.io.ObjectInputStream.class) @SuppressWarnings({"static-method"}) final class Target_java_io_ObjectInputStream { - + /** + * Private method latestUserDefinedLoader is called by + * java.io.ObjectInputStream.resolveProxyClass and java.io.ObjectInputStream.resolveClass. The + * returned classloader is eventually used in Class.forName and Proxy.getProxyClass0 which are + * substituted by Substrate VM and the classloader is ignored. Therefore, this substitution is + * safe. + * + * @return The only classloader in native image + */ @Substitute - private Object readObject() { - throw VMError.unsupportedFeature("ObjectInputStream.readObject()"); + private static ClassLoader latestUserDefinedLoader() { + return Target_java_io_ObjectInputStream.class.getClassLoader(); } +} + +@TargetClass(java.io.ObjectStreamClass.class) +final class Target_java_io_ObjectStreamClass { @Substitute - private Object readUnshared() { - throw VMError.unsupportedFeature("ObjectInputStream.readUnshared()"); + private static boolean hasStaticInitializer(Class cl) { + return DynamicHub.fromClass(cl).getClassInitializationInfo().hasInitializer(); } } -@TargetClass(java.io.ObjectOutputStream.class) -@SuppressWarnings({"static-method", "unused"}) -final class Target_java_io_ObjectOutputStream { +@TargetClass(value = java.io.ObjectStreamClass.class, innerClass = "Caches") +final class Target_java_io_ObjectStreamClass_Caches { - @Substitute - private void writeObject(Object obj) { - throw VMError.unsupportedFeature("ObjectOutputStream.writeObject()"); - } + @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClass = ConcurrentHashMap.class) static ConcurrentMap localDescs; - @Substitute - private void writeUnshared(Object obj) { - throw VMError.unsupportedFeature("ObjectOutputStream.writeUnshared()"); - } + @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClass = ConcurrentHashMap.class) static ConcurrentMap reflectors; + + @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClass = ReferenceQueue.class) private static ReferenceQueue> localDescsQueue; + + @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClass = ReferenceQueue.class) private static ReferenceQueue> reflectorsQueue; } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Package_jdk_internal_reflect.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Package_jdk_internal_reflect.java index 7d66089980df4..81fb69ca7fbc3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Package_jdk_internal_reflect.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Package_jdk_internal_reflect.java @@ -36,10 +36,14 @@ public class Package_jdk_internal_reflect implements Function { @Override public String apply(TargetClass annotation) { + return getQualifiedName() + "." + annotation.className(); + } + + public static String getQualifiedName() { if (JavaVersionUtil.JAVA_SPEC <= 8) { - return "sun.reflect." + annotation.className(); + return "sun.reflect"; } else { - return "jdk.internal.reflect." + annotation.className(); + return "jdk.internal.reflect"; } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_AccessorGenerator.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_AccessorGenerator.java index 2bca6a162cfd7..b040dce26e5db 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_AccessorGenerator.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_AccessorGenerator.java @@ -24,15 +24,30 @@ */ package com.oracle.svm.core.jdk; -import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.serialize.SerializationRegistry; +import org.graalvm.nativeimage.ImageSingletons; -@Delete @TargetClass(classNameProvider = Package_jdk_internal_reflect.class, className = "AccessorGenerator") public final class Target_jdk_internal_reflect_AccessorGenerator { } -@Delete @TargetClass(classNameProvider = Package_jdk_internal_reflect.class, className = "MethodAccessorGenerator") final class Target_jdk_internal_reflect_MethodAccessorGenerator { + + @SuppressWarnings("static-method") + @Substitute + public Target_jdk_internal_reflect_SerializationConstructorAccessorImpl generateSerializationConstructor(Class declaringClass, + @SuppressWarnings("unused") Class[] parameterTypes, + @SuppressWarnings("unused") Class[] checkedExceptions, + @SuppressWarnings("unused") int modifiers, + Class targetConstructorClass) { + SerializationRegistry serializationRegistry = ImageSingletons.lookup(SerializationRegistry.class); + return (Target_jdk_internal_reflect_SerializationConstructorAccessorImpl) serializationRegistry.getSerializationConstructorAccessorClass(declaringClass, targetConstructorClass.getName()); + } +} + +@TargetClass(classNameProvider = Package_jdk_internal_reflect.class, className = "SerializationConstructorAccessorImpl") +final class Target_jdk_internal_reflect_SerializationConstructorAccessorImpl { } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/SerializationRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/SerializationRegistry.java new file mode 100644 index 0000000000000..a11536c8786ed --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/SerializationRegistry.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Alibaba Group Holding Limited. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.jdk.serialize; + +public interface SerializationRegistry { + + Object getSerializationConstructorAccessorClass(Class serializationTargetClass, String targetConstructorClass); + +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/json/JSONParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/json/JSONParser.java index 4b9c5f5eb7076..2209b86473e92 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/json/JSONParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/json/JSONParser.java @@ -278,6 +278,7 @@ private void skipDigits() { } private Number parseNumber() { + boolean isFloating = false; final int start = pos; int c = next(); @@ -294,6 +295,7 @@ private Number parseNumber() { // fraction if (peek() == '.') { + isFloating = true; pos++; if (!isDigit(next())) { throw numberError(pos - 1); @@ -315,13 +317,17 @@ private Number parseNumber() { skipDigits(); } - final double d = Double.parseDouble(source.substring(start, pos)); - if ((int) d == d) { - return (int) d; - } else if ((long) d == d) { - return (long) d; + String literalValue = source.substring(start, pos); + if (isFloating) { + return Double.parseDouble(literalValue); + } else { + final long l = Long.parseLong(literalValue); + if ((int) l == l) { + return (int) l; + } else { + return l; + } } - return d; } private Object parseKeyword(final String keyword, final Object value) { diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index dfc4cd4477f72..39013e60547be 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -187,6 +187,7 @@ private static String oR(OptionKey option) { final String oHDynamicProxyConfigurationFiles = oH(ConfigurationFiles.Options.DynamicProxyConfigurationFiles); final String oHResourceConfigurationFiles = oH(ConfigurationFiles.Options.ResourceConfigurationFiles); final String oHJNIConfigurationFiles = oH(ConfigurationFiles.Options.JNIConfigurationFiles); + final String oHSerializationConfigurationFiles = oH(ConfigurationFiles.Options.SerializationConfigurationFiles); final String oHInspectServerContentPath = oH(PointstoOptions.InspectServerContentPath); final String oHDeadlockWatchdogInterval = oH(SubstrateOptions.DeadlockWatchdogInterval); @@ -863,7 +864,8 @@ enum MetaInfFileType { JniConfiguration(ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFiles.JNI_NAME), ReflectConfiguration(ConfigurationFiles.Options.ReflectionConfigurationResources, ConfigurationFiles.REFLECTION_NAME), ResourceConfiguration(ConfigurationFiles.Options.ResourceConfigurationResources, ConfigurationFiles.RESOURCES_NAME), - ProxyConfiguration(ConfigurationFiles.Options.DynamicProxyConfigurationResources, ConfigurationFiles.DYNAMIC_PROXY_NAME); + ProxyConfiguration(ConfigurationFiles.Options.DynamicProxyConfigurationResources, ConfigurationFiles.DYNAMIC_PROXY_NAME), + SerializationConfiguration(ConfigurationFiles.Options.SerializationConfigurationResources, ConfigurationFiles.SERIALIZATION_NAME); final OptionKey optionKey; final String fileName; @@ -1095,6 +1097,7 @@ private int completeImageBuild() { consolidateListArgs(imageBuilderArgs, oHJNIConfigurationFiles, ",", canonicalizedPathStr); consolidateListArgs(imageBuilderArgs, oHTraceClassInitialization, ",", Function.identity()); consolidateListArgs(imageBuilderArgs, oHTraceObjectInstantiation, ",", Function.identity()); + consolidateListArgs(imageBuilderArgs, oHSerializationConfigurationFiles, ",", canonicalizedPathStr); imageBuilderJavaArgs.addAll(getAgentArguments()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FallbackFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FallbackFeature.java index 07e0ff3909933..4a3873056cabd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FallbackFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FallbackFeature.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.hosted; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -58,6 +60,7 @@ public class FallbackFeature implements Feature { private final List resourceCalls = new ArrayList<>(); private final List jniCalls = new ArrayList<>(); private final List proxyCalls = new ArrayList<>(); + private final List serializationCalls = new ArrayList<>(); private static class AutoProxyInvoke { private final ResolvedJavaMethod method; @@ -167,6 +170,11 @@ public FallbackFeature() { addCheck(Proxy.class.getMethod("newProxyInstance", ClassLoader.class, Class[].class, InvocationHandler.class), this::collectProxyInvokes); addCheck(System.class.getMethod("loadLibrary", String.class), this::collectJNIInvokes); + + addCheck(ObjectInputStream.class.getMethod("readObject"), this::collectDeserializationInvokes); + addCheck(ObjectInputStream.class.getMethod("readUnshared"), this::collectDeserializationInvokes); + addCheck(ObjectOutputStream.class.getMethod("writeObject", Object.class), this::collectSerializationInvokes); + addCheck(ObjectOutputStream.class.getMethod("writeUnshared", Object.class), this::collectSerializationInvokes); } catch (NoSuchMethodException e) { throw VMError.shouldNotReachHere("Registering ReflectionInvocationChecks failed", e); } @@ -190,6 +198,14 @@ private void collectProxyInvokes(ReflectionInvocationCheck check, BytecodePositi } } + private void collectDeserializationInvokes(ReflectionInvocationCheck check, BytecodePosition invokeLocation) { + serializationCalls.add("Deserialization method " + check.locationString(invokeLocation)); + } + + private void collectSerializationInvokes(ReflectionInvocationCheck check, BytecodePosition invokeLocation) { + serializationCalls.add("Serialization method " + check.locationString(invokeLocation)); + } + static FallbackImageRequest reportFallback(String message) { return reportFallback(message, null); } @@ -255,6 +271,7 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { public FallbackImageRequest resourceFallback = null; public FallbackImageRequest jniFallback = null; public FallbackImageRequest proxyFallback = null; + public FallbackImageRequest serializationFallback = null; @Override public void afterAnalysis(AfterAnalysisAccess a) { @@ -296,5 +313,9 @@ public void afterAnalysis(AfterAnalysisAccess a) { proxyCalls.add(ABORT_MSG_PREFIX + " due to dynamic proxy use without configuration."); proxyFallback = new FallbackImageRequest(proxyCalls); } + if (!serializationCalls.isEmpty()) { + serializationCalls.add(ABORT_MSG_PREFIX + " due to serialization use without configuration."); + serializationFallback = new FallbackImageRequest(serializationCalls); + } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java index 3be78d7f87378..5892dd9195cd9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java @@ -322,7 +322,9 @@ private Set initializeSafeDelayedClasses(TypeInitializerGraph init */ if (!classInitializationSupport.shouldInitializeAtRuntime(c)) { provenSafe.add(type); - ((SVMHost) universe.hostVM()).dynamicHub(type).setClassInitializationInfo(ClassInitializationInfo.INITIALIZED_INFO_SINGLETON); + ClassInitializationInfo initializationInfo = type.getClassInitializer() == null ? ClassInitializationInfo.NO_INITIALIZER_INFO_SINGLETON + : ClassInitializationInfo.INITIALIZED_INFO_SINGLETON; + ((SVMHost) universe.hostVM()).dynamicHub(type).setClassInitializationInfo(initializationInfo); } } }); @@ -344,7 +346,7 @@ private void buildClassInitializationInfo(FeatureImpl.DuringAnalysisAccessImpl a info = buildRuntimeInitializationInfo(access, type); } else { assert type.isInitialized(); - info = ClassInitializationInfo.INITIALIZED_INFO_SINGLETON; + info = type.getClassInitializer() == null ? ClassInitializationInfo.NO_INITIALIZER_INFO_SINGLETON : ClassInitializationInfo.INITIALIZED_INFO_SINGLETON; } hub.setClassInitializationInfo(info, type.hasDefaultMethods(), type.declaresDefaultMethods()); } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/nativeapi/JNIFunctionPointerTypes.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/nativeapi/JNIFunctionPointerTypes.java index 4f88c7a7da8bd..3728cdc6dcdd7 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/nativeapi/JNIFunctionPointerTypes.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/nativeapi/JNIFunctionPointerTypes.java @@ -212,6 +212,36 @@ public interface ReleaseByteArrayElementsFunctionPointer extends CFunctionPointe CCharPointer invoke(JNIEnvironment env, JNIObjectHandle byteArray, CCharPointer elements, int mode); } + public interface GetObjectFieldFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + JNIObjectHandle invoke(JNIEnvironment env, JNIObjectHandle obj, JNIFieldId fieldId); + } + + public interface GetBooleanFieldFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + boolean invoke(JNIEnvironment env, JNIObjectHandle obj, JNIFieldId fieldId); + } + + public interface CallStaticObjectMethodAFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + JNIObjectHandle invoke(JNIEnvironment env, JNIObjectHandle clazz, JNIMethodId methodID, JNIValue args); + } + + public interface GetStaticObjectFieldFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + JNIObjectHandle invoke(JNIEnvironment env, JNIObjectHandle clazz, JNIFieldId fieldID); + } + + public interface CallStaticLongMethodAFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + long invoke(JNIEnvironment env, JNIObjectHandle clazz, JNIMethodId methodID, JNIValue args); + } + + public interface IsSameObjectFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + boolean invoke(JNIEnvironment env, JNIObjectHandle obj1, JNIObjectHandle obj2); + } + private JNIFunctionPointerTypes() { } } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/nativeapi/JNINativeInterface.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/nativeapi/JNINativeInterface.java index 90d09aeb6f655..b0ee0cb97cce8 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/nativeapi/JNINativeInterface.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/nativeapi/JNINativeInterface.java @@ -39,6 +39,8 @@ import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallBooleanMethodAFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallLongMethodAFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallObjectMethodAFunctionPointer; +import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallStaticLongMethodAFunctionPointer; +import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallStaticObjectMethodAFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.DefineClassFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.DeleteGlobalRefFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.ExceptionCheckFunctionPointer; @@ -52,9 +54,13 @@ import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.GetMethodIDFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.GetObjectArrayElementFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.GetObjectClassFunctionPointer; +import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.GetBooleanFieldFunctionPointer; +import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.GetObjectFieldFunctionPointer; +import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.GetStaticObjectFieldFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.GetStringUTFCharsFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.GetSuperclassFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.IsAssignableFromFunctionPointer; +import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.IsSameObjectFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.NewGlobalRefFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.NewObjectAFunctionPointer; import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.NewObjectArrayFunctionPointer; @@ -204,10 +210,10 @@ public interface JNINativeInterface extends PointerBase { void setDeleteLocalRef(CFunctionPointer p); @CField - CFunctionPointer getIsSameObject(); + IsSameObjectFunctionPointer getIsSameObject(); @CField - void setIsSameObject(CFunctionPointer p); + void setIsSameObject(IsSameObjectFunctionPointer p); @CField CFunctionPointer getNewLocalRef(); @@ -630,16 +636,16 @@ public interface JNINativeInterface extends PointerBase { void setGetFieldID(GetFieldIDFunctionPointer p); @CField - CFunctionPointer getGetObjectField(); + GetObjectFieldFunctionPointer getGetObjectField(); @CField - void setGetObjectField(CFunctionPointer p); + void setGetObjectField(GetObjectFieldFunctionPointer p); @CField - CFunctionPointer getGetBooleanField(); + GetBooleanFieldFunctionPointer getGetBooleanField(); @CField - void setGetBooleanField(CFunctionPointer p); + void setGetBooleanField(GetBooleanFieldFunctionPointer p); @CField CFunctionPointer getGetByteField(); @@ -756,10 +762,10 @@ public interface JNINativeInterface extends PointerBase { void setCallStaticObjectMethodV(CFunctionPointer p); @CField - CallObjectMethodAFunctionPointer getCallStaticObjectMethodA(); + CallStaticObjectMethodAFunctionPointer getCallStaticObjectMethodA(); @CField - void setCallStaticObjectMethodA(CallObjectMethodAFunctionPointer p); + void setCallStaticObjectMethodA(CallStaticObjectMethodAFunctionPointer p); @CField CFunctionPointer getCallStaticBooleanMethod(); @@ -864,10 +870,10 @@ public interface JNINativeInterface extends PointerBase { void setCallStaticLongMethodV(CFunctionPointer p); @CField - CallLongMethodAFunctionPointer getCallStaticLongMethodA(); + CallStaticLongMethodAFunctionPointer getCallStaticLongMethodA(); @CField - void setCallStaticLongMethodA(CallLongMethodAFunctionPointer p); + void setCallStaticLongMethodA(CallStaticLongMethodAFunctionPointer p); @CField CFunctionPointer getCallStaticFloatMethod(); @@ -930,7 +936,7 @@ public interface JNINativeInterface extends PointerBase { void setGetStaticFieldID(GetFieldIDFunctionPointer p); @CField - CFunctionPointer getGetStaticObjectField(); + GetStaticObjectFieldFunctionPointer getGetStaticObjectField(); @CField void setGetStaticObjectField(CFunctionPointer p); diff --git a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/JNIHandleSet.java b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/JNIHandleSet.java index da41544adb6b4..15f59dcaea495 100644 --- a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/JNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/JNIHandleSet.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock; +import com.oracle.svm.jni.nativeapi.JNIFieldId; import org.graalvm.nativeimage.c.type.CTypeConversion; import com.oracle.svm.jni.JNIObjectHandles; @@ -144,6 +145,24 @@ public JNIMethodId getMethodIdOptional(JNIEnvironment env, JNIObjectHandle clazz } } + public JNIFieldId getFieldId(JNIEnvironment env, JNIObjectHandle clazz, String name, String signature, boolean isStatic) { + assert !destroyed; + JNIFieldId id = getFieldIdOptional(env, clazz, name, signature, isStatic); + guarantee(id.isNonNull()); + return id; + } + + public JNIFieldId getFieldIdOptional(JNIEnvironment env, JNIObjectHandle clazz, String name, String signature, boolean isStatic) { + assert !destroyed; + try (CTypeConversion.CCharPointerHolder cname = Support.toCString(name); CTypeConversion.CCharPointerHolder csignature = Support.toCString(signature)) { + if (isStatic) { + return Support.jniFunctions().getGetStaticFieldID().invoke(env, clazz, cname.get(), csignature.get()); + } else { + return Support.jniFunctions().getGetFieldID().invoke(env, clazz, cname.get(), csignature.get()); + } + } + } + /** * Creates a global JNI handle of the specified local JNI handle. The handle will be tracked and * freed with a call to {@link #destroy}. The handle must not be freed manually. diff --git a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java index 76487d1d67f39..0e3f710087005 100644 --- a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java +++ b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java @@ -254,6 +254,18 @@ public static String getMethodNameOr(JNIMethodId methodId, String defaultValue) return methodName; } + public static JNIObjectHandle getObjectField(JNIEnvironment env, JNIObjectHandle clazz, JNIObjectHandle obj, String name, String signature) { + try (CCharPointerHolder nameHolder = toCString(name); + CCharPointerHolder sigHolder = toCString(signature);) { + JNIFieldId fieldId = jniFunctions().getGetFieldID().invoke(env, clazz, nameHolder.get(), sigHolder.get()); + if (nullHandle().notEqual(fieldId)) { + return jniFunctions().getGetObjectField().invoke(env, obj, fieldId); + } else { + return nullHandle(); + } + } + } + public static boolean clearException(JNIEnvironment localEnv) { if (jniFunctions().getExceptionCheck().invoke(localEnv)) { jniFunctions().getExceptionClear().invoke(localEnv); @@ -385,6 +397,12 @@ public static int callIntMethodL(JNIEnvironment env, JNIObjectHandle obj, JNIMet return jniFunctions().getCallIntMethodA().invoke(env, obj, method, args); } + public static JNIObjectHandle newObjectL(JNIEnvironment env, JNIObjectHandle clazz, JNIMethodId ctor, JNIObjectHandle l0) { + JNIValue args = StackValue.get(1, JNIValue.class); + args.addressOf(0).setObject(l0); + return jniFunctions().getNewObjectA().invoke(env, clazz, ctor, args); + } + public static JNIObjectHandle newObjectLLLJ(JNIEnvironment env, JNIObjectHandle clazz, JNIMethodId ctor, JNIObjectHandle l0, JNIObjectHandle l1, JNIObjectHandle l2, long l3) { JNIValue args = StackValue.get(4, JNIValue.class); args.addressOf(0).setObject(l0); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/SerializationSupport.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/SerializationSupport.java new file mode 100644 index 0000000000000..6a591007b935e --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/SerializationSupport.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Alibaba Group Holding Limited. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.reflect.serialize; + +// Checkstyle: allow reflection + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.svm.core.jdk.Package_jdk_internal_reflect; +import com.oracle.svm.core.jdk.serialize.SerializationRegistry; +import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.NativeImageOptions; +import com.oracle.svm.util.ReflectionUtil; +import com.oracle.svm.util.SerializationChecksumCalculator; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature.FeatureAccess; + +import java.io.Externalizable; +import java.io.ObjectStreamClass; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SerializationSupport implements SerializationRegistry { + + class ChecksumCalculator extends SerializationChecksumCalculator.JavaCalculator { + @Override + protected String getClassName(Class clazz) { + return clazz.getName(); + } + + @Override + protected Class getSuperClass(Class clazz) { + return clazz.getSuperclass(); + } + + @Override + protected Long calculateFromComputeDefaultSUID(Class clazz) { + try { + Method computeDefaultSUID = ReflectionUtil.lookupMethod(ObjectStreamClass.class, "computeDefaultSUID", java.lang.Class.class); + return (Long) computeDefaultSUID.invoke(null, clazz); + } catch (ReflectionUtil.ReflectionUtilError | InvocationTargetException | IllegalAccessException e) { + throw VMError.shouldNotReachHere(e); + } + } + + @Override + protected boolean isClassAbstract(Class clazz) { + return Modifier.isAbstract(clazz.getModifiers()); + } + } + + /** + * Method MethodAccessorGenerator.generateSerializationConstructor dynamically defines a + * SerializationConstructorAccessorImpl type class. The class has a newInstance method which + * news the class specified by generateSerializationConstructor's first + * parameter--declaringClass and then calls declaringClass' first non-serializable superclass. + * The bytecode of the generated class like : + * + * jdk.internal.reflect.GeneratedSerializationConstructorAccessor2.newInstance(Unknown Source) + * [bci: 0, intrinsic: false] + * 0: new #6 // declaringClass + * 3: dup + * 4: aload_1 + * 5: ifnull 24 + * 8: aload_1 + * 9: arraylength + * 10: sipush 0 + * ... + * + * The declaringClass could be an abstract class. At deserialization time, + * SerializationConstructorAccessorImpl classes are generated for the target class and all of + * its serializable super classes. The super classes could be abstract. So it is possible to + * generate bytecode that new an abstract class. In JDK, the super class' generated newInstance + * method shall never get invoked, so the "new abstract class" code won't cause any error. But + * in Substrate VM, the generated class gets compiled at build time and the "new abstract class" + * code causes compilation error. + * + * We introduce this StubForAbstractClass class to replace any abstract classes at method + * generateSerializationConstructor's declaringClass parameter place. So there won't be "new + * abstract class" bytecode anymore, and it's also safe for runtime as the corresponding + * newInstance method is never actually called. + */ + static class StubForAbstractClass implements Serializable { + private static final long serialVersionUID = 1L; + } + + private static Object stubAccessor = null; + + /** + * Using a separated classloader for serialization checksum computation to avoid initializing + * Classes that should be initialized at run time. + */ + class SerializationChecksumClassLoader extends URLClassLoader { + + SerializationChecksumClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + } + + private class CachedEntity { + private final Object serializationConstructorAccessor; + private final Long configuredChecksum; + + CachedEntity(Object accessor, Long checksum) { + this.serializationConstructorAccessor = accessor; + this.configuredChecksum = checksum; + } + } + + // Cached SerializationConstructorAccessors for runtime usage + private Map cachedSerializationConstructorAccessors; + private static final String MULTIPLE_CHECKSUMS = "MULTIPLE_CHECKSUM"; + private static final String CHECKSUM_VERIFY_FAIL = "CHECKSUM_VERIFY_FAIL"; + private SerializationChecksumClassLoader serializationChecksumClassLoader; + private final ChecksumCalculator checksumCalculator; + + public SerializationSupport(ImageClassLoader imageClassLoader) { + cachedSerializationConstructorAccessors = new ConcurrentHashMap<>(); + checksumCalculator = new ChecksumCalculator(); + URLClassLoader cl = (URLClassLoader) imageClassLoader.getClassLoader(); + serializationChecksumClassLoader = new SerializationChecksumClassLoader(cl.getURLs(), cl.getParent()); + } + + private static void reportError(FeatureImpl.BeforeAnalysisAccessImpl access, String msgKey, String exceptionsMsg) { + // Checkstyle: stop + String option = SubstrateOptionsParser.commandArgument(NativeImageOptions.ReportUnsupportedElementsAtRuntime, "+"); + if (!NativeImageOptions.ReportUnsupportedElementsAtRuntime.getValue()) { + BigBang bb = access.getBigBang(); + bb.getUnsupportedFeatures().addMessage(msgKey, null, + exceptionsMsg + "\n" + "To allow continuing compilation with above unsupported features, set " + option); + } else { + System.out.println(exceptionsMsg); + System.out.println("Compilation will continue because " + option + + " was set. But the program may behave unexpectedly at runtime."); + } + // Checkstyle: resume + } + + @Platforms({Platform.HOSTED_ONLY.class}) + public Class addSerializationConstructorAccessorClass(Class serializationTargetClass, Long configuredChecksum, FeatureAccess access) { + if (serializationTargetClass.isArray() || Enum.class.isAssignableFrom(serializationTargetClass)) { + return null; + } + // Don't generate SerializationConstructorAccessor class for Externalizable case + if (Externalizable.class.isAssignableFrom(serializationTargetClass)) { + try { + Method getExternalizableConstructor = ReflectionUtil.lookupMethod(ObjectStreamClass.class, "getExternalizableConstructor", Class.class); + Constructor c = (Constructor) getExternalizableConstructor.invoke(null, serializationTargetClass); + return c.getDeclaringClass(); + } catch (Exception e) { + throw VMError.shouldNotReachHere(e); + } + } + + /** + * Using reflection to make sure code is compatible with both JDK 8 and above Reflectively + * call method ReflectionFactory.newConstructorForSerialization(Class) to get the + * SerializationConstructorAccessor instance. + */ + Constructor buildTimeConstructor; + Class buildTimeConsClass; + long checksum = 0; + Object constructorAccessor; + String targetClassName = serializationTargetClass.getName(); + boolean isAbstract = Modifier.isAbstract(serializationTargetClass.getModifiers()); + Constructor stubConstructor = null; + try { + Class reflectionFactoryClass = access.findClassByName(Package_jdk_internal_reflect.getQualifiedName() + ".ReflectionFactory"); + Method getReflectionFactoryMethod = ReflectionUtil.lookupMethod(reflectionFactoryClass, "getReflectionFactory"); + Object reflFactoryInstance = getReflectionFactoryMethod.invoke(null); + Method newConstructorForSerializationMethod = ReflectionUtil.lookupMethod(reflectionFactoryClass, "newConstructorForSerialization", Class.class); + buildTimeConstructor = (Constructor) newConstructorForSerializationMethod.invoke(reflFactoryInstance, serializationTargetClass); + + // Calculate GeneratedSerializationConstructor for StubForAbstractClass only once + if (isAbstract && stubAccessor == null) { + stubConstructor = (Constructor) newConstructorForSerializationMethod.invoke(reflFactoryInstance, StubForAbstractClass.class); + } + } catch (ReflectiveOperationException e) { + throw VMError.shouldNotReachHere(e); + } + if (buildTimeConstructor == null) { + return null; + } + buildTimeConsClass = buildTimeConstructor.getDeclaringClass(); + + if (isAbstract && stubAccessor != null) { + constructorAccessor = stubAccessor; + targetClassName = StubForAbstractClass.class.getName(); + } else { + // Prepare build time checksum and verify with configured checksum only for non-abstract + // classes. Abstract class' checksum is always 0. + if (!isAbstract) { + checksum = getChecksum(serializationTargetClass, configuredChecksum, (FeatureImpl.BeforeAnalysisAccessImpl) access, buildTimeConsClass, targetClassName); + } + try { + Method getConstructorAccessor = ReflectionUtil.lookupMethod(Constructor.class, "getConstructorAccessor"); + constructorAccessor = getConstructorAccessor.invoke(isAbstract ? stubConstructor : buildTimeConstructor); + if (isAbstract) { + assert constructorAccessor != null; + stubAccessor = constructorAccessor; + targetClassName = StubForAbstractClass.class.getName(); + } + } catch (Exception e) { + throw VMError.shouldNotReachHere(e); + } + } + // Cache constructorAccessor + CachedEntity exitingEntity = cachedSerializationConstructorAccessors.putIfAbsent(targetClassName, + new CachedEntity(constructorAccessor, checksum)); + if (exitingEntity != null && exitingEntity.configuredChecksum != checksum) { + StringBuilder sb = new StringBuilder(); + sb.append("Suspicious multiple-classloader usage is detected from serialization configurations:\n"); + sb.append("Serialization target class (name=").append(targetClassName).append(", checksum=").append(checksum).append(")"); + sb.append(" is already registered with checksum ").append(exitingEntity.configuredChecksum); + reportError((FeatureImpl.BeforeAnalysisAccessImpl) access, MULTIPLE_CHECKSUMS, sb.toString()); + } + + return buildTimeConsClass; + } + + private long getChecksum(Class serializationTargetClass, Long configuredChecksum, FeatureImpl.BeforeAnalysisAccessImpl access, Class buildTimeConsClass, String targetClassName) { + long checksum; + // this class is getting from SerializationChecksumClassLoader classloader + Class checksumCalculationTargetClass; + try { + // Checkstyle: stop + checksumCalculationTargetClass = Class.forName(serializationTargetClass.getName(), false, serializationChecksumClassLoader); + // Checkstyle resume + } catch (ClassNotFoundException e) { + throw VMError.shouldNotReachHere(e); + } + long buildTimeChecksum = checksumCalculator.calculateChecksum(buildTimeConsClass.getName(), serializationTargetClass.getName(), checksumCalculationTargetClass); + if (configuredChecksum != null) { + if (configuredChecksum.longValue() != buildTimeChecksum) { + StringBuilder sb = new StringBuilder(); + sb.append("\nBuild time serialization class checksum verify failure.") + .append(" The classes' hierarchy may have been changed from configuration collecting time to image build time:\n"); + sb.append(targetClassName).append(": configured checksum is ").append(configuredChecksum).append(", build time checksum is ").append(buildTimeChecksum); + reportError(access, CHECKSUM_VERIFY_FAIL, sb.toString()); + } + checksum = configuredChecksum; + } else { + // If the checksum is not set in configurations, use the build time checksum instead + checksum = buildTimeChecksum; + } + return checksum; + } + + @Override + public Object getSerializationConstructorAccessorClass(Class serializationTargetClass, String targetConstructorClass) { + boolean isAbstract = Modifier.isAbstract(serializationTargetClass.getModifiers()); + if (isAbstract) { + serializationTargetClass = StubForAbstractClass.class; + } + String serializationTargetClassName = serializationTargetClass.getName(); + CachedEntity ret = cachedSerializationConstructorAccessors.get(serializationTargetClassName); + if (ret == null) { + // Not support serializing Lambda yet + if (serializationTargetClassName.contains("$$Lambda$")) { + throw VMError.unsupportedFeature("Can't serialize " + serializationTargetClassName + ". Serializing Lambda class is not supported"); + } else { + throw VMError.unsupportedFeature("SerializationConstructorAccessor class is not found for class :" + serializationTargetClassName + "\n" + + "Generating SerializationConstructorAccessor classes at runtime is not supported. "); + } + } else { + Object accessor = ret.serializationConstructorAccessor; + Long configuredChecksum = ret.configuredChecksum; + long runtimeChecksum; + + if (isAbstract) { + runtimeChecksum = 0; + } else { + runtimeChecksum = checksumCalculator.calculateChecksum(targetConstructorClass, + serializationTargetClassName, serializationTargetClass); + } + // configuredChecksum could be null if it is not set in the configuration. + if ((configuredChecksum != null && configuredChecksum.longValue() == runtimeChecksum) || configuredChecksum == null) { + return accessor; + } else { + throw VMError.unimplemented("Serialization target class " + serializationTargetClassName + "'s hierarchy has been changed at run time. Configured checksum is " + configuredChecksum + + ", runtime checksum is " + runtimeChecksum); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java new file mode 100644 index 0000000000000..3495c1b605352 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Alibaba Group Holding Limited. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.reflect.serialize.hosted; + +// Checkstyle: allow reflection + +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.configure.ConfigurationFiles; +import com.oracle.svm.core.configure.SerializationConfigurationParser; +import com.oracle.svm.core.configure.SerializationConfigurationParser.SerializationParserFunction; +import com.oracle.svm.core.jdk.serialize.SerializationRegistry; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.json.JSONParserException; +import com.oracle.svm.hosted.FallbackFeature; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.NativeImageOptions; +import com.oracle.svm.hosted.config.ConfigurationParserUtils; +import com.oracle.svm.reflect.serialize.SerializationSupport; +import com.oracle.svm.util.ReflectionUtil; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeReflection; + +import java.io.Externalizable; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +@AutomaticFeature +public class SerializationFeature implements Feature { + private int loadedConfigurations; + + @Override + public void beforeAnalysis(BeforeAnalysisAccess a) { + FeatureImpl.BeforeAnalysisAccessImpl access = (FeatureImpl.BeforeAnalysisAccessImpl) a; + ImageClassLoader imageClassLoader = access.getImageClassLoader(); + + SerializationSupport serializationSupport = new SerializationSupport(imageClassLoader); + ImageSingletons.add(SerializationRegistry.class, serializationSupport); + + SerializationParserFunction serializationAdapter = (strTargetSerializationClass, checksum) -> { + Class serializationTargetClass = resolveClass(strTargetSerializationClass, access); + UserError.guarantee(serializationTargetClass != null, "Cannot find serialization target class %s. The missing of this class can't be ignored even if -H:+AllowIncompleteClasspath is set." + + " Please make sure it is in the classpath", strTargetSerializationClass); + if (Serializable.class.isAssignableFrom(serializationTargetClass)) { + Class targetConstructor = serializationSupport.addSerializationConstructorAccessorClass(serializationTargetClass, checksum, access); + addReflections(serializationTargetClass, targetConstructor); + } + }; + + SerializationConfigurationParser parser = new SerializationConfigurationParser(serializationAdapter); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "serialization", + ConfigurationFiles.Options.SerializationConfigurationFiles, ConfigurationFiles.Options.SerializationConfigurationResources, + ConfigurationFiles.SERIALIZATION_NAME); + } + + public static void addReflections(Class serializationTargetClass, Class targetConstructorClass) { + if (targetConstructorClass != null) { + RuntimeReflection.register(ReflectionUtil.lookupConstructor(targetConstructorClass)); + } + + if (Externalizable.class.isAssignableFrom(serializationTargetClass)) { + RuntimeReflection.register(ReflectionUtil.lookupConstructor(serializationTargetClass, (Class[]) null)); + } + + RuntimeReflection.register(serializationTargetClass); + /** + * ObjectStreamClass.computeDefaultSUID is always called at runtime to verify serialization + * class consistency, so need to register all constructors, methods and fields/ + */ + RuntimeReflection.register(serializationTargetClass.getDeclaredConstructors()); + registerMethods(serializationTargetClass); + registerFields(serializationTargetClass); + } + + private static void registerMethods(Class serializationTargetClass) { + RuntimeReflection.register(serializationTargetClass.getDeclaredMethods()); + // computeDefaultSUID will be reflectively called at runtime to verify class consistency + Method computeDefaultSUID = ReflectionUtil.lookupMethod(ObjectStreamClass.class, "computeDefaultSUID", Class.class); + RuntimeReflection.register(computeDefaultSUID); + } + + private static void registerFields(Class serializationTargetClass) { + int staticFinalMask = Modifier.STATIC | Modifier.FINAL; + int privateStaticFinalMask = Modifier.PRIVATE | staticFinalMask; + + Set serialPersistentFieldNames = new HashSet<>(); + try { + Field f = ReflectionUtil.lookupField(serializationTargetClass, "serialPersistentFields"); + if ((f.getModifiers() & privateStaticFinalMask) == privateStaticFinalMask) { + ObjectStreamField[] serialPersistentFields = (ObjectStreamField[]) f.get(null); + for (int i = 0; i < serialPersistentFields.length; i++) { + serialPersistentFieldNames.add(serialPersistentFields[i].getName()); + } + } + } catch (ReflectionUtil.ReflectionUtilError | IllegalAccessException e) { + // No serialPersistentFields field or failed to get the field value, continue + } + + for (Field f : serializationTargetClass.getDeclaredFields()) { + int modifiers = f.getModifiers(); + boolean allowWrite = false; + boolean allowUnsafeAccess = false; + if ((modifiers & staticFinalMask) != staticFinalMask) { + allowWrite = Modifier.isFinal(f.getModifiers()); + allowUnsafeAccess = !Modifier.isStatic(f.getModifiers()); + } + RuntimeReflection.register(allowWrite, allowUnsafeAccess, f); + } + } + + private static Class resolveClass(String typeName, FeatureAccess a) { + Class ret = a.findClassByName(typeName); + if (ret == null) { + handleError("Could not resolve " + typeName + " for serialization configuration."); + } + return ret; + } + + @Override + public void beforeCompilation(BeforeCompilationAccess access) { + if (!ImageSingletons.contains(FallbackFeature.class)) { + return; + } + FallbackFeature.FallbackImageRequest serializationFallback = ImageSingletons.lookup(FallbackFeature.class).serializationFallback; + if (serializationFallback != null && loadedConfigurations == 0) { + throw serializationFallback; + } + } + + private static void handleError(String message) { + // Checkstyle: stop + boolean allowIncompleteClasspath = NativeImageOptions.AllowIncompleteClasspath.getValue(); + if (allowIncompleteClasspath) { + System.out.println("WARNING: " + message); + } else { + throw new JSONParserException(message + " To allow unresolvable reflection configuration, use option -H:+AllowIncompleteClasspath"); + } + // Checkstyle: resume + } +} diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/SerializationChecksumCalculator.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/SerializationChecksumCalculator.java new file mode 100644 index 0000000000000..a74a155781123 --- /dev/null +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/SerializationChecksumCalculator.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Alibaba Group Holding Limited. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.util; + +import org.graalvm.word.WordBase; + +/** + * This class keeps the serialization checksum calculation algorithm at one place. The calculation + * is employed from JVMTI Agent and SerializationSupport. The algorithm is identical but the code + * cannot be reused because JVMTI Agent uses org.graalvm.word.WordBase as its base + * class which is incompatible with regular Java world base class--java.lang.Object. + * + * So we prepare two inner classes for JVMCI Agent usage and regular Java usage respectively. The + * two calculateChecksum methods should be modified together. + */ +public class SerializationChecksumCalculator { + + /** + * This class is for JVMCI Agent. + */ + public abstract static class JVMCIAgentCalculator { + public long calculateChecksum(String targetConstructorClassName, + String serializationClassName, + WordBase serializationClass) { + long checksum = 0L; + if (isClassAbstract(serializationClass)) { + return checksum; + } + if (targetConstructorClassName != null && targetConstructorClassName.length() > 0) { + String currentClassName = serializationClassName; + WordBase currentClass = serializationClass; + while (!targetConstructorClassName.equals(currentClassName)) { + long classSUID = calculateFromComputeDefaultSUID(currentClass); + checksum = checksum * 31 + classSUID; + currentClass = getSuperClass(currentClass); + currentClassName = getClassName(currentClass); + } + checksum = checksum * 31 + targetConstructorClassName.hashCode(); + } + return checksum; + } + + protected abstract String getClassName(WordBase clazz); + + protected abstract WordBase getSuperClass(WordBase clazz); + + protected abstract Long calculateFromComputeDefaultSUID(WordBase clazz); + + protected abstract boolean isClassAbstract(WordBase clazz); + + } + + /** + * This class is for regular Java usage. + */ + public abstract static class JavaCalculator { + public long calculateChecksum(String targetConstructorClassName, + String serializationClassName, + Class serializationClass) { + long checksum = 0L; + if (isClassAbstract(serializationClass)) { + return checksum; + } + if (targetConstructorClassName != null && targetConstructorClassName.length() > 0) { + String currentClassName = serializationClassName; + Class currentClass = serializationClass; + while (!targetConstructorClassName.equals(currentClassName)) { + long classSUID = calculateFromComputeDefaultSUID(currentClass); + checksum = checksum * 31 + classSUID; + currentClass = getSuperClass(currentClass); + currentClassName = getClassName(currentClass); + } + checksum = checksum * 31 + targetConstructorClassName.hashCode(); + } + return checksum; + } + + protected abstract String getClassName(Class clazz); + + protected abstract Class getSuperClass(Class clazz); + + protected abstract Long calculateFromComputeDefaultSUID(Class clazz); + + protected abstract boolean isClassAbstract(Class clazz); + } + +}