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 b6dc39add9748..222b780394161 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 @@ -37,6 +37,7 @@ 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.getIntArgument; import static com.oracle.svm.jvmtiagentbase.Support.getMethodDeclaringClass; import static com.oracle.svm.jvmtiagentbase.Support.getObjectArgument; import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions; @@ -49,6 +50,7 @@ import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_NATIVE_METHOD_BIND; import static org.graalvm.word.WordFactory.nullPointer; +import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; @@ -81,10 +83,12 @@ import com.oracle.svm.agent.restrict.ProxyAccessVerifier; import com.oracle.svm.agent.restrict.ReflectAccessVerifier; import com.oracle.svm.agent.restrict.ResourceAccessVerifier; +import com.oracle.svm.agent.restrict.SerializationAccessVerifier; import com.oracle.svm.configure.config.ConfigurationMethod; import com.oracle.svm.core.c.function.CEntryPointOptions; import com.oracle.svm.core.util.VMError; import com.oracle.svm.jni.nativeapi.JNIEnvironment; +import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes; import com.oracle.svm.jni.nativeapi.JNIMethodId; import com.oracle.svm.jni.nativeapi.JNINativeMethod; import com.oracle.svm.jni.nativeapi.JNIObjectHandle; @@ -129,6 +133,7 @@ final class BreakpointInterceptor { private static ReflectAccessVerifier accessVerifier; private static ProxyAccessVerifier proxyVerifier; private static ResourceAccessVerifier resourceVerifier; + private static SerializationAccessVerifier serializationAccessVerifier; private static NativeImageAgent agent; private static Map installedBreakpoints; @@ -913,6 +918,59 @@ private static String asInternalSignature(Object paramTypesArray) { return null; } + @SuppressWarnings("unused") + private static boolean generateSerializationConstructor(JNIEnvironment jni, Breakpoint bp) { + JNIObjectHandle callerClass = getDirectCallerClass(); + JNIObjectHandle self = getObjectArgument(0); + JNIObjectHandle serializeTargetClass = getObjectArgument(1); + String serializeTargetClassName = getClassNameOrNull(jni, serializeTargetClass); + JNIObjectHandle parameterTypes = getObjectArgument(2); + Object parameterTypeNames = getClassArrayNames(jni, parameterTypes); + JNIObjectHandle checkedExceptions = getObjectArgument(3); + Object checkedExceptionNames = getClassArrayNames(jni, checkedExceptions); + int modifiers = getIntArgument(4); + JNIObjectHandle targetConstructorClass = getObjectArgument(5); + String targetConstructorClassName = getClassNameOrNull(jni, targetConstructorClass); + boolean allowed = (serializationAccessVerifier == null || + serializationAccessVerifier.verifyGenerateSerializationConstructor(jni, serializeTargetClassName, parameterTypeNames, + checkedExceptionNames, modifiers, targetConstructorClassName, self, callerClass)); + Object result = false; + if (allowed) { + JNIValue args = StackValue.get(6, JNIValue.class); + args.addressOf(0).setObject(self); + args.addressOf(1).setObject(serializeTargetClass); + args.addressOf(2).setObject(parameterTypes); + args.addressOf(3).setObject(checkedExceptions); + args.addressOf(4).setInt(modifiers); + args.addressOf(5).setObject(targetConstructorClass); + result = nullHandle().notEqual(jniFunctions().getCallObjectMethodA().invoke(jni, bp.clazz, bp.method, args)); + if (clearException(jni)) { + result = false; + } + } + + if (traceWriter != null) { + traceWriter.traceCall("serialization", + "generateSerializationConstructor", + null, + null, + null, + result, + serializeTargetClassName, parameterTypeNames, + checkedExceptionNames, modifiers, targetConstructorClassName); + guarantee(!testException(jni)); + } + + if (!allowed) { + try (CCharPointerHolder message = toCString( + NativeImageAgent.MESSAGE_PREFIX + "configuration does not permit SerializationConstructorAccessor class for class: " + serializeTargetClassName + + " with first unserializable super class " + targetConstructorClassName)) { + jniFunctions().getThrowNew().invoke(jni, agent.handles().javaLangSecurityException, message.get()); + } + } + return allowed; + } + @CEntryPoint @CEntryPointOptions(prologue = AgentIsolate.Prologue.class) private static void onBreakpoint(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni, @@ -991,12 +1049,14 @@ private static void installBreakpointIfClassLoader(JNIEnvironment jni, JNIObject JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class); public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, TraceWriter writer, ReflectAccessVerifier verifier, - ProxyAccessVerifier prverifier, ResourceAccessVerifier resverifier, NativeImageAgent nativeImageTracingAgent, boolean exptlClassLoaderSupport) { + ProxyAccessVerifier prverifier, ResourceAccessVerifier resverifier, SerializationAccessVerifier serializationAccessVerifier, NativeImageAgent nativeImageTracingAgent, + boolean exptlClassLoaderSupport) { BreakpointInterceptor.traceWriter = writer; BreakpointInterceptor.accessVerifier = verifier; BreakpointInterceptor.proxyVerifier = prverifier; BreakpointInterceptor.resourceVerifier = resverifier; + BreakpointInterceptor.serializationAccessVerifier = serializationAccessVerifier; BreakpointInterceptor.agent = nativeImageTracingAgent; BreakpointInterceptor.experimentalClassLoaderSupport = exptlClassLoaderSupport; @@ -1218,6 +1278,13 @@ private interface BreakpointHandler { brk("java/lang/reflect/Proxy", "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;", BreakpointInterceptor::newProxyInstance), + optionalBrk("sun/reflect/MethodAccessorGenerator", "generateSerializationConstructor", + "(Ljava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Class;ILjava/lang/Class;)Lsun/reflect/SerializationConstructorAccessorImpl;", + BreakpointInterceptor::generateSerializationConstructor), + // MethodAccessorGenerator has been moved to jdk/internal/reflect since JDK9 + optionalBrk("jdk/internal/reflect/MethodAccessorGenerator", "generateSerializationConstructor", + "(Ljava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Class;ILjava/lang/Class;)Ljdk/internal/reflect/SerializationConstructorAccessorImpl;", + BreakpointInterceptor::generateSerializationConstructor), optionalBrk("java/util/ResourceBundle", "getBundleImpl", "(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;", 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 d5bea023885e2..0fd627185a068 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 @@ -53,6 +53,7 @@ import java.util.function.Function; import java.util.regex.Pattern; +import com.oracle.svm.agent.restrict.SerializationAccessVerifier; import org.graalvm.compiler.options.OptionKey; import org.graalvm.nativeimage.ProcessProperties; @@ -92,6 +93,7 @@ public final class NativeImageAgent extends JvmtiAgentBase String oH(OptionKey option) { @@ -256,7 +258,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); @@ -298,7 +300,11 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c if (!restrictConfigs.getResourceConfigPaths().isEmpty()) { resourceVerifier = new ResourceAccessVerifier(restrictConfigs.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION), accessAdvisor); } - BreakpointInterceptor.onLoad(jvmti, callbacks, traceWriter, verifier, proxyVerifier, resourceVerifier, this, experimentalClassLoaderSupport); + SerializationAccessVerifier serializationAccessVerifier = null; + if (!restrictConfigs.getSerializationConfigPaths().isEmpty()) { + serializationAccessVerifier = new SerializationAccessVerifier(restrictConfigs.loadSerializationConfig(ConfigurationSet.FAIL_ON_EXCEPTION), accessAdvisor); + } + BreakpointInterceptor.onLoad(jvmti, callbacks, traceWriter, verifier, proxyVerifier, resourceVerifier, serializationAccessVerifier, this, experimentalClassLoaderSupport); } catch (Throwable t) { System.err.println(MESSAGE_PREFIX + t); return 3; @@ -439,12 +445,15 @@ private static boolean addRestrictConfigs(JvmtiEnv jvmti, ConfigurationSet restr addURI.add(restrictConfigs.getProxyConfigPaths(), cpEntry, optionParts[1]); } else if (oHResourceConfigurationResources.equals(argName)) { addURI.add(restrictConfigs.getResourceConfigPaths(), cpEntry, optionParts[1]); + } else if (oHSerializationConfigurationResources.equals(argName)) { + addURI.add(restrictConfigs.getSerializationConfigPaths(), cpEntry, optionParts[1]); } else if (oHConfigurationResourceRoots.equals(argName)) { String resourceLocation = optionParts[1]; addURI.add(restrictConfigs.getJniConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.JNI_NAME); addURI.add(restrictConfigs.getReflectConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.REFLECTION_NAME); addURI.add(restrictConfigs.getProxyConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.DYNAMIC_PROXY_NAME); addURI.add(restrictConfigs.getResourceConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.RESOURCES_NAME); + addURI.add(restrictConfigs.getSerializationConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.SERIALIZATION_NAME); } } }); @@ -557,6 +566,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/restrict/SerializationAccessVerifier.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/SerializationAccessVerifier.java new file mode 100644 index 0000000000000..368fe5e649ad2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/SerializationAccessVerifier.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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.agent.restrict; + +import com.oracle.svm.configure.config.SerializationConfiguration; +import com.oracle.svm.configure.trace.AccessAdvisor; +import com.oracle.svm.jni.nativeapi.JNIEnvironment; +import com.oracle.svm.jni.nativeapi.JNIObjectHandle; + +public class SerializationAccessVerifier extends AbstractAccessVerifier { + private final SerializationConfiguration configuration; + + public SerializationAccessVerifier(SerializationConfiguration configuration, AccessAdvisor advisor) { + super(advisor); + this.configuration = configuration; + } + + public boolean verifyGenerateSerializationConstructor(JNIEnvironment env, String serializationTargetClass, Object parameterTypes, Object checkedExceptions, + int modifiers, String targetConstructorClass, JNIObjectHandle queriedClass, JNIObjectHandle callerClass) { + if (shouldApproveWithoutChecks(env, queriedClass, callerClass)) { + return true; + } + return (parameterTypes instanceof String[] && checkedExceptions instanceof String[] && + configuration.contains(serializationTargetClass, (String[]) parameterTypes, (String[]) checkedExceptions, modifiers, targetConstructorClass)); + } +} 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..ee78105ec41b5 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,12 @@ public ResourceConfiguration loadResourceConfig(Function return resourceConfiguration; } + public SerializationConfiguration loadSerializationConfig(Function exceptionHandler) throws Exception { + SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); + loadConfig(serializationConfigPaths, new SerializationConfigurationParser(key -> serializationConfiguration.add(key)), 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..62b6706ce7479 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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 com.oracle.svm.core.configure.SerializationKey; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + +public class SerializationConfiguration implements JsonPrintable { + + private final ConcurrentHashMap.KeySetView, Boolean> serializations = ConcurrentHashMap.newKeySet(); + + public void add(String serializationTargetClass, String[] parameterTypes, String[] checkedExceptions, int modifiers, String targetConstructorClass) { + SerializationKey key = new SerializationKey<>(serializationTargetClass, parameterTypes, checkedExceptions, modifiers, targetConstructorClass); + add(key); + } + + public void add(SerializationKey key) { + serializations.add(key); + } + + public boolean contains(String serializationTargetClass, String[] parameterTypes, String[] checkedExceptions, int modifiers, String targetConstructorClass) { + SerializationKey key = new SerializationKey<>(serializationTargetClass, parameterTypes, checkedExceptions, modifiers, targetConstructorClass); + return serializations.contains(key); + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.append('[').indent(); + String prefix = ""; + for (SerializationKey skey : serializations) { + writer.append(prefix).newline().append('{'); + writer.newline(); + String typePrefix = ""; + writer.quote("name").append(":").quote(skey.getSerializationTargetClass()).append(",").newline(); + writer.quote("parameterTypes").append(":").append('['); + for (String parameterType : skey.getParameterTypes()) { + writer.append(typePrefix).quote(parameterType); + typePrefix = ","; + } + writer.append(']').append(",").newline(); + + typePrefix = ""; + writer.quote("checkedExceptions").append(":").append('['); + for (String checkedException : skey.getCheckedExceptions()) { + writer.append(typePrefix).quote(checkedException); + typePrefix = ","; + } + writer.append(']').append(",").newline(); + + writer.quote("modifiers").append(':').append(Integer.toString(skey.getModifiers())).append(",").newline(); + writer.quote("targetConstructorClass").append(':').quote(skey.getTargetConstructorClass()).newline(); + writer.append('}'); + prefix = ","; + } + writer.unindent().newline(); + writer.append(']').newline(); + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java index de17efeb3f892..228c1922b46ff 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java @@ -35,6 +35,8 @@ import com.oracle.svm.configure.json.JsonWriter; import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.json.JSONParserException; +import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MetaUtil; public class TypeConfiguration implements JsonPrintable { @@ -63,7 +65,17 @@ public ConfigurationType getOrCreateType(String qualifiedForNameString) { } if (n > 0) { // transform to Java source syntax StringBuilder sb = new StringBuilder(s.length() + n); - sb.append(s, n + 1, s.length() - 1); // cut off leading '[' and 'L' and trailing ';' + if (s.charAt(n) == 'L' && s.charAt(s.length() - 1) == ';') { + sb.append(s, n + 1, s.length() - 1); // cut off leading '[' and 'L' and trailing ';' + } else { + // Primitive Array + if (n == s.length() - 1) { + s = JavaKind.fromPrimitiveOrVoidTypeChar(s.charAt(n)).getJavaName(); + } else { + throw new JSONParserException("Wrong position calculation for primitive array " + s + ": calculated position " + n + " doesn't equal to expected position " + (s.length() - 1)); + } + sb.append(s); + } for (int i = 0; i < n; i++) { sb.append("[]"); } 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..cf85165c2214d 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,11 @@ public final class AccessAdvisor { internalCallerFilter.addOrGetChildren("org.graalvm.compiler.**", RuleNode.Inclusion.Exclude); internalCallerFilter.addOrGetChildren("org.graalvm.libgraal.**", RuleNode.Inclusion.Exclude); + + // For serializations + internalCallerFilter.addOrGetChildren("java.io.ObjectInputStream", RuleNode.Inclusion.Include); + internalCallerFilter.addOrGetChildren("java.io.ObjectOutputStream", RuleNode.Inclusion.Include); + internalCallerFilter.addOrGetChildren("java.io.ObjectStreamClass", RuleNode.Inclusion.Include); internalCallerFilter.removeRedundantNodes(); accessWithoutCallerFilter = RuleNode.createRoot(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index 2eacff660440b..6e0d774658426 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -145,7 +145,8 @@ public void processEntry(Map entry) { memberKind = ConfigurationMemberKind.DECLARED; // fall through case "getField": { - configuration.getOrCreateType(clazzOrDeclaringClass).addField(singleElement(args), memberKind, false, unsafeAccess); + configuration.getOrCreateType(clazzOrDeclaringClass).addField(singleElement(args), memberKind, entry.containsKey("allowWrite") ? (Boolean) entry.get("allowWrite") : false, + entry.containsKey("unsafeAccess") ? (Boolean) entry.get("unsafeAccess") : unsafeAccess); break; } 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..b9ef9ea19b654 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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 ("generateSerializationConstructor".equals(function)) { + expectSize(args, 5); + if((args.get(1) instanceof List) && (args.get(2) instanceof List)) { + List paramTypes = (List) args.get(1); + List checkedExceptions = (List) args.get(2); + serializationConfiguration.add((String) args.get(0), paramTypes.toArray(new String[0]), checkedExceptions.toArray(new String[0]), (Integer) args.get(3), (String) args.get(4)); + } else{ + throw new IllegalArgumentException("The second and third arguments should all be List."); + } + } + } +} 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/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index 92dded4091d0a..3f5be9c1a499e 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..15dfe72b8a779 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,11 @@ protected static boolean asBoolean(Object value, String propertyName) { } throw new JSONParserException("Invalid boolean value '" + value + "' for element '" + propertyName + "'"); } + + protected static int asInteger(Object value, String propertyName) { + if (value instanceof Integer) { + return (int) value; + } + throw new JSONParserException("Invalid int 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..b3f83277b1940 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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; +import java.util.function.Consumer; + +public class SerializationConfigurationParser extends ConfigurationParser { + private final Consumer> consumer; + + public SerializationConfigurationParser(Consumer> 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")); + String[] parameterTypes = asList(data.get("parameterTypes"), "parameterTypes must be a list of parameter types").toArray(new String[0]); + String[] checkedExceptions = asList(data.get("checkedExceptions"), "checkedExceptions must be a list of exceptions").toArray(new String[0]); + int modifiers = asInteger(data.get("modifiers"), "modifiers"); + String targetConstructorClass = asString(data.get("targetConstructorClass")); + SerializationKey key = new SerializationKey<>(targetSerializationClass, parameterTypes, checkedExceptions, modifiers, targetConstructorClass); + consumer.accept(key); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationKey.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationKey.java new file mode 100644 index 0000000000000..dea573c422a6a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationKey.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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 java.util.Arrays; + +public class SerializationKey { + private T serializationTargetClass; + private T[] parameterTypes; + private T[] checkedExceptions; + private int modifiers; + private T targetConstructorClass; + private String flattenString; + + public T getSerializationTargetClass() { + return serializationTargetClass; + } + + public T[] getParameterTypes() { + return parameterTypes; + } + + public T[] getCheckedExceptions() { + return checkedExceptions; + } + + public int getModifiers() { + return modifiers; + } + + public T getTargetConstructorClass() { + return targetConstructorClass; + } + + public SerializationKey(T serializationTargetClass, T[] parameterTypes, T[] checkedExceptions, int modifiers, T targetConstructorClass) { + this.serializationTargetClass = serializationTargetClass; + this.parameterTypes = parameterTypes; + this.checkedExceptions = checkedExceptions; + this.modifiers = modifiers; + this.targetConstructorClass = targetConstructorClass; + + StringBuilder sb = new StringBuilder(); + sb.append("serializationTargetClass:").append(getStringValue(serializationTargetClass)).append("\n"); + sb.append("parameterTypes:").append(parameterTypes.length).append("."); + Arrays.stream(parameterTypes).forEach(c -> sb.append(getStringValue(c)).append(";")); + sb.append("\n"); + sb.append("checkedExceptions:").append(checkedExceptions.length).append("."); + Arrays.stream(checkedExceptions).forEach(c -> sb.append(getStringValue(c)).append(";")); + sb.append("\n"); + sb.append("modifiers:").append(modifiers).append("\n"); + sb.append("targetConstructorClass:").append(getStringValue(targetConstructorClass)).append("\n"); + flattenString = sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SerializationKey) { + SerializationKey that = SerializationKey.class.cast(obj); + return this.flattenString.equals(that.flattenString); + } else { + return false; + } + } + + @Override + public String toString() { + return flattenString; + } + + @Override + public int hashCode() { + return flattenString.hashCode(); + } + + private String getStringValue(T t) { + if (t instanceof Class) { + return ((Class) t).getName(); + } else if (t instanceof String) { + return (String) t; + } else { + throw new RuntimeException("SerializeKey should be either Class of String, but is " + t.getClass()); + } + } + + public String asKey() { + return flattenString; + } +} 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..e29349dccad97 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt @@ -0,0 +1,19 @@ +One or several (comma-separated) paths to JSON files that specify lists of serialization configurations that define Java SerializationConstructorAccessor classes. +The structure is an array of elements specifying the parameters of method + + sun.reflect.MethodAccessorGenerator.generateSerializationConstructor(Class declaringClass, + Class[] parameterTypes, + Class[] checkedExceptions, + int modifiers, + Class targetConstructorClass) + +Example: + + [ + {"name":"java.lang.ArrayList", + "parameterTypes":[], + "checkedExceptions":[], + "modifiers":"4", + "targetConstructorClass":"AbstractList" + } + ] \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 886cb8d702c52..15817047f40ee 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -266,6 +266,12 @@ public final class DynamicHub implements JavaKind.FormatWithToString, AnnotatedE */ private ClassInitializationInfo classInitializationInfo; + /** + * Indicates if this class originally has a method. It is used for serialization + * support. + */ + private boolean hasCLinit; + /** * Classloader used for loading this class during image-build time. */ @@ -367,6 +373,11 @@ public void setClassInitializationInfo(ClassInitializationInfo classInitializati this.classInitializationInfo = classInitializationInfo; } + @Platforms(Platform.HOSTED_ONLY.class) + public void setHasCLinit(boolean hasCLinit) { + this.hasCLinit = hasCLinit; + } + @Platforms(Platform.HOSTED_ONLY.class) public void setData(int layoutEncoding, int typeID, int monitorOffset, int hashCodeOffset, int[] assignableFromMatches, BitSet instanceOfBits, CFunctionPointer[] vtable, long referenceMapIndex, boolean isInstantiated) { @@ -506,6 +517,10 @@ public ClassInitializationInfo getClassInitializationInfo() { return classInitializationInfo; } + public boolean isHasCLinit() { + return hasCLinit; + } + public boolean isInitialized() { return classInitializationInfo.isInitialized(); } 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..e82373b59e96c 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 @@ -24,18 +24,18 @@ */ package com.oracle.svm.core.jdk; -import java.io.Closeable; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.concurrent.locks.ReentrantLock; - import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.RecomputeFieldValue; 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; + +import java.io.Closeable; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; @TargetClass(java.io.FileDescriptor.class) final class Target_java_io_FileDescriptor { @@ -47,30 +47,27 @@ final class Target_java_io_FileDescriptor { @TargetClass(java.io.ObjectInputStream.class) @SuppressWarnings({"static-method"}) final class Target_java_io_ObjectInputStream { - - @Substitute - private Object readObject() { - throw VMError.unsupportedFeature("ObjectInputStream.readObject()"); - } - + /** + * 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 readUnshared() { - throw VMError.unsupportedFeature("ObjectInputStream.readUnshared()"); + private static ClassLoader latestUserDefinedLoader() { + return Target_java_io_ObjectInputStream.class.getClassLoader(); } } -@TargetClass(java.io.ObjectOutputStream.class) -@SuppressWarnings({"static-method", "unused"}) -final class Target_java_io_ObjectOutputStream { - - @Substitute - private void writeObject(Object obj) { - throw VMError.unsupportedFeature("ObjectOutputStream.writeObject()"); - } +@TargetClass(java.io.ObjectStreamClass.class) +final class Target_java_io_ObjectStreamClass { @Substitute - private void writeUnshared(Object obj) { - throw VMError.unsupportedFeature("ObjectOutputStream.writeUnshared()"); + private static boolean hasStaticInitializer(Class cl) { + return DynamicHub.fromClass(cl).isHasCLinit(); } } 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..6f21fb1146170 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..a8e041c5cefc1 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,31 @@ */ 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.configure.SerializationKey; +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 { + @Substitute + public Target_SerializationConstructorAccessorImpl generateSerializationConstructor(Class declaringClass, + Class[] parameterTypes, + Class[] checkedExceptions, + int modifiers, + Class targetConstructorClass) { + SerializationRegistry serializationRegistry = ImageSingletons.lookup(SerializationRegistry.class); + return (Target_SerializationConstructorAccessorImpl) serializationRegistry.getSerializationConstructorAccessorClass(declaringClass, parameterTypes, + checkedExceptions, modifiers, targetConstructorClass); + } +} + +@TargetClass(classNameProvider = Package_jdk_internal_reflect.class, className = "SerializationConstructorAccessorImpl") +final class Target_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..cf635c422454c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/SerializationRegistry.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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; + +import com.oracle.svm.core.configure.SerializationKey; +import com.oracle.svm.core.jdk.Package_jdk_internal_reflect; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public interface SerializationRegistry { + @Platforms(Platform.HOSTED_ONLY.class) + void addSerializationConstructorAccessorClass(SerializationKey> serializationKey); + + @Platforms(Platform.HOSTED_ONLY.class) + String collectMultiDefinitions(); + + Object getSerializationConstructorAccessorClass(Class serializationTargetClass, Class[] parameterTypes, Class[] checkedExceptions, + int modifiers, Class targetConstructorClass); + + @Platforms(Platform.HOSTED_ONLY.class) + static Object createSerializationConstructorAccessorClass(Class serializationTargetClass, Class[] parameterTypes, Class[] checkedExceptions, + int modifiers, Class targetConstructorClass) { + try { + Class generatorClass = Class.forName(Package_jdk_internal_reflect.getQualifiedName() + ".MethodAccessorGenerator"); + Constructor c = generatorClass.getDeclaredConstructor(); + c.setAccessible(true); + Object generator = c.newInstance(); + Method generateMethod = generatorClass.getMethod("generateSerializationConstructor", Class.class, Class[].class, Class[].class, int.class, Class.class); + generateMethod.setAccessible(true); + return generateMethod.invoke(generator, serializationTargetClass, parameterTypes, checkedExceptions, modifiers, targetConstructorClass); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + throw new UnsupportedOperationException("Cannot create SerializationConstructorAccessor class for " + serializationTargetClass.getName(), e); + } + } +} 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 be2477e241c2e..c7c59e04e8d1d 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 @@ -183,6 +183,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); @@ -852,7 +853,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; @@ -1071,6 +1073,7 @@ private int completeImageBuild() { consolidateListArgs(imageBuilderArgs, oHDynamicProxyConfigurationFiles, ",", canonicalizedPathStr); consolidateListArgs(imageBuilderArgs, oHResourceConfigurationFiles, ",", canonicalizedPathStr); consolidateListArgs(imageBuilderArgs, oHJNIConfigurationFiles, ",", canonicalizedPathStr); + consolidateListArgs(imageBuilderArgs, oHSerializationConfigurationFiles, ",", canonicalizedPathStr); BiFunction takeLast = (a, b) -> b; String imagePathStr = consolidateArgs(imageBuilderArgs, oHPath, Function.identity(), canonicalizedPathStr, () -> null, takeLast); 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 f5391a8f3e729..328ed2732bb1c 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 @@ -33,6 +33,8 @@ import java.util.Objects; import java.util.Set; +import com.oracle.svm.core.jdk.Package_jdk_internal_reflect; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.hosted.Feature; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; @@ -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,7 +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); - } catch (NoSuchMethodException e) { + + Class generatorClass = Class.forName(Package_jdk_internal_reflect.getQualifiedName() + ".MethodAccessorGenerator"); + Method generateMethod = generatorClass.getMethod("generateSerializationConstructor", Class.class, Class[].class, Class[].class, int.class, Class.class); + addCheck(generateMethod, this::collectSerializationInvokes); + } catch (NoSuchMethodException | ClassNotFoundException e) { throw VMError.shouldNotReachHere("Registering ReflectionInvocationChecks failed", e); } } @@ -190,6 +197,10 @@ private void collectProxyInvokes(ReflectionInvocationCheck check, BytecodePositi } } + private void collectSerializationInvokes(ReflectionInvocationCheck check, BytecodePosition invokeLocation) { + serializationCalls.add("SerializationConstructorAccessor generation method " + check.locationString(invokeLocation)); + } + static FallbackImageRequest reportFallback(String message) { return reportFallback(message, null); } @@ -255,6 +266,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 +308,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 ebd9c442b1d3b..5e379e2de10ee 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 @@ -227,8 +227,12 @@ public void duringAnalysis(DuringAnalysisAccess a) { classInitializationSupport.checkDelayedInitialization(); for (AnalysisType type : access.getUniverse().getTypes()) { + // Check if the class has cclinit method + DynamicHub hub = access.getHostVM().dynamicHub(type); + boolean hasCLinit = (type.isArray() || type.getClassInitializer() == null) ? false : true; + hub.setHasCLinit(hasCLinit); + if (type.isInTypeCheck() || type.isInstantiated()) { - DynamicHub hub = access.getHostVM().dynamicHub(type); if (hub.getClassInitializationInfo() == null) { buildClassInitializationInfo(access, type, hub); access.requireAnalysisIteration(); 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 fe2c01c3eb7f4..1b47cb71200dc 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 @@ -207,6 +207,16 @@ public interface ReleaseByteArrayElementsFunctionPointer extends CFunctionPointe CCharPointer invoke(JNIEnvironment env, JNIObjectHandle byteArray, CCharPointer elements, int mode); } + public interface CallObjectMethod0FunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + JNIObjectHandle invoke(JNIEnvironment env, JNIObjectHandle objOrClass, JNIMethodId methodID); + } + + public interface CallIntMethodFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + int invoke(JNIEnvironment env, JNIObjectHandle objOrClass, JNIMethodId methodID); + } + 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 89ce4e80a89db..2fbe1033a1347 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 @@ -263,7 +263,7 @@ public interface JNINativeInterface extends PointerBase { void setGetMethodID(GetMethodIDFunctionPointer p); @CField - CFunctionPointer getCallObjectMethod(); + T getCallObjectMethod(); @CField void setCallObjectMethod(CFunctionPointer p); @@ -353,7 +353,7 @@ public interface JNINativeInterface extends PointerBase { void setCallShortMethodA(CFunctionPointer p); @CField - CFunctionPointer getCallIntMethod(); + T getCallIntMethod(); @CField void setCallIntMethod(CFunctionPointer p); 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 032e7d3584635..fe4977e768f5e 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 @@ -182,6 +182,15 @@ public static JNIObjectHandle getObjectArgument(int slot) { return handlePtr.read(); } + public static int getIntArgument(int slot) { + CIntPointer handlePtr = StackValue.get(CIntPointer.class); + JvmtiError error = jvmtiFunctions().GetLocalInt().invoke(jvmtiEnv(), nullHandle(), 0, slot, handlePtr); + if (error != JvmtiError.JVMTI_ERROR_NONE) { + throw new RuntimeException(error.toString()); + } + return handlePtr.read(); + } + public static String getClassNameOr(JNIEnvironment env, JNIObjectHandle clazz, String forNullHandle, String forNullNameOrException) { if (clazz.notEqual(nullHandle())) { JNIObjectHandle clazzName = callObjectMethod(env, clazz, JvmtiAgentBase.singleton().handles().javaLangClassGetName); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java index 4055b98abccff..f5f1dc411ce4c 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java @@ -62,8 +62,12 @@ public void duringSetup(DuringSetupAccess a) { access.registerSubstitutionProcessor(subst); ImageSingletons.add(ReflectionSubstitution.class, subst); - reflectionData = new ReflectionDataBuilder(access); - ImageSingletons.add(RuntimeReflectionSupport.class, reflectionData); + if(ImageSingletons.contains(RuntimeReflectionSupport.class)){ + reflectionData = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); + }else { + reflectionData = new ReflectionDataBuilder(access); + ImageSingletons.add(RuntimeReflectionSupport.class, reflectionData); + } ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(reflectionData, access.getImageClassLoader()); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "reflection", 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..e4e5ba8225c95 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/SerializationSupport.java @@ -0,0 +1,78 @@ +package com.oracle.svm.reflect.serialize; + +import com.oracle.svm.core.configure.SerializationKey; +import com.oracle.svm.core.jdk.serialize.SerializationRegistry; +import com.oracle.svm.core.util.VMError; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SerializationSupport implements SerializationRegistry { + //Cached SerializationConstructorAccessors for runtime usage + private Map cachedSerializationConstructorAccessors; + + /* This map is used to track multiple classloader usage + * Map of serialization target class and a list of class T that stores the parameters to + * generate its SerializationConstructorAccessor class and other relevant information. + * Each T entity represents one generation. One target class usually has only one generation, + * multiple-generations is a suggestion of multiple classloader usage. + */ + private Map>> accessorDefinitions; + + public SerializationSupport() { + cachedSerializationConstructorAccessors = new ConcurrentHashMap<>(); + accessorDefinitions = new ConcurrentHashMap<>(); + } + + @Override + public String collectMultiDefinitions() { + StringBuilder sb = new StringBuilder(); + accessorDefinitions.forEach((targetClass, definitions) -> { + int size = definitions.size(); + if (size > 1) { + sb.append("Suspicious multiple-classloader usage is detected:\n"); + sb.append("There are " + size + " SerializationConstructorAccessor classes have been defined for the same serialization target class:\n\n"); + int i = 0; + while (i < size) { + sb.append("(").append((i + 1)).append(")"); + sb.append(definitions.get(i).toString()); + sb.append("\n"); + i++; + } + } + }); + return sb.toString(); + } + + @Platforms({Platform.HOSTED_ONLY.class}) + @Override + public void addSerializationConstructorAccessorClass(SerializationKey> serializationKey) { + cachedSerializationConstructorAccessors.computeIfAbsent(serializationKey.asKey(), (k) -> SerializationRegistry.createSerializationConstructorAccessorClass( + serializationKey.getSerializationTargetClass(), serializationKey.getParameterTypes(), serializationKey.getParameterTypes(), serializationKey.getModifiers(), serializationKey.getTargetConstructorClass())); + String targetClass = serializationKey.getSerializationTargetClass().getName(); + if (accessorDefinitions.containsKey(targetClass)) { + accessorDefinitions.get(targetClass).add(serializationKey); + } else { + List> value = new ArrayList<>(); + value.add(serializationKey); + accessorDefinitions.put(targetClass, value); + } + } + + + @Override + public Object getSerializationConstructorAccessorClass(Class serializationTargetClass, Class[] parameterTypes, Class[] checkedExceptions, int modifiers, Class targetConstructorClass) { + SerializationKey> key = new SerializationKey<>(serializationTargetClass, parameterTypes, checkedExceptions, modifiers, targetConstructorClass); + Object ret = cachedSerializationConstructorAccessors.get(key.toString()); + if (ret == null) { + throw VMError.unsupportedFeature("SerializationConstructorAccessor class is not found for SerializationKey:\n" + key.toString() + + "Generating SerializationConstructorAccessor classes at runtime is not supported. "); + } + return ret; + } + +} 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..565c2bd8f0826 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java @@ -0,0 +1,135 @@ +package com.oracle.svm.reflect.serialize.hosted; + +import com.oracle.graal.pointsto.BigBang; +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.SerializationKey; +import com.oracle.svm.core.jdk.serialize.SerializationRegistry; +import com.oracle.svm.core.option.SubstrateOptionsParser; +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.hosted.ReflectionDataBuilder; +import com.oracle.svm.reflect.serialize.SerializationSupport; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.function.Consumer; + +@AutomaticFeature +public class SerializationFeature implements Feature { + private int loadedConfigurations; + + @Override + public void duringSetup(DuringSetupAccess a) { + FeatureImpl.DuringSetupAccessImpl access = (FeatureImpl.DuringSetupAccessImpl) a; + ImageClassLoader imageClassLoader = access.getImageClassLoader(); + + SerializationSupport serializationSupport; + if (ImageSingletons.contains(SerializationRegistry.class)) { + serializationSupport = (SerializationSupport) ImageSingletons.lookup(SerializationRegistry.class); + } else { + serializationSupport = new SerializationSupport(); + ImageSingletons.add(SerializationRegistry.class, serializationSupport); + } + + ReflectionDataBuilder reflectionData; + if (ImageSingletons.contains(RuntimeReflectionSupport.class)) { + reflectionData = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); + } else { + reflectionData = new ReflectionDataBuilder(access); + ImageSingletons.add(RuntimeReflectionSupport.class, reflectionData); + } + + Consumer> serializationAdapter = (serializationKey) -> { + Class serializationTargetClass = resolveClass(serializationKey.getSerializationTargetClass(), imageClassLoader); + Class[] parameterTypes = Arrays.stream(serializationKey.getParameterTypes()).map(parameterType -> resolveClass(parameterType, imageClassLoader)).toArray(Class[]::new); + Class[] checkedExceptions = Arrays.stream(serializationKey.getCheckedExceptions()).map(parameterType -> resolveClass(parameterType, imageClassLoader)).toArray(Class[]::new); + Class targetConstructor = resolveClass(serializationKey.getTargetConstructorClass(), imageClassLoader); + SerializationKey> serializationKeyAsClass = new SerializationKey<>(serializationTargetClass, parameterTypes, checkedExceptions, serializationKey.getModifiers(), targetConstructor); + serializationSupport.addSerializationConstructorAccessorClass(serializationKeyAsClass); + addReflections(reflectionData, serializationTargetClass, targetConstructor); + }; + + SerializationConfigurationParser parser = new SerializationConfigurationParser(serializationAdapter); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "serialization", + ConfigurationFiles.Options.SerializationConfigurationFiles, ConfigurationFiles.Options.SerializationConfigurationResources, + ConfigurationFiles.SERIALIZATION_NAME); + String exceptionsMsg = serializationSupport.collectMultiDefinitions(); + if (exceptionsMsg.length() > 0) { + System.out.println(exceptionsMsg); + if (!NativeImageOptions.ReportUnsupportedElementsAtRuntime.getValue()) { + BigBang bb = access.getBigBang(); + bb.getUnsupportedFeatures().addMessage("Unsupported dynamic features", null, + "To allow continuing compilation with above unsupported features, set " + + SubstrateOptionsParser.commandArgument(NativeImageOptions.ReportUnsupportedElementsAtRuntime, "+")); + } else { + System.out.println("Compilation will continue because " + SubstrateOptionsParser.commandArgument(NativeImageOptions.ReportUnsupportedElementsAtRuntime, "+") + + " was set. But the program may behave unexpectedly at runtime."); + } + } + } + + public static void addReflections(ReflectionDataBuilder reflectionData, Class serializationTargetClass, Class targetConstructor) { + reflectionData.register(targetConstructor.getDeclaredConstructors()); + + for (Field f : serializationTargetClass.getDeclaredFields()) { + String fieldName = f.getName(); + int modifiers = f.getModifiers(); + boolean allowWrite = false; + boolean allowUnsafeAccess = false; + boolean needRegister = false; + if ("serialPersistentFields".equals(fieldName) && Modifier.isPrivate(modifiers) && Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) { + needRegister = true; + } else if ("serialVersionUID".equals(fieldName) && Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) { + needRegister = true; + } else if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) { + allowWrite = Modifier.isFinal(f.getModifiers()); + allowUnsafeAccess = !Modifier.isStatic(f.getModifiers()); + needRegister = true; + } + if (needRegister) { + reflectionData.register(allowWrite, allowUnsafeAccess, f); + reflectionData.register(f.getDeclaringClass()); + } + } + } + + private Class resolveClass(String typeName, ImageClassLoader imageClassLoader) { + Class ret = imageClassLoader.findClassByName(typeName, false); + 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 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 + } +}