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..d2e17c19d150a 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,119 @@ private static String asInternalSignature(Object paramTypesArray) { return null; } + @SuppressWarnings("unused") + private static boolean generateSerializationConstructor(JNIEnvironment jni, Breakpoint bp) { + 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)); + 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; + } + } + JNIObjectHandle callerClass = getDirectCallerClass(); + if (traceWriter != null) { + traceWriter.traceCall("serialization", + "generateSerializationConstructor", + null, + null, + null, + result, + serializeTargetClassName, parameterTypeNames, + checkedExceptionNames, modifiers, targetConstructorClassName); + JNIFunctionPointerTypes.CallIntMethodFunctionPointer noArgRetIntCall = jniFunctions().getCallIntMethod(); + int privateStaticFinalMask = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL; + int staticFinalMask = Modifier.STATIC | Modifier.FINAL; + + // call serializationTargetClass.getDeclaredFields(); + JNIObjectHandle javaLangClass = agent.handles().findClass(jni, "java/lang/Class"); + JNIMethodId getDeclaredFieldsMI = agent.handles().getMethodId(jni, javaLangClass, "getDeclaredFields", + "()[Ljava/lang/reflect/Field;", false); + JNIFunctionPointerTypes.CallObjectMethod0FunctionPointer noArgRetObjectCall = jniFunctions().getCallObjectMethod(); + JNIObjectHandle fieldsJArray = noArgRetObjectCall.invoke(jni, serializeTargetClass, getDeclaredFieldsMI); + + // Prepare JNIMethodIds for later calls + JNIObjectHandle javaLangReflectField = agent.handles().findClass(jni, "java/lang/reflect/Field"); + JNIMethodId getFieldNameId = agent.handles().getMethodId(jni, javaLangReflectField, "getName", "()Ljava/lang/String;", false); + JNIMethodId getFieldModifiersId = agent.handles().getMethodId(jni, javaLangReflectField, "getModifiers", "()I", false); + JNIMethodId getFieldTypeId = agent.handles().getMethodId(jni, javaLangReflectField, "getType", "()Ljava/lang/Class;", false); + // Add serialize and deserialize fields into reflection configs + // Check each field + int fieldArrayLength = jniFunctions().getGetArrayLength().invoke(jni, fieldsJArray); + for (int i = 0; i < fieldArrayLength; i++) { + // Get field object from array + JNIObjectHandle field = jniFunctions().getGetObjectArrayElement().invoke(jni, fieldsJArray, i); + // call field.getName() to get field's name + String fieldName = fromJniString(jni, noArgRetObjectCall.invoke(jni, field, getFieldNameId)); + // call field.getModifiers tp get field's modifiers + int fieldModifiers = noArgRetIntCall.invoke(jni, field, getFieldModifiersId); + if (fieldName.equals("serialPersistentFields") && + (fieldModifiers & privateStaticFinalMask) == privateStaticFinalMask) { + traceWriter.traceCall("reflect", + "getDeclaredField", + serializeTargetClassName, + null, + null, + result, "serialPersistentFields"); + } else if (fieldName.equals("serialVersionUID") && + (fieldModifiers & staticFinalMask) == staticFinalMask) { + traceWriter.traceCall("reflect", + "getDeclaredField", + serializeTargetClassName, + null, + null, + result, "serialVersionUID"); + } else if ((fieldModifiers & staticFinalMask) != staticFinalMask) { + // Set the field's allowWrite and unsafeAccess properties + traceWriter.traceCall("reflect", + "getDeclaredField", + serializeTargetClassName, + null, + null, + result, (modifiers & Modifier.FINAL) == Modifier.FINAL, (modifiers & Modifier.STATIC) == 0, fieldName); + } + // Add field's class in config + // call field.getType() + JNIObjectHandle fieldClass = noArgRetObjectCall.invoke(jni, field, getFieldTypeId); + traceWriter.traceCall("reflect", + "forName", + serializeTargetClassName, + null, + null, + result, getClassNameOrNull(jni, fieldClass)); + } + 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 +1109,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 +1338,9 @@ 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("sun/reflect/MethodAccessorGenerator", "generateSerializationConstructor", + "(Ljava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Class;ILjava/lang/Class;)Lsun/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/TraceWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/TraceWriter.java index 14ec5b108e4c5..ca83422f5681b 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/TraceWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/TraceWriter.java @@ -89,6 +89,23 @@ public void tracePhaseChange(String phase) { * @param args Arguments to the call, which may contain arrays (which can contain more arrays) */ public void traceCall(String tracer, String function, Object clazz, Object declaringClass, Object callerClass, Object result, Object... args) { + Map entry = createTraceEntry(tracer, function, clazz, declaringClass, callerClass, result, + args); + traceEntry(entry); + } + + public void traceCall(String tracer, String function, Object clazz, Object declaringClass, Object callerClass, + Object result, boolean allowWrite, boolean unsafeAccess, String fieldName) { + Map entry = createTraceEntry(tracer, function, clazz, declaringClass, callerClass, result, + fieldName); + entry.put("allowWrite", allowWrite); + entry.put("unsafeAccess", unsafeAccess); + traceEntry(entry); + } + + @SuppressWarnings("static-method") + private Map createTraceEntry(String tracer, String function, Object clazz, Object declaringClass, + Object callerClass, Object result, Object... args) { Map entry = new HashMap<>(); entry.put("tracer", tracer); entry.put("function", function); @@ -107,7 +124,7 @@ public void traceCall(String tracer, String function, Object clazz, Object decla if (args != null) { entry.put("args", handleSpecialValue(args)); } - traceEntry(entry); + return entry; } abstract void traceEntry(Map entry); 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..81929a6b9bd71 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/SerializationAccessVerifier.java @@ -0,0 +1,45 @@ +/* + * 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; + +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) { + 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..976f99ed1bdad --- /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(':').quote(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..18c61721219a3 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 @@ -63,7 +63,12 @@ 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 + return types.computeIfAbsent(s, ConfigurationType::new); + } 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..aec36ecc0d5ce --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java @@ -0,0 +1,60 @@ +/* + * 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); + + 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)); + } + } +} 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/SerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java new file mode 100644 index 0000000000000..64906a2a1e3ec --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java @@ -0,0 +1,58 @@ +/* + * 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.ArrayList; +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 = (String) data.get("name"); + String[] parameterTypes = ((ArrayList) data.get("parameterTypes")).toArray(new String[0]); + String[] checkedExceptions = ((ArrayList) data.get("checkedExceptions")).toArray(new String[0]); + int modifiers = Integer.valueOf((String) data.get("modifiers")); + String targetConstructorClass = (String) 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..cdba6bb68e8f3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationKey.java @@ -0,0 +1,107 @@ +/* + * 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) 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()); + } + } +} 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/JDK9OrLater.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDK9OrLater.java new file mode 100644 index 0000000000000..b1353417b4d4d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDK9OrLater.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019, 2019, 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. Alibaba 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. + * + */ +package com.oracle.svm.core.jdk; + +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; + +import java.util.function.BooleanSupplier; + +public class JDK9OrLater implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return JavaVersionUtil.JAVA_SPEC >= 9; + } +} 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/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..d48fb4f69e620 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 @@ -28,11 +28,6 @@ import com.oracle.svm.core.annotate.TargetClass; @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") +@TargetClass(className = "jdk.internal.reflect.MethodAccessorGenerator", onlyWith = JDK9OrLater.class) final class Target_jdk_internal_reflect_MethodAccessorGenerator { } 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..882320b2aec55 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/SerializationRegistry.java @@ -0,0 +1,58 @@ +/* + * 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 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 addSerializationConstrutorAccessorClass(Class serializationTargetClass, Class[] parameterTypes, Class[] checkedExceptions, + int modifiers, Class targetConstructorClass); + + Object getSerializationConstrutorAccessorClass(Class serializationTargetClass, Class[] parameterTypes, Class[] checkedExceptions, + int modifiers, Class targetConstructorClass); + + @Platforms(Platform.HOSTED_ONLY.class) + static Object createSerializationConstrutorAccessorClass(Class serializationTargetClass, Class[] parameterTypes, Class[] checkedExceptions, + int modifiers, Class targetConstructorClass) { + try { + Class generatorClass = Class.forName("sun.reflect.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 SerializationConstrutorAccessor class for " + serializationTargetClass.getName(), e); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/Target_sun_reflect_MethodAccessorGenerator.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/Target_sun_reflect_MethodAccessorGenerator.java new file mode 100644 index 0000000000000..8d24e41bc9b4d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/Target_sun_reflect_MethodAccessorGenerator.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019, 2019, 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.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.JDK8OrEarlier; +import org.graalvm.nativeimage.ImageSingletons; + +@TargetClass(className = "sun.reflect.MethodAccessorGenerator", onlyWith = JDK8OrEarlier.class) +public final class Target_sun_reflect_MethodAccessorGenerator { + + @Substitute + public Target_sun_reflect_SerializationConstructorAccessorImpl generateSerializationConstructor(Class declaringClass, + Class[] parameterTypes, + Class[] checkedExceptions, + int modifiers, + Class targetConstructorClass) { + return (Target_sun_reflect_SerializationConstructorAccessorImpl) ImageSingletons.lookup(SerializationRegistry.class).getSerializationConstrutorAccessorClass(declaringClass, parameterTypes, + checkedExceptions, modifiers, targetConstructorClass); + } + +} + +@TargetClass(className = "sun.reflect.SerializationConstructorAccessorImpl") +final class Target_sun_reflect_SerializationConstructorAccessorImpl { +} 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..99c120629396c 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 @@ -58,6 +58,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 +168,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("sun.reflect.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 +195,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 +264,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 +306,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/serialize/SerializationSupport.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/SerializationSupport.java new file mode 100644 index 0000000000000..f38c4bd033284 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/SerializationSupport.java @@ -0,0 +1,70 @@ +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 { + private Map cachedSerializationConstructorAccessors; + private Map> accessorDeinitions; + + public SerializationSupport() { + cachedSerializationConstructorAccessors = new ConcurrentHashMap<>(); + accessorDeinitions = new ConcurrentHashMap<>(); + } + + public String collectMultiDefinitions() { + StringBuilder sb = new StringBuilder(); + accessorDeinitions.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 addSerializationConstrutorAccessorClass(Class serializationTargetClass, Class[] parameterTypes, Class[] checkedExceptions, + int modifiers, Class targetConstructorClass) { + SerializationKey key = new SerializationKey<>(serializationTargetClass, parameterTypes, checkedExceptions, modifiers, targetConstructorClass); + cachedSerializationConstructorAccessors.computeIfAbsent(key.toString(), (k) -> SerializationRegistry.createSerializationConstrutorAccessorClass( + serializationTargetClass, parameterTypes, checkedExceptions, modifiers, targetConstructorClass)); + String targetClass = key.getSerializationTargetClass().getName(); + if (accessorDeinitions.containsKey(targetClass)) { + accessorDeinitions.get(targetClass).add(key); + } else { + List value = new ArrayList<>(); + value.add(key); + accessorDeinitions.put(targetClass, value); + } + } + + @Override + public Object getSerializationConstrutorAccessorClass(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..3c4185473fa4c --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java @@ -0,0 +1,98 @@ +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.serialize.SerializationSupport; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; + +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(SerializationSupport.class)) { + serializationSupport = (SerializationSupport) ImageSingletons.lookup(SerializationRegistry.class); + } else { + serializationSupport = new SerializationSupport(); + ImageSingletons.add(SerializationRegistry.class, serializationSupport); + } + + 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); + serializationSupport.addSerializationConstrutorAccessorClass(serializationTargetClass, parameterTypes, checkedExceptions, serializationKey.getModifiers(), targetConstructor); + }; + + SerializationConfigurationParser parser = new SerializationConfigurationParser(serializationAdapter); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "serialization", + ConfigurationFiles.Options.SerializationConfigurationFiles, ConfigurationFiles.Options.SerializationConfigurationResources, + ConfigurationFiles.SERIALIZATION_NAME); + String expectionsMsg = serializationSupport.collectMultiDefinitions(); + if (expectionsMsg.length() > 0) { + System.out.println(expectionsMsg); + 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."); + } + } + } + + 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 + } +}