From 012872406ba6f5e2bfbdf1405c56f9988137246c Mon Sep 17 00:00:00 2001 From: Kristof Dhondt Date: Sun, 20 Dec 2020 14:22:00 +0100 Subject: [PATCH] serialization registration from inside features --- .../hosted/RuntimeSerialization.java | 57 ++++++++ .../impl/RuntimeSerializationSupport.java | 56 +++++++ .../configure/config/ConfigurationSet.java | 2 +- .../config/SerializationConfiguration.java | 100 ++++++++----- .../SerializationConfigurationType.java | 80 ++++++++++ .../trace/SerializationProcessor.java | 2 +- .../SerializationConfigurationParser.java | 25 ++-- ...ializationConfigurationParserDelegate.java | 36 +++++ .../SerializationDenyConfigurationParser.java | 38 +++++ .../config/SerializationRegistryAdapter.java | 59 ++++++++ .../serialize/SerializationSupport.java | 6 +- .../hosted/SerializationFeature.java | 138 ++++++++++-------- .../hosted}/SerializationRegistry.java | 2 +- ...dk_internal_reflect_AccessorGenerator.java | 8 +- 14 files changed, 492 insertions(+), 117 deletions(-) create mode 100644 sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java create mode 100644 sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParserDelegate.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationDenyConfigurationParser.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/SerializationRegistryAdapter.java rename substratevm/src/{com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize => com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted}/SerializationRegistry.java (96%) rename substratevm/src/{com.oracle.svm.core/src/com/oracle/svm/core/jdk => com.oracle.svm.reflect/src/com/oracle/svm/reflect/target}/Target_jdk_internal_reflect_AccessorGenerator.java (87%) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java new file mode 100644 index 0000000000000..843166d287c6b --- /dev/null +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.nativeimage.hosted; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; + +@Platforms(Platform.HOSTED_ONLY.class) +public final class RuntimeSerialization { + + public static void register(Class... classes) { + ImageSingletons.lookup(RuntimeSerializationSupport.class).register(classes); + } + + private RuntimeSerialization() { + } +} diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java new file mode 100644 index 0000000000000..2e7c748cd2892 --- /dev/null +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.nativeimage.impl; + +import java.util.Collection; +import java.util.Collections; + +public interface RuntimeSerializationSupport { + + default void register(Class... classes) { + for (Class clazz : classes) { + register(clazz, Collections.emptyList()); + } + } + + void register(Class clazz, Collection checkSums); + +} 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 dfb2c343ee31b..3bb7cfb7fe110 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 @@ -106,7 +106,7 @@ public ResourceConfiguration loadResourceConfig(Function public SerializationConfiguration loadSerializationConfig(Function exceptionHandler) throws Exception { SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); - loadConfig(serializationConfigPaths, new SerializationConfigurationParser((targetSerializationClass, checksums) -> serializationConfiguration.addAll(targetSerializationClass, checksums)), + loadConfig(serializationConfigPaths, new SerializationConfigurationParser<>(new SerializationConfiguration.ParseAdapter(serializationConfiguration)), exceptionHandler); return serializationConfiguration; } 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 index 311c4025d0384..9fbd8d412fb1b 100644 --- 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 @@ -26,60 +26,88 @@ package com.oracle.svm.configure.config; import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import com.oracle.svm.configure.json.JsonPrintable; import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.TypeResult; +import com.oracle.svm.core.configure.SerializationConfigurationParserDelegate; +import com.oracle.svm.core.util.UserError; +import jdk.vm.ci.meta.JavaKind; public class SerializationConfiguration implements JsonPrintable { - private final ConcurrentHashMap> serializations = new ConcurrentHashMap<>(); + public static class ParseAdapter implements SerializationConfigurationParserDelegate { - public void addAll(String serializationTargetClass, Collection checksums) { - serializations.computeIfAbsent(serializationTargetClass, key -> new LinkedHashSet<>()).addAll(checksums); - } + private final SerializationConfiguration configuration; + + public ParseAdapter(SerializationConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public void registerType(SerializationConfigurationType type, List checkSums) { + configuration.add(type, checkSums); + } - public void add(String serializationTargetClass, String checksum) { - if (checksum == null) { - addAll(serializationTargetClass, Collections.emptySet()); - } else { - addAll(serializationTargetClass, Collections.singleton(checksum)); + @Override + public TypeResult resolveTypeResult(String typeName) { + SerializationConfigurationType type = configuration.get(typeName); + SerializationConfigurationType result = type != null ? type : new SerializationConfigurationType(typeName); + return TypeResult.forType(typeName, result); } } - public boolean contains(String serializationTargetClass, String checksum) { - Set checksums = serializations.get(serializationTargetClass); - return checksums != null && checksums.contains(checksum); + private final ConcurrentHashMap serializations = new ConcurrentHashMap<>(); + + public SerializationConfigurationType get(String qualifiedJavaName) { + return serializations.get(qualifiedJavaName); + } + + public void add(SerializationConfigurationType type, List checksums) { + SerializationConfigurationType previous = serializations.putIfAbsent(type.getQualifiedJavaName(), type); + UserError.guarantee(previous == null || previous == type, "Cannot replace existing type %s with %s", previous, type); + type.addCheckSums(checksums); + } + + public SerializationConfigurationType getOrCreateType(String qualifiedForNameString) { + assert qualifiedForNameString.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; + assert !qualifiedForNameString.endsWith("[]") : "Requires Class.forName syntax, for example '[Ljava.lang.String;'"; + + String s = qualifiedForNameString; + int n = 0; + while (n < s.length() && s.charAt(n) == '[') { + n++; + } + if (n > 0) { // transform to Java source syntax + StringBuilder sb = new StringBuilder(s.length() + n); + 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 if (n == s.length() - 1) { + sb.append(JavaKind.fromPrimitiveOrVoidTypeChar(s.charAt(n)).getJavaName()); + } else { + throw new IllegalArgumentException(); + } + for (int i = 0; i < n; i++) { + sb.append("[]"); + } + s = sb.toString(); + } + return serializations.computeIfAbsent(s, SerializationConfigurationType::new); } @Override public void printJson(JsonWriter writer) throws IOException { writer.append('[').indent(); String prefix = ""; - for (Map.Entry> entry : serializations.entrySet()) { - writer.append(prefix); - writer.newline().append('{').newline(); - String className = entry.getKey(); - writer.quote("name").append(":").quote(className); - Set checksums = entry.getValue(); - if (!checksums.isEmpty()) { - writer.append(",").newline(); - writer.quote("checksum").append(':'); - if (checksums.size() == 1) { - writer.quote(checksums.iterator().next()); - } else { - writer.append(checksums.stream() - .map(JsonWriter::quoteString) - .collect(Collectors.joining(", ", "[", "]"))); - } - } - writer.newline().append('}'); + List list = new ArrayList<>(serializations.values()); + list.sort(Comparator.comparing(SerializationConfigurationType::getQualifiedJavaName)); + for (SerializationConfigurationType type : list) { + writer.append(prefix).newline(); + type.printJson(writer); prefix = ","; } writer.unindent().newline(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java new file mode 100644 index 0000000000000..b2dcde30d2feb --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. 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 java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import com.oracle.svm.configure.json.JsonPrintable; +import com.oracle.svm.configure.json.JsonWriter; + +public class SerializationConfigurationType implements JsonPrintable { + + private final String qualifiedJavaName; + private Set checkSums; + + public SerializationConfigurationType(String qualifiedJavaName) { + assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; + assert !qualifiedJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]"; + this.qualifiedJavaName = qualifiedJavaName; + } + + public String getQualifiedJavaName() { + return qualifiedJavaName; + } + + public void addCheckSums(Collection checkSumsToAdd) { + if (checkSums == null) { + checkSums = new HashSet<>(); + } + checkSums.addAll(checkSumsToAdd); + } + + public void addCheckSum(String checkSum) { + addCheckSums(Collections.singleton(checkSum)); + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.append('{').indent().newline(); + writer.quote("name").append(':').quote(qualifiedJavaName); + if (checkSums != null && !checkSums.isEmpty()) { + writer.append(',').newline(); + writer.quote("checksum").append(':'); + if (checkSums.size() == 1) { + writer.quote(checkSums.iterator().next()); + } else { + writer.append(checkSums.stream() + .map(JsonWriter::quoteString) + .collect(Collectors.joining(", ", "[", "]"))); + } + } + writer.unindent().newline().append('}'); + } +} 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 index bd26ab00ee799..215afe103ef4f 100644 --- 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 @@ -51,7 +51,7 @@ void processEntry(Map entry) { List args = (List) entry.get("args"); if ("ObjectStreamClass.".equals(function)) { expectSize(args, 2); - serializationConfiguration.add((String) args.get(0), (String) args.get(1)); + serializationConfiguration.getOrCreateType((String) args.get(0)).addCheckSum((String) args.get(1)); } } } 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 index a98d3635c7feb..10a51aa74001b 100644 --- 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 @@ -32,14 +32,15 @@ import java.util.List; import java.util.Map; +import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.util.json.JSONParser; import com.oracle.svm.core.util.json.JSONParserException; -public class SerializationConfigurationParser extends ConfigurationParser { - private final SerializationParserFunction consumer; +public class SerializationConfigurationParser extends ConfigurationParser { + private final SerializationConfigurationParserDelegate delegate; - public SerializationConfigurationParser(SerializationParserFunction consumer) { - this.consumer = consumer; + public SerializationConfigurationParser(SerializationConfigurationParserDelegate delegate) { + this.delegate = delegate; } @Override @@ -48,7 +49,7 @@ public void parseAndRegister(Reader reader) throws IOException { 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 className = asString(data.get("name")); Object checksumValue = data.get("checksum"); List checksums = new ArrayList<>(); if (checksumValue != null) { @@ -62,12 +63,18 @@ public void parseAndRegister(Reader reader) throws IOException { checksums.add(asString(jsonChecksum, "checksum")); } } - consumer.accept(targetSerializationClass, checksums); + TypeResult result = delegate.resolveTypeResult(className); + if (result.isPresent()) { + delegate.registerType(result.get(), checksums); + } else { + handleUnresolvedClass(className); + } } } - @FunctionalInterface - public interface SerializationParserFunction { - void accept(String targetSerializationClass, List checksum); + protected void handleUnresolvedClass(String className) { + throw new JSONParserException("Could not resolve " + className + " during serialization configuration." + + " The missing of this class can't be ignored even if -H:+AllowIncompleteClasspath is set." + + " Please make sure it is in the classpath"); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParserDelegate.java new file mode 100644 index 0000000000000..597cf3785054a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParserDelegate.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. 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.TypeResult; + +import java.util.List; + +public interface SerializationConfigurationParserDelegate { + + void registerType(T type, List checkSums); + + TypeResult resolveTypeResult(String typeName); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationDenyConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationDenyConfigurationParser.java new file mode 100644 index 0000000000000..5285aaafce5a0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationDenyConfigurationParser.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. 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; + +public class SerializationDenyConfigurationParser extends SerializationConfigurationParser> { + public SerializationDenyConfigurationParser(SerializationConfigurationParserDelegate> delegate) { + super(delegate); + } + + @Override + protected void handleUnresolvedClass(String className) { + // Checkstyle: stop + System.out.println("WARNING: Could not resolve " + className + " during serialization-deny configuration."); + // Checkstyle: resume + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/SerializationRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/SerializationRegistryAdapter.java new file mode 100644 index 0000000000000..748529339aa70 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/SerializationRegistryAdapter.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. 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.hosted.config; + +import com.oracle.svm.core.TypeResult; +import com.oracle.svm.core.configure.SerializationConfigurationParserDelegate; +import com.oracle.svm.hosted.ImageClassLoader; +import jdk.vm.ci.meta.MetaUtil; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; + +import java.util.List; + +public class SerializationRegistryAdapter implements SerializationConfigurationParserDelegate> { + + private final RuntimeSerializationSupport serializationSupport; + private final ImageClassLoader classLoader; + + public SerializationRegistryAdapter(RuntimeSerializationSupport serializationSupport, ImageClassLoader classLoader) { + this.serializationSupport = serializationSupport; + this.classLoader = classLoader; + } + + @Override + public void registerType(Class type, List checkSums) { + serializationSupport.register(type, checkSums); + } + + @Override + public TypeResult> resolveTypeResult(String typeName) { + String name = typeName; + if (name.indexOf('[') != -1) { + /* accept "int[][]", "java.lang.String[]" */ + name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true); + } + return classLoader.findClass(name); + } +} 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 index 501d3487e54ce..815e54eda7e6d 100644 --- 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 @@ -33,7 +33,7 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import com.oracle.svm.core.jdk.serialize.SerializationRegistry; +import com.oracle.svm.reflect.serialize.hosted.SerializationRegistry; import com.oracle.svm.core.util.VMError; public class SerializationSupport implements SerializationRegistry { @@ -44,7 +44,7 @@ public class SerializationSupport implements SerializationRegistry { * news the class specified by generateSerializationConstructor's first parameter declaringClass * and then calls declaringClass' first non-serializable superclass. The bytecode of the * generated class looks like: - * + * *
      * jdk.internal.reflect.GeneratedSerializationConstructorAccessor2.newInstance(Unknown Source)
      * [bci: 0, intrinsic: false] 
@@ -57,7 +57,7 @@ public class SerializationSupport implements SerializationRegistry {
      * 10: sipush 0
      * ...
      * 
- * + * * The declaringClass could be an abstract class. At deserialization time, * SerializationConstructorAccessorImpl classes are generated for the target class and all of * its serializable super classes. The super classes could be abstract. So it is possible to 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 index a76fd32871817..4e3cf8f4d4118 100644 --- 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 @@ -37,85 +37,91 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Collection; import java.net.URL; import java.net.URLClassLoader; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.SerializationConfigurationParser; -import com.oracle.svm.core.configure.SerializationConfigurationParser.SerializationParserFunction; +import com.oracle.svm.core.configure.SerializationDenyConfigurationParser; import com.oracle.svm.core.jdk.Package_jdk_internal_reflect; -import com.oracle.svm.core.jdk.serialize.SerializationRegistry; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; -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.hosted.config.SerializationRegistryAdapter; import com.oracle.svm.reflect.serialize.SerializationSupport; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.SerializationChecksumCalculator; -import jdk.vm.ci.meta.MetaUtil; - @AutomaticFeature public class SerializationFeature implements Feature { + private SerializationBuilder serializationBuilder; private int loadedConfigurations; + private final Map, Boolean> deniedClasses = new HashMap<>(); + private final Map, Set> newClasses = new ConcurrentHashMap<>(); + + private boolean sealed; + + private void abortIfSealed() { + UserError.guarantee(!sealed, "Too late to add classes for serialization. Registration must happen in a Feature before the analysis has finished."); + } + @Override public void beforeAnalysis(BeforeAnalysisAccess a) { FeatureImpl.BeforeAnalysisAccessImpl access = (FeatureImpl.BeforeAnalysisAccessImpl) a; - SerializationBuilder serializationBuilder = new SerializationBuilder(access); - Map, Boolean> deniedClasses = new HashMap<>(); - SerializationConfigurationParser denyCollectorParser = new SerializationConfigurationParser((strTargetSerializationClass, checksums) -> { - Class serializationTargetClass = resolveClass(strTargetSerializationClass, access); - if (serializationTargetClass != null) { - deniedClasses.put(serializationTargetClass, true); - } - }); + RuntimeSerializationSupportImpl runtimeSerializationSupport = new RuntimeSerializationSupportImpl(); + ImageSingletons.add(RuntimeSerializationSupport.class, runtimeSerializationSupport); + + serializationBuilder = new SerializationBuilder(access, runtimeSerializationSupport); + ImageClassLoader imageClassLoader = access.getImageClassLoader(); + SerializationDenyConfigurationParser denyCollectorParser = new SerializationDenyConfigurationParser( + new SerializationRegistryAdapter((clazz, checkSums) -> deniedClasses.put(clazz, true), imageClassLoader)); ConfigurationParserUtils.parseAndRegisterConfigurations(denyCollectorParser, imageClassLoader, "serialization", ConfigurationFiles.Options.SerializationDenyConfigurationFiles, ConfigurationFiles.Options.SerializationDenyConfigurationResources, ConfigurationFiles.SERIALIZATION_DENY_NAME); - SerializationParserFunction serializationAdapter = (strTargetSerializationClass, checksums) -> { - Class serializationTargetClass = resolveClass(strTargetSerializationClass, access); - UserError.guarantee(serializationTargetClass != null, "Cannot find serialization target class %s. The missing of this class can't be ignored even if -H:+AllowIncompleteClasspath is set." + - " Please make sure it is in the classpath", strTargetSerializationClass); - if (Serializable.class.isAssignableFrom(serializationTargetClass)) { - if (deniedClasses.containsKey(serializationTargetClass)) { - if (deniedClasses.get(serializationTargetClass)) { - deniedClasses.put(serializationTargetClass, false); /* Warn only once */ - println("Warning: Serialization deny list contains " + serializationTargetClass.getName() + ". Image will not support serialization/deserialization of this class."); - } - } else { - Class targetConstructor = serializationBuilder.addConstructorAccessor(serializationTargetClass, checksums); - addReflections(serializationTargetClass, targetConstructor); - } - } - }; - - SerializationConfigurationParser parser = new SerializationConfigurationParser(serializationAdapter); + SerializationConfigurationParser> parser = new SerializationConfigurationParser<>(new SerializationRegistryAdapter(runtimeSerializationSupport, imageClassLoader)); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "serialization", ConfigurationFiles.Options.SerializationConfigurationFiles, ConfigurationFiles.Options.SerializationConfigurationResources, ConfigurationFiles.SERIALIZATION_NAME); } - public static void addReflections(Class serializationTargetClass, Class targetConstructorClass) { + @Override + public void duringAnalysis(DuringAnalysisAccess a) { + if (newClasses.isEmpty()) { + return; + } + FeatureImpl.DuringAnalysisAccessImpl access = (FeatureImpl.DuringAnalysisAccessImpl) a; + for (Map.Entry, Set> entry : newClasses.entrySet()) { + Class targetConstructor = serializationBuilder.addConstructorAccessor(entry.getKey(), entry.getValue()); + addReflections(entry.getKey(), targetConstructor); + } + newClasses.clear(); + + access.requireAnalysisIteration(); + } + + private static void addReflections(Class serializationTargetClass, Class targetConstructorClass) { if (targetConstructorClass != null) { RuntimeReflection.register(ReflectionUtil.lookupConstructor(targetConstructorClass)); } @@ -171,36 +177,21 @@ private static void registerFields(Class serializationTargetClass) { } } - private static Class resolveClass(String typeName, FeatureAccess a) { - String name = typeName; - if (name.indexOf('[') != -1) { - /* accept "int[][]", "java.lang.String[]" */ - name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true); - } - Class ret = a.findClassByName(name); - if (ret == null) { - handleError("Could not resolve " + name + " for serialization configuration."); + @Override + public void afterAnalysis(AfterAnalysisAccess access) { + sealed = true; + if (!newClasses.isEmpty()) { + abortIfSealed(); } - return ret; } @Override public void beforeCompilation(BeforeCompilationAccess access) { - if (!ImageSingletons.contains(FallbackFeature.class)) { - return; - } - FallbackFeature.FallbackImageRequest serializationFallback = ImageSingletons.lookup(FallbackFeature.class).serializationFallback; - if (serializationFallback != null && loadedConfigurations == 0) { - throw serializationFallback; - } - } - - private static void handleError(String message) { - boolean allowIncompleteClasspath = NativeImageOptions.AllowIncompleteClasspath.getValue(); - if (allowIncompleteClasspath) { - println("WARNING: " + message); - } else { - throw new JSONParserException(message + " To allow unresolvable reflection configuration, use option -H:+AllowIncompleteClasspath"); + if (ImageSingletons.contains(FallbackFeature.class)) { + FallbackFeature.FallbackImageRequest serializationFallback = ImageSingletons.lookup(FallbackFeature.class).serializationFallback; + if (serializationFallback != null && loadedConfigurations == 0) { + throw serializationFallback; + } } } @@ -209,6 +200,28 @@ static void println(String str) { System.out.println(str); // Checkstyle: resume } + + private class RuntimeSerializationSupportImpl extends SerializationSupport implements RuntimeSerializationSupport { + + @Override + public void register(Class clazz, Collection checkSums) { + abortIfSealed(); + if (!Serializable.class.isAssignableFrom(clazz)) { + println("WARNING: Could not register " + clazz.getName() + " for serialization as it does not implement Serializable."); + } else if (deniedClasses.containsKey(clazz)) { + if (deniedClasses.get(clazz)) { + deniedClasses.put(clazz, false); /* Warn only once */ + println("WARNING: Serialization deny list contains " + clazz.getName() + ". Image will not support serialization/deserialization of this class."); + } + } else { + newClasses.compute(clazz, (k, v) -> { + Set storedCheckSums = v != null ? v : new HashSet<>(); + storedCheckSums.addAll(checkSums); + return storedCheckSums; + }); + } + } + } } final class SerializationBuilder { @@ -267,7 +280,7 @@ protected boolean isClassAbstract(Class clazz) { private final SerializationSupport serializationSupport; - SerializationBuilder(FeatureImpl.BeforeAnalysisAccessImpl access) { + SerializationBuilder(FeatureImpl.BeforeAnalysisAccessImpl access, SerializationSupport serializationSupport) { try { Class reflectionFactoryClass = access.findClassByName(Package_jdk_internal_reflect.getQualifiedName() + ".ReflectionFactory"); Method getReflectionFactoryMethod = ReflectionUtil.lookupMethod(reflectionFactoryClass, "getReflectionFactory"); @@ -285,8 +298,7 @@ protected boolean isClassAbstract(Class clazz) { serializationChecksumClassLoader = new SerializationChecksumClassLoader(cl.getURLs(), cl.getParent()); checksumCalculator = new ChecksumCalculator(); - serializationSupport = new SerializationSupport(); - ImageSingletons.add(SerializationRegistry.class, serializationSupport); + this.serializationSupport = serializationSupport; } private Constructor newConstructorForSerialization(Class serializationTargetClass) { @@ -313,7 +325,7 @@ private Constructor getExternalizableConstructor(Class serializationTarget } } - Class addConstructorAccessor(Class serializationTargetClass, List configuredChecksums) { + Class addConstructorAccessor(Class serializationTargetClass, Collection configuredChecksums) { if (serializationTargetClass.isArray() || Enum.class.isAssignableFrom(serializationTargetClass)) { return null; } @@ -347,7 +359,7 @@ Class addConstructorAccessor(Class serializationTargetClass, List return targetConstructorClass; } - private void verifyBuildTimeChecksum(Class serializationTargetClass, Class targetConstructorClass, List configuredChecksums) { + private void verifyBuildTimeChecksum(Class serializationTargetClass, Class targetConstructorClass, Collection configuredChecksums) { if (configuredChecksums.isEmpty()) { return; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/SerializationRegistry.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationRegistry.java similarity index 96% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/SerializationRegistry.java rename to substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationRegistry.java index 82487938e327f..f487287c2bb32 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/serialize/SerializationRegistry.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationRegistry.java @@ -23,7 +23,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.jdk.serialize; +package com.oracle.svm.reflect.serialize.hosted; public interface SerializationRegistry { 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.reflect/src/com/oracle/svm/reflect/target/Target_jdk_internal_reflect_AccessorGenerator.java similarity index 87% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_AccessorGenerator.java rename to substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_jdk_internal_reflect_AccessorGenerator.java index 0640148db7a8c..eb8aceadb0f61 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.reflect/src/com/oracle/svm/reflect/target/Target_jdk_internal_reflect_AccessorGenerator.java @@ -22,13 +22,15 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.jdk; +package com.oracle.svm.reflect.target; import org.graalvm.nativeimage.ImageSingletons; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.jdk.serialize.SerializationRegistry; +import com.oracle.svm.core.jdk.Package_jdk_internal_reflect; +import com.oracle.svm.reflect.serialize.hosted.SerializationRegistry; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; @TargetClass(classNameProvider = Package_jdk_internal_reflect.class, className = "AccessorGenerator") public final class Target_jdk_internal_reflect_AccessorGenerator { @@ -44,7 +46,7 @@ public Target_jdk_internal_reflect_SerializationConstructorAccessorImpl generate @SuppressWarnings("unused") Class[] checkedExceptions, @SuppressWarnings("unused") int modifiers, Class targetConstructorClass) { - SerializationRegistry serializationRegistry = ImageSingletons.lookup(SerializationRegistry.class); + SerializationRegistry serializationRegistry = (SerializationRegistry) ImageSingletons.lookup(RuntimeSerializationSupport.class); Object constructorAccessor = serializationRegistry.getSerializationConstructorAccessor(declaringClass, targetConstructorClass); return (Target_jdk_internal_reflect_SerializationConstructorAccessorImpl) constructorAccessor; }