From 80c9ff8f66a5708f64d5c0ac48f75e41d57fd915 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 17 Jul 2021 07:54:34 +0200 Subject: [PATCH] Conditional reflection and serialization config Introducing conditions for reflection, JNI, and serialization configuration. The condition can be only a type name. For example, "condition": { "typeReachable": "org.graalvm.ReachableClass" }, will apply configuration only if type "org.graalvm.ReachableClass" is reachable. The default condition for every reflection entry is "java.lang.Object" and as such it will not be printed. The Native Image agent does not emit conditions, but will fuse conditions accordingly. Instead all config of one type, the fusion will happen by the condition and the type. --- .../native-image/Reflection.md | 35 +++++- .../nativeimage/hosted/RuntimeReflection.java | 7 +- .../hosted/RuntimeSerialization.java | 5 +- .../impl/ConfigurationCondition.java | 90 +++++++++++++++ .../nativeimage/impl/ReflectionRegistry.java | 6 +- .../impl/RuntimeSerializationSupport.java | 7 +- .../test/config/OmitPreviousConfigTests.java | 18 +-- .../ConfigurationConditionPrintable.java | 44 ++++++++ .../configure/config/ConfigurationType.java | 21 +++- .../config/ParserConfigurationAdapter.java | 15 ++- .../config/SerializationConfiguration.java | 24 ++-- .../SerializationConfigurationType.java | 35 +++--- .../configure/config/TypeConfiguration.java | 31 +++--- .../svm/configure/trace/JniProcessor.java | 20 ++-- .../configure/trace/ReflectionProcessor.java | 38 ++++--- .../trace/SerializationProcessor.java | 5 +- .../src/com/oracle/svm/core/TypeResult.java | 12 +- .../core/configure/ConditionalElement.java | 66 +++++++++++ .../core/configure/ConfigurationParser.java | 37 +++++-- .../ReflectionConfigurationParser.java | 23 ++-- ...ReflectionConfigurationParserDelegate.java | 12 +- .../SerializationConfigurationParser.java | 8 +- .../SerializationConfigurationFilesHelp.txt | 2 + .../oracle/svm/core/jni/JNIRuntimeAccess.java | 7 +- .../hotspot/libgraal/LibGraalFeature.java | 12 +- .../ConditionalConfigurationRegistry.java | 65 +++++++++++ .../config/ConfigurationParserUtils.java | 3 +- .../config/ReflectionRegistryAdapter.java | 104 ++++++++++-------- .../svm/jni/access/JNIAccessFeature.java | 32 ++++-- .../reflect/hosted/ReflectionDataBuilder.java | 23 +++- .../svm/reflect/hosted/ReflectionFeature.java | 12 +- .../hosted/SerializationFeature.java | 87 +++++++-------- 32 files changed, 665 insertions(+), 241 deletions(-) create mode 100644 sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationConditionPrintable.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalElement.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java diff --git a/docs/reference-manual/native-image/Reflection.md b/docs/reference-manual/native-image/Reflection.md index ccc0f52babad..483653e2b70e 100644 --- a/docs/reference-manual/native-image/Reflection.md +++ b/docs/reference-manual/native-image/Reflection.md @@ -107,14 +107,15 @@ Here, `reflectconfig` is a JSON file in the following format (use `--expert-opti { "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] } ] }, - { - "name" : "java.lang.String$CaseInsensitiveComparator", - "methods" : [ - { "name" : "compare" } - ] - } + { + "name" : "java.lang.String$CaseInsensitiveComparator", + "methods" : [ + { "name" : "compare" } + ] + } ] ``` + The native image builder generates reflection metadata for all classes, methods, and fields referenced in that file. The `allPublicConstructors`, `allDeclaredConstructors`, `allPublicMethods`, `allDeclaredMethods`, `allPublicFields`, `allDeclaredFields`, `allPublicClasses`, and `allDeclaredClasses` attributes can be used to automatically include an entire set of members of a class. @@ -125,6 +126,28 @@ Code may also write non-static final fields like `String.value` in this example, More than one configuration can be used by specifying multiple paths for `ReflectionConfigurationFiles` and separating them with `,`. Also, `-H:ReflectionConfigurationResources` can be specified to load one or several configuration files from the native image build's class path, such as from a JAR file. +### Conditional Configuration + +With conditional configuraiton, a class configuration entry is applied only if a provided `condition` is satisfied. The only currently supported condition is `typeReachable`, which enables the configuration entry if the specified type is reachable through other code. For example, to support reflective access to `sun.misc.Unsafe.theUnsafe` only when `io.netty.util.internal.PlatformDependent0` is reachable, the configuration should look like: + +```json +{ + "condition" : { "typeReachable" : "io.netty.util.internal.PlatformDependent0" }, + "name" : "sun.misc.Unsafe", + "fields" : [ + { "name" : "theUnsafe" } + ] +} +``` + +Conditional configuration is the *preferred* way to specify reflection configuration: if code doing a reflective access is not reachable, it is unnecessary to include its corresponding reflection entry. The consistent usage of `condition` results in *smaller binaries* and *better build times* as the image builder can selectively include reflectively accessed code. + +If a `condition` is omitted, the element is always included. When the same `condition` is used for two distinct elements in two configuration entries, both elements will be included when the condition is satisfied. When a configuration entry should be enabled if one of several types are reachable, it is necessary to add two configuration entries: one entry for each condition. + +When used with [assisted configuration](BuildConfiguration.md#assisted-configuration-of-native-image-builds), conditional entries of existing configuration will not be fused with agent-collected entries as agent-collected entries. + +### Configuration with Features + Alternatively, a custom `Feature` implementation can register program elements before and during the analysis phase of the native image build using the `RuntimeReflection` class. For example: ```java class RuntimeReflectionRegistrationFeature implements Feature { diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java index 93750f1e4de3..bdc7321ecb6e 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java @@ -48,6 +48,7 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; //Checkstyle: allow reflection @@ -68,7 +69,7 @@ public final class RuntimeReflection { * @since 19.0 */ public static void register(Class... classes) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).register(classes); + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.objectReachable(), classes); } /** @@ -79,7 +80,7 @@ public static void register(Class... classes) { * @since 19.0 */ public static void register(Executable... methods) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).register(methods); + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.objectReachable(), methods); } /** @@ -90,7 +91,7 @@ public static void register(Executable... methods) { * @since 19.0 */ public static void register(Field... fields) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).register(false, fields); + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.objectReachable(), false, fields); } /** 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 index 220d47c7ef1e..738d5e0fea9c 100644 --- 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 @@ -43,6 +43,7 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; /** @@ -60,7 +61,7 @@ public final class RuntimeSerialization { * @since 21.3 */ public static void register(Class... classes) { - ImageSingletons.lookup(RuntimeSerializationSupport.class).register(classes); + ImageSingletons.lookup(RuntimeSerializationSupport.class).register(ConfigurationCondition.objectReachable(), classes); } /** @@ -75,7 +76,7 @@ public static void register(Class... classes) { * @since 21.3 */ public static void registerWithTargetConstructorClass(Class clazz, Class customTargetConstructorClazz) { - ImageSingletons.lookup(RuntimeSerializationSupport.class).registerWithTargetConstructorClass(clazz, customTargetConstructorClazz); + ImageSingletons.lookup(RuntimeSerializationSupport.class).registerWithTargetConstructorClass(ConfigurationCondition.objectReachable(), clazz, customTargetConstructorClazz); } private RuntimeSerialization() { diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java new file mode 100644 index 000000000000..a1fa9d6d8538 --- /dev/null +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2021, 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.Objects; + +public final class ConfigurationCondition implements Comparable { + private final String typeName; + private static final ConfigurationCondition OBJECT_REACHABLE = new ConfigurationCondition(Object.class.getTypeName()); + + public static ConfigurationCondition objectReachable() { + return OBJECT_REACHABLE; + } + + public static ConfigurationCondition create(String typeReachability) { + Objects.requireNonNull(typeReachability); + if (OBJECT_REACHABLE.typeName.equals(typeReachability)) { + return OBJECT_REACHABLE; + } + return new ConfigurationCondition(typeReachability); + } + + private ConfigurationCondition(String typeName) { + this.typeName = typeName; + } + + public String getTypeName() { + return typeName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConfigurationCondition condition = (ConfigurationCondition) o; + return Objects.equals(typeName, condition.typeName); + } + + @Override + public int hashCode() { + return Objects.hash(typeName); + } + + @Override + public int compareTo(ConfigurationCondition o) { + return this.typeName.compareTo(o.typeName); + } +} diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java index 8c568a4ac101..7890a95893d7 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java @@ -44,10 +44,10 @@ import java.lang.reflect.Field; public interface ReflectionRegistry { - void register(Class... classes); + void register(ConfigurationCondition condition, Class... classes); - void register(Executable... methods); + void register(ConfigurationCondition condition, Executable... methods); - void register(boolean finalIsWritable, Field... fields); + void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields); } 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 index f0cdcb979384..6f4952f687cd 100644 --- 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 @@ -42,9 +42,10 @@ public interface RuntimeSerializationSupport { - void register(Class... classes); + void register(ConfigurationCondition condition, Class... classes); - void registerWithTargetConstructorClass(Class clazz, Class customTargetConstructorClazz); + void registerWithTargetConstructorClass(ConfigurationCondition condition, Class clazz, Class customTargetConstructorClazz); + + void registerWithTargetConstructorClass(ConfigurationCondition condition, String className, String customTargetConstructorClassName); - void registerWithTargetConstructorClass(String className, String customTargetConstructorClassName); } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java index bff52d830a21..af149ec37399 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java @@ -35,6 +35,7 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.junit.Assert; import org.junit.Test; @@ -145,8 +146,8 @@ private static void doTestTypeConfig(TypeConfiguration typeConfig) { } private static void doTestExpectedMissingTypes(TypeConfiguration typeConfig) { - Assert.assertNull(typeConfig.get("FlagTestA")); - Assert.assertNull(typeConfig.get("FlagTestB")); + Assert.assertNull(typeConfig.get(ConfigurationCondition.objectReachable(), "FlagTestA")); + Assert.assertNull(typeConfig.get(ConfigurationCondition.objectReachable(), "FlagTestB")); } private static void doTestTypeFlags(TypeConfiguration typeConfig) { @@ -194,12 +195,13 @@ private static void doTestResourceConfig(ResourceConfiguration resourceConfig) { } private static void doTestSerializationConfig(SerializationConfiguration serializationConfig) { - Assert.assertFalse(serializationConfig.contains("seenType", null)); - Assert.assertTrue(serializationConfig.contains("unseenType", null)); + ConfigurationCondition condition = ConfigurationCondition.objectReachable(); + Assert.assertFalse(serializationConfig.contains(condition, "seenType", null)); + Assert.assertTrue(serializationConfig.contains(condition, "unseenType", null)); } private static ConfigurationType getConfigTypeOrFail(TypeConfiguration typeConfig, String typeName) { - ConfigurationType type = typeConfig.get(typeName); + ConfigurationType type = typeConfig.get(ConfigurationCondition.objectReachable(), typeName); Assert.assertNotNull(type); return type; } @@ -259,11 +261,11 @@ Map getMethodsMap(ConfigurationMem } void populateConfig() { - ConfigurationType oldType = new ConfigurationType(getTypeName()); + ConfigurationType oldType = new ConfigurationType(ConfigurationCondition.objectReachable(), getTypeName()); setFlags(oldType); previousConfig.add(oldType); - ConfigurationType newType = new ConfigurationType(getTypeName()); + ConfigurationType newType = new ConfigurationType(ConfigurationCondition.objectReachable(), getTypeName()); for (Map.Entry methodEntry : methodsThatMustExist.entrySet()) { newType.addMethod(methodEntry.getKey().getName(), methodEntry.getKey().getInternalSignature(), methodEntry.getValue()); } @@ -294,7 +296,7 @@ String getTypeName() { void doTest() { String name = getTypeName(); - ConfigurationType configurationType = currentConfig.get(name); + ConfigurationType configurationType = currentConfig.get(ConfigurationCondition.objectReachable(), name); if (methodsThatMustExist.size() == 0) { Assert.assertNull("Generated configuration type " + name + " exists. Expected it to be cleared as it is empty.", configurationType); } else { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationConditionPrintable.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationConditionPrintable.java new file mode 100644 index 000000000000..9cb6d7164bb6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationConditionPrintable.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, 2021, 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 static com.oracle.svm.core.configure.ConfigurationParser.CONDITIONAL_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.TYPE_REACHABLE_KEY; + +import java.io.IOException; + +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.configure.json.JsonWriter; + +final class ConfigurationConditionPrintable { + static void printConditionAttribute(ConfigurationCondition condition, JsonWriter writer) throws IOException { + if (!condition.equals(ConfigurationCondition.objectReachable())) { + writer.quote(CONDITIONAL_KEY).append(":{"); + writer.quote(TYPE_REACHABLE_KEY).append(':').quote(condition.getTypeName()); + writer.append("},").newline(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index 2f1ee8fb9ebe..eca768ac720f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -31,11 +31,14 @@ import java.util.function.BiPredicate; import java.util.function.Consumer; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + import com.oracle.svm.configure.json.JsonPrintable; import com.oracle.svm.configure.json.JsonPrinter; import com.oracle.svm.configure.json.JsonWriter; public class ConfigurationType implements JsonPrintable { + private final ConfigurationCondition condition; private final String qualifiedJavaName; private Map fields; @@ -50,18 +53,21 @@ public class ConfigurationType implements JsonPrintable { private boolean allDeclaredConstructors; private boolean allPublicConstructors; - public ConfigurationType(String qualifiedJavaName) { + public ConfigurationType(ConfigurationCondition condition, 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.condition = condition; this.qualifiedJavaName = qualifiedJavaName; } public ConfigurationType(ConfigurationType other) { qualifiedJavaName = other.qualifiedJavaName; + condition = other.condition; mergeWith(other); } public void mergeWith(ConfigurationType other) { + assert condition.equals(other.condition); assert qualifiedJavaName.equals(other.qualifiedJavaName); mergeFlagsWith(other); mergeFieldsWith(other); @@ -134,6 +140,8 @@ private void maybeRemoveMethods(boolean hasAllDeclaredMethods, boolean hasAllPub } public void removeAll(ConfigurationType other) { + assert condition.equals(other.condition); + assert qualifiedJavaName.equals(other.qualifiedJavaName); removeFlags(other); removeFields(other); removeMethods(other); @@ -317,6 +325,9 @@ public void setAllPublicConstructors() { @Override public void printJson(JsonWriter writer) throws IOException { writer.append('{').indent().newline(); + + ConfigurationConditionPrintable.printConditionAttribute(condition, writer); + writer.quote("name").append(':').quote(qualifiedJavaName); optionallyPrintJsonBoolean(writer, haveAllDeclaredFields(), "allDeclaredFields"); optionallyPrintJsonBoolean(writer, haveAllPublicFields(), "allPublicFields"); @@ -337,8 +348,8 @@ public void printJson(JsonWriter writer) throws IOException { Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))), JsonPrintable::printJson); } - writer.unindent().newline(); - writer.append('}'); + + writer.append('}').unindent().newline(); } private static void printField(Map.Entry entry, JsonWriter w) throws IOException { @@ -373,4 +384,8 @@ private static Map maybeRemove(Map fromMap, Consumer resolveTypeResult(String typeName) { - ConfigurationType type = configuration.get(typeName); - ConfigurationType result = type != null ? type : new ConfigurationType(typeName); + public TypeResult resolveCondition(String typeName) { + return TypeResult.forType(typeName, ConfigurationCondition.create(typeName)); + } + + @Override + public TypeResult resolveType(ConfigurationCondition condition, String typeName) { + ConfigurationType type = configuration.get(condition, typeName); + ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeName); return TypeResult.forType(typeName, result); } 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 425874cf35d7..a9347464e645 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 @@ -32,9 +32,11 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; + import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.json.JsonWriter; -import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; public class SerializationConfiguration implements ConfigurationBase, RuntimeSerializationSupport { @@ -51,8 +53,8 @@ public void removeAll(SerializationConfiguration other) { serializations.removeAll(other.serializations); } - public boolean contains(String serializationTargetClass, String customTargetConstructorClass) { - return serializations.contains(createConfigurationType(serializationTargetClass, customTargetConstructorClass)); + public boolean contains(ConfigurationCondition condition, String serializationTargetClass, String customTargetConstructorClass) { + return serializations.contains(createConfigurationType(condition, serializationTargetClass, customTargetConstructorClass)); } @Override @@ -71,20 +73,20 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public void register(Class... classes) { + public void register(ConfigurationCondition condition, Class... classes) { for (Class clazz : classes) { - registerWithTargetConstructorClass(clazz, null); + registerWithTargetConstructorClass(condition, clazz, null); } } @Override - public void registerWithTargetConstructorClass(Class clazz, Class customTargetConstructorClazz) { - registerWithTargetConstructorClass(clazz.getName(), customTargetConstructorClazz == null ? null : customTargetConstructorClazz.getName()); + public void registerWithTargetConstructorClass(ConfigurationCondition condition, Class clazz, Class customTargetConstructorClazz) { + registerWithTargetConstructorClass(condition, clazz.getName(), customTargetConstructorClazz == null ? null : customTargetConstructorClazz.getName()); } @Override - public void registerWithTargetConstructorClass(String className, String customTargetConstructorClassName) { - serializations.add(createConfigurationType(className, customTargetConstructorClassName)); + public void registerWithTargetConstructorClass(ConfigurationCondition condition, String className, String customTargetConstructorClassName) { + serializations.add(createConfigurationType(condition, className, customTargetConstructorClassName)); } @Override @@ -92,9 +94,9 @@ public boolean isEmpty() { return serializations.isEmpty(); } - private static SerializationConfigurationType createConfigurationType(String className, String customTargetConstructorClassName) { + private static SerializationConfigurationType createConfigurationType(ConfigurationCondition condition, String className, String customTargetConstructorClassName) { String convertedClassName = SignatureUtil.toInternalClassName(className); String convertedCustomTargetConstructorClassName = customTargetConstructorClassName == null ? null : SignatureUtil.toInternalClassName(customTargetConstructorClassName); - return new SerializationConfigurationType(convertedClassName, convertedCustomTargetConstructorClassName); + return new SerializationConfigurationType(condition, convertedClassName, convertedCustomTargetConstructorClassName); } } 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 index 47d1ef8bdeb6..02de8118fc3b 100644 --- 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 @@ -24,39 +24,37 @@ */ 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.SerializationConfigurationParser; - import java.io.IOException; import java.util.Comparator; import java.util.Objects; -public class SerializationConfigurationType implements JsonPrintable, Comparable { +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import com.oracle.svm.configure.json.JsonPrintable; +import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.configure.SerializationConfigurationParser; + +public class SerializationConfigurationType implements JsonPrintable, Comparable { + private final ConfigurationCondition condition; private final String qualifiedJavaName; private final String qualifiedCustomTargetConstructorJavaName; - public SerializationConfigurationType(String qualifiedJavaName, String qualifiedCustomTargetConstructorJavaName) { + public SerializationConfigurationType(ConfigurationCondition condition, String qualifiedJavaName, String qualifiedCustomTargetConstructorJavaName) { assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; assert !qualifiedJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]"; assert qualifiedCustomTargetConstructorJavaName == null || qualifiedCustomTargetConstructorJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; assert qualifiedCustomTargetConstructorJavaName == null || !qualifiedCustomTargetConstructorJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]"; + Objects.requireNonNull(condition); + this.condition = condition; + Objects.requireNonNull(qualifiedJavaName); this.qualifiedJavaName = qualifiedJavaName; this.qualifiedCustomTargetConstructorJavaName = qualifiedCustomTargetConstructorJavaName; } - public String getQualifiedJavaName() { - return qualifiedJavaName; - } - - public String getQualifiedCustomTargetConstructorJavaName() { - return qualifiedCustomTargetConstructorJavaName; - } - @Override public void printJson(JsonWriter writer) throws IOException { writer.append('{').indent().newline(); + ConfigurationConditionPrintable.printConditionAttribute(condition, writer); writer.quote(SerializationConfigurationParser.NAME_KEY).append(':').quote(qualifiedJavaName); if (qualifiedCustomTargetConstructorJavaName != null) { writer.append(',').newline(); @@ -75,13 +73,14 @@ public boolean equals(Object o) { return false; } SerializationConfigurationType that = (SerializationConfigurationType) o; - return Objects.equals(qualifiedJavaName, that.qualifiedJavaName) && + return condition.equals(that.condition) && + qualifiedJavaName.equals(that.qualifiedJavaName) && Objects.equals(qualifiedCustomTargetConstructorJavaName, that.qualifiedCustomTargetConstructorJavaName); } @Override public int hashCode() { - return Objects.hash(qualifiedJavaName, qualifiedCustomTargetConstructorJavaName); + return Objects.hash(condition, qualifiedJavaName, qualifiedCustomTargetConstructorJavaName); } @Override @@ -90,6 +89,10 @@ public int compareTo(SerializationConfigurationType other) { if (compareName != 0) { return compareName; } + int compareCondition = condition.compareTo(other.condition); + if (compareCondition != 0) { + return compareCondition; + } Comparator nullsFirstCompare = Comparator.nullsFirst(Comparator.naturalOrder()); return nullsFirstCompare.compare(qualifiedCustomTargetConstructorJavaName, other.qualifiedCustomTargetConstructorJavaName); } 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 98516d267d95..529a1ab3bb83 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 @@ -32,28 +32,32 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.util.VMError; public class TypeConfiguration implements ConfigurationBase { - private final ConcurrentMap types = new ConcurrentHashMap<>(); + private final ConcurrentMap, ConfigurationType> types = new ConcurrentHashMap<>(); public TypeConfiguration() { } public TypeConfiguration(TypeConfiguration other) { - for (ConfigurationType configurationType : other.types.values()) { - types.put(configurationType.getQualifiedJavaName(), new ConfigurationType(configurationType)); + for (Map.Entry, ConfigurationType> entry : other.types.entrySet()) { + types.put(entry.getKey(), new ConfigurationType(entry.getValue())); } } public void removeAll(TypeConfiguration other) { - for (Map.Entry typeEntry : other.types.entrySet()) { + for (Map.Entry, ConfigurationType> typeEntry : other.types.entrySet()) { types.computeIfPresent(typeEntry.getKey(), (key, value) -> { if (value.equals(typeEntry.getValue())) { return null; } + assert value.getCondition().equals(typeEntry.getValue().getCondition()); assert value.getQualifiedJavaName().equals(typeEntry.getValue().getQualifiedJavaName()); value.removeAll(typeEntry.getValue()); return value.isEmpty() ? null : value; @@ -61,30 +65,31 @@ public void removeAll(TypeConfiguration other) { } } - public ConfigurationType get(String qualifiedJavaName) { - return types.get(qualifiedJavaName); + public ConfigurationType get(ConfigurationCondition condition, String qualifiedJavaName) { + return types.get(new ConditionalElement<>(condition, qualifiedJavaName)); } public void add(ConfigurationType type) { - ConfigurationType previous = types.putIfAbsent(type.getQualifiedJavaName(), type); + ConfigurationType previous = types.putIfAbsent(new ConditionalElement<>(type.getCondition(), type.getQualifiedJavaName()), type); if (previous != null && previous != type) { VMError.shouldNotReachHere("Cannot replace existing type " + previous + " with " + type); } } - public ConfigurationType getOrCreateType(String qualifiedForNameString) { - return types.computeIfAbsent(SignatureUtil.toInternalClassName(qualifiedForNameString), ConfigurationType::new); + public ConfigurationType getOrCreateType(ConfigurationCondition condition, String qualifiedForNameString) { + return types.computeIfAbsent(new ConditionalElement<>(condition, qualifiedForNameString), p -> new ConfigurationType(p.getCondition(), p.getElement())); } @Override public void printJson(JsonWriter writer) throws IOException { + List typesList = new ArrayList<>(this.types.values()); + typesList.sort(Comparator.comparing(ConfigurationType::getQualifiedJavaName).thenComparing(ConfigurationType::getCondition)); + writer.append('['); String prefix = ""; - List list = new ArrayList<>(types.values()); - list.sort(Comparator.comparing(ConfigurationType::getQualifiedJavaName)); - for (ConfigurationType value : list) { + for (ConfigurationType type : typesList) { writer.append(prefix).newline(); - value.printJson(writer); + type.printJson(writer); prefix = ","; } writer.newline().append(']'); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java index 48e37e0406c2..05c586079b4b 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java @@ -30,6 +30,7 @@ import java.util.Map; import org.graalvm.compiler.phases.common.LazyValue; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationMemberKind; import com.oracle.svm.configure.config.ConfigurationMethod; @@ -55,6 +56,7 @@ public TypeConfiguration getConfiguration() { @Override @SuppressWarnings("fallthrough") void processEntry(Map entry) { + ConfigurationCondition condition = ConfigurationCondition.objectReachable(); boolean invalidResult = Boolean.FALSE.equals(entry.get("result")); if (invalidResult) { return; @@ -70,7 +72,7 @@ void processEntry(Map entry) { String forNameString = MetaUtil.internalNameToJava(internalName, true, true); if (!advisor.shouldIgnore(lazyValue(forNameString), callerClassLazyValue)) { if (function.equals("FindClass")) { - configuration.getOrCreateType(forNameString); + configuration.getOrCreateType(condition, forNameString); } else if (!AccessAdvisor.PROXY_CLASS_NAME_PATTERN.matcher(lookupName).matches()) { // DefineClass logWarning("Unsupported JNI function DefineClass used to load class " + forNameString); } @@ -92,9 +94,9 @@ void processEntry(Map entry) { String name = (String) args.get(0); String signature = (String) args.get(1); if (!advisor.shouldIgnoreJniMethodLookup(lazyValue(clazz), lazyValue(name), lazyValue(signature), callerClassLazyValue)) { - config.getOrCreateType(declaringClassOrClazz).addMethod(name, signature, memberKind); + config.getOrCreateType(condition, declaringClassOrClazz).addMethod(name, signature, memberKind); if (!declaringClassOrClazz.equals(clazz)) { - config.getOrCreateType(clazz); + config.getOrCreateType(condition, clazz); } } break; @@ -103,9 +105,9 @@ void processEntry(Map entry) { case "GetStaticFieldID": { expectSize(args, 2); String name = (String) args.get(0); - config.getOrCreateType(declaringClassOrClazz).addField(name, memberKind, false); + config.getOrCreateType(condition, declaringClassOrClazz).addField(name, memberKind, false); if (!declaringClassOrClazz.equals(clazz)) { - config.getOrCreateType(clazz); + config.getOrCreateType(condition, clazz); } break; } @@ -114,7 +116,7 @@ void processEntry(Map entry) { String name = ConfigurationMethod.CONSTRUCTOR_NAME; String signature = "(Ljava/lang/String;)V"; if (!advisor.shouldIgnoreJniMethodLookup(lazyValue(clazz), lazyValue(name), lazyValue(signature), callerClassLazyValue)) { - config.getOrCreateType(declaringClassOrClazz).addMethod(name, signature, memberKind); + config.getOrCreateType(condition, declaringClassOrClazz).addMethod(name, signature, memberKind); assert declaringClassOrClazz.equals(clazz) : "Constructor can only be accessed via declaring class"; } break; @@ -124,7 +126,7 @@ void processEntry(Map entry) { case "FromReflectedField": { expectSize(args, 1); String name = (String) args.get(0); - config.getOrCreateType(declaringClassOrClazz).addField(name, memberKind, false); + config.getOrCreateType(condition, declaringClassOrClazz).addField(name, memberKind, false); break; } case "ToReflectedMethod": @@ -133,13 +135,13 @@ void processEntry(Map entry) { expectSize(args, 2); String name = (String) args.get(0); String signature = (String) args.get(1); - config.getOrCreateType(declaringClassOrClazz).addMethod(name, signature, memberKind); + config.getOrCreateType(condition, declaringClassOrClazz).addMethod(name, signature, memberKind); break; } case "NewObjectArray": { expectSize(args, 0); String arrayQualifiedJavaName = MetaUtil.internalNameToJava(clazz, true, true); - config.getOrCreateType(arrayQualifiedJavaName); + config.getOrCreateType(condition, arrayQualifiedJavaName); break; } } 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 d97a41bc69e1..44a33d8a1621 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 @@ -33,6 +33,7 @@ import java.util.regex.Pattern; import org.graalvm.compiler.phases.common.LazyValue; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationMemberKind; import com.oracle.svm.configure.config.ConfigurationMethod; @@ -72,6 +73,7 @@ public ResourceConfiguration getResourceConfiguration() { @SuppressWarnings("fallthrough") public void processEntry(Map entry) { boolean invalidResult = Boolean.FALSE.equals(entry.get("result")); + ConfigurationCondition condition = ConfigurationCondition.objectReachable(); if (invalidResult) { return; } @@ -100,14 +102,14 @@ public void processEntry(Map entry) { } if (!advisor.shouldIgnore(lazyValue(name), lazyValue(callerClass)) && !(isLoadClass && advisor.shouldIgnoreLoadClass(lazyValue(name), lazyValue(callerClass)))) { - configuration.getOrCreateType(name); + configuration.getOrCreateType(condition, name); } return; } else if (function.equals("methodTypeDescriptor")) { List typeNames = singleElement(args); for (String type : typeNames) { if (!advisor.shouldIgnore(lazyValue(type), lazyValue(callerClass))) { - configuration.getOrCreateType(type); + configuration.getOrCreateType(condition, type); } } } @@ -119,39 +121,39 @@ public void processEntry(Map entry) { String clazzOrDeclaringClass = entry.containsKey("declaring_class") ? (String) entry.get("declaring_class") : clazz; switch (function) { case "getDeclaredFields": { - configuration.getOrCreateType(clazz).setAllDeclaredFields(); + configuration.getOrCreateType(condition, clazz).setAllDeclaredFields(); break; } case "getFields": { - configuration.getOrCreateType(clazz).setAllPublicFields(); + configuration.getOrCreateType(condition, clazz).setAllPublicFields(); break; } case "getDeclaredMethods": { - configuration.getOrCreateType(clazz).setAllDeclaredMethods(); + configuration.getOrCreateType(condition, clazz).setAllDeclaredMethods(); break; } case "asInterfaceInstance": case "getMethods": { - configuration.getOrCreateType(clazz).setAllPublicMethods(); + configuration.getOrCreateType(condition, clazz).setAllPublicMethods(); break; } case "getDeclaredConstructors": { - configuration.getOrCreateType(clazz).setAllDeclaredConstructors(); + configuration.getOrCreateType(condition, clazz).setAllDeclaredConstructors(); break; } case "getConstructors": { - configuration.getOrCreateType(clazz).setAllPublicConstructors(); + configuration.getOrCreateType(condition, clazz).setAllPublicConstructors(); break; } case "getDeclaredClasses": { - configuration.getOrCreateType(clazz).setAllDeclaredClasses(); + configuration.getOrCreateType(condition, clazz).setAllDeclaredClasses(); break; } case "getClasses": { - configuration.getOrCreateType(clazz).setAllPublicClasses(); + configuration.getOrCreateType(condition, clazz).setAllPublicClasses(); break; } @@ -162,9 +164,9 @@ public void processEntry(Map entry) { memberKind = "findFieldHandle".equals(function) ? ConfigurationMemberKind.PRESENT : ConfigurationMemberKind.DECLARED; // fall through case "getField": { - configuration.getOrCreateType(clazzOrDeclaringClass).addField(singleElement(args), memberKind, false); + configuration.getOrCreateType(condition, clazzOrDeclaringClass).addField(singleElement(args), memberKind, false); if (!clazzOrDeclaringClass.equals(clazz)) { - configuration.getOrCreateType(clazz); + configuration.getOrCreateType(condition, clazz); } break; } @@ -180,9 +182,9 @@ public void processEntry(Map entry) { if (parameterTypes == null) { // tolerated and equivalent to no parameter types parameterTypes = Collections.emptyList(); } - configuration.getOrCreateType(clazzOrDeclaringClass).addMethod(name, SignatureUtil.toInternalSignature(parameterTypes), memberKind); + configuration.getOrCreateType(condition, clazzOrDeclaringClass).addMethod(name, SignatureUtil.toInternalSignature(parameterTypes), memberKind); if (!clazzOrDeclaringClass.equals(clazz)) { - configuration.getOrCreateType(clazz); + configuration.getOrCreateType(condition, clazz); } break; } @@ -198,7 +200,7 @@ public void processEntry(Map entry) { } String signature = SignatureUtil.toInternalSignature(parameterTypes); assert clazz.equals(clazzOrDeclaringClass) : "Constructor can only be accessed via declaring class"; - configuration.getOrCreateType(clazzOrDeclaringClass).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, memberKind); + configuration.getOrCreateType(condition, clazzOrDeclaringClass).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, memberKind); break; } @@ -227,9 +229,9 @@ public void processEntry(Map entry) { case "newInstance": { if (clazz.equals("java.lang.reflect.Array")) { // reflective array instantiation - configuration.getOrCreateType((String) args.get(0)); + configuration.getOrCreateType(condition, (String) args.get(0)); } else { - configuration.getOrCreateType(clazz).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, "()V", ConfigurationMemberKind.DECLARED); + configuration.getOrCreateType(condition, clazz).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, "()V", ConfigurationMemberKind.DECLARED); } break; } @@ -255,7 +257,7 @@ private void addFullyQualifiedDeclaredMethod(String descriptor) { String qualifiedClass = descriptor.substring(0, classend); String methodName = descriptor.substring(classend + 1, sigbegin); String signature = descriptor.substring(sigbegin); - configuration.getOrCreateType(qualifiedClass).addMethod(methodName, signature, ConfigurationMemberKind.DECLARED); + configuration.getOrCreateType(ConfigurationCondition.objectReachable(), qualifiedClass).addMethod(methodName, signature, ConfigurationMemberKind.DECLARED); } private void addDynamicProxy(List interfaceList, LazyValue callerClass) { 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 9ead38b9c0de..0c82ef50a231 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 @@ -28,6 +28,8 @@ import java.util.List; import java.util.Map; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + import com.oracle.svm.configure.config.SerializationConfiguration; public class SerializationProcessor extends AbstractProcessor { @@ -46,6 +48,7 @@ public SerializationConfiguration getSerializationConfiguration() { @Override void processEntry(Map entry) { boolean invalidResult = Boolean.FALSE.equals(entry.get("result")); + ConfigurationCondition condition = ConfigurationCondition.objectReachable(); if (invalidResult) { return; } @@ -58,7 +61,7 @@ void processEntry(Map entry) { return; } - serializationConfiguration.registerWithTargetConstructorClass((String) args.get(0), (String) args.get(1)); + serializationConfiguration.registerWithTargetConstructorClass(condition, (String) args.get(0), (String) args.get(1)); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/TypeResult.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/TypeResult.java index 01c604c57bd7..ce4271736f8e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/TypeResult.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/TypeResult.java @@ -25,6 +25,7 @@ package com.oracle.svm.core; import java.util.function.Consumer; +import java.util.function.Function; import com.oracle.svm.core.util.VMError; @@ -42,7 +43,7 @@ public static TypeResult> forClass(Class clazz) { return new TypeResult<>(clazz.getName(), clazz); } - public static TypeResult> forException(String name, Throwable exception) { + public static TypeResult forException(String name, Throwable exception) { return new TypeResult<>(name, null, exception); } @@ -74,6 +75,15 @@ public T get() { return type; } + public TypeResult map(Function f) { + if (isPresent()) { + return new TypeResult<>(name, f.apply(get())); + } else { + return TypeResult.forException(name, getException()); + } + + } + public Throwable getException() { return exception; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalElement.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalElement.java new file mode 100644 index 000000000000..db6ba6cf8ce1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalElement.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021, 2021, 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 java.util.Objects; + +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +public class ConditionalElement { + private ConfigurationCondition condition; + private T element; + + public ConditionalElement(ConfigurationCondition condition, T element) { + this.condition = condition; + this.element = element; + } + + public ConfigurationCondition getCondition() { + return condition; + } + + public T getElement() { + return element; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConditionalElement that = (ConditionalElement) o; + return Objects.equals(condition, that.condition) && + Objects.equals(element, that.element); + } + + @Override + public int hashCode() { + return Objects.hash(condition, element); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java index d7824a7f3b80..fb1945bba485 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java @@ -36,9 +36,13 @@ import java.util.Map; import java.util.Set; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + import com.oracle.svm.core.util.json.JSONParserException; public abstract class ConfigurationParser { + public static final String CONDITIONAL_KEY = "condition"; + public static final String TYPE_REACHABLE_KEY = "typeReachable"; private final Map> seenUnknownAttributesByType = new HashMap<>(); private final boolean strictConfiguration; @@ -86,18 +90,22 @@ protected void checkAttributes(Map map, String type, Collection< if (unknownAttributes.size() > 0) { String message = "Unknown attribute(s) [" + String.join(", ", unknownAttributes) + "] in " + type; - if (strictConfiguration) { - throw new JSONParserException(message); - } else { - // Checkstyle: stop - System.err.println("WARNING: " + message); - // Checkstyle: resume - } + warnOrFail(message); Set unknownAttributesForType = seenUnknownAttributesByType.computeIfAbsent(type, key -> new HashSet<>()); unknownAttributesForType.addAll(unknownAttributes); } } + protected void warnOrFail(String message) { + if (strictConfiguration) { + throw new JSONParserException(message); + } else { + // Checkstyle: stop + System.err.println("Warning: " + message); + // Checkstyle: resume + } + } + protected void checkAttributes(Map map, String type, Collection requiredAttrs) { checkAttributes(map, type, requiredAttrs, Collections.emptyList()); } @@ -136,4 +144,19 @@ protected static long asLong(Object value, String propertyName) { } throw new JSONParserException("Invalid long value '" + value + "' for element '" + propertyName + "'"); } + + protected ConfigurationCondition parseCondition(Map data) { + Object conditionData = data.get(CONDITIONAL_KEY); + if (conditionData != null) { + Map conditionObject = asMap(conditionData, "Attribute 'condition' must be an object"); + Object conditionType = conditionObject.get(TYPE_REACHABLE_KEY); + if (conditionType instanceof String) { + return ConfigurationCondition.create((String) conditionType); + } else { + warnOrFail("'" + TYPE_REACHABLE_KEY + "' should be of type string"); + } + } + return ConfigurationCondition.objectReachable(); + } + } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 8a11489b739c..f0bdc1a11f9d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -33,6 +33,8 @@ import java.util.Map; import java.util.stream.Collectors; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.util.json.JSONParser; import com.oracle.svm.core.util.json.JSONParserException; @@ -50,7 +52,7 @@ public final class ReflectionConfigurationParser extends ConfigurationParser private final boolean allowIncompleteClasspath; private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", - "allDeclaredClasses", "allPublicClasses", "methods", "fields"); + "allDeclaredClasses", "allPublicClasses", "methods", "fields", CONDITIONAL_KEY); public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate) { this(delegate, false, true); @@ -81,7 +83,13 @@ private void parseClass(Map data) { Object classObject = data.get("name"); String className = asString(classObject, "name"); - TypeResult result = delegate.resolveTypeResult(className); + TypeResult conditionResult = delegate.resolveCondition(parseCondition(data).getTypeName()); + if (!conditionResult.isPresent()) { + handleError("Could not resolve condition " + parseCondition(data).getTypeName() + " for reflection.", conditionResult.getException()); + } + ConfigurationCondition condition = conditionResult.get(); + + TypeResult result = delegate.resolveType(condition, className); if (!result.isPresent()) { handleError("Could not resolve " + className + " for reflection configuration.", result.getException()); return; @@ -179,8 +187,7 @@ private void parseMethod(Map data, T clazz) { List methodParameterTypes = null; Object parameterTypes = data.get("parameterTypes"); if (parameterTypes != null) { - methodParameterTypes = parseMethodParameters(clazz, methodName, asList(parameterTypes, - "Attribute 'parameterTypes' must be a list of type names")); + methodParameterTypes = parseMethodParameters(clazz, methodName, asList(parameterTypes, "Attribute 'parameterTypes' must be a list of type names")); if (methodParameterTypes == null) { return; } @@ -220,7 +227,7 @@ private List parseMethodParameters(T clazz, String methodName, List t List result = new ArrayList<>(); for (Object type : types) { String typeName = asString(type, "types"); - TypeResult typeResult = delegate.resolveTypeResult(typeName); + TypeResult typeResult = delegate.resolveType(ConfigurationCondition.objectReachable(), typeName); if (!typeResult.isPresent()) { handleError("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", typeResult.getException()); return null; @@ -257,10 +264,10 @@ private void handleError(String msg, Throwable cause) { if (cause != null) { message += " Reason: " + formatError(cause) + '.'; } - if (this.allowIncompleteClasspath) { - System.out.println("Warning: " + message); + if (allowIncompleteClasspath) { + System.err.println("Warning: " + message); } else { - throw new JSONParserException(message + " To allow unresolvable reflection configuration, use option -H:+AllowIncompleteClasspath"); + throw new JSONParserException(message + " To allow unresolvable reflection configuration, use option --allow-incomplete-classpath"); } // Checkstyle: resume } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 731218fc330a..9a4782d8a31f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -26,19 +26,15 @@ import java.util.List; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + import com.oracle.svm.core.TypeResult; public interface ReflectionConfigurationParserDelegate { - /** - * @deprecated use {@link #resolveTypeResult(String)} instead. - */ - @Deprecated - default T resolveType(String typeName) { - return resolveTypeResult(typeName).get(); - } + TypeResult resolveCondition(String typeName); - TypeResult resolveTypeResult(String typeName); + TypeResult resolveType(ConfigurationCondition condition, String typeName); void registerType(T type); 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 4a764b314942..626b83369576 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 @@ -27,9 +27,11 @@ import java.io.IOException; import java.io.Reader; +import java.util.Arrays; import java.util.Collections; import java.util.Map; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import com.oracle.svm.core.util.json.JSONParser; @@ -56,10 +58,12 @@ public void parseAndRegister(Reader reader) throws IOException { } private void parseSerializationDescriptorObject(Map data) { - checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Collections.singleton(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)); + checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); + ConfigurationCondition unresolvedCondition = parseCondition(data); String targetSerializationClass = asString(data.get(NAME_KEY)); Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; - serializationSupport.registerWithTargetConstructorClass(targetSerializationClass, customTargetConstructorClass); + + serializationSupport.registerWithTargetConstructorClass(unresolvedCondition, targetSerializationClass, customTargetConstructorClass); } } 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 index 26a340b04683..5905008f3142 100644 --- 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 @@ -5,6 +5,7 @@ Example: [ { + "condition":{"typeReachable":"app.DataSerializer"}, "name":"java.util.ArrayList" } ] @@ -23,6 +24,7 @@ serialization-config.json: [ { + "condition":{"typeReachable":"org.apache.spark.SparkContext"}, "name":"org.apache.spark.SparkContext$$anonfun$hadoopFile$1", "customTargetConstructorClass":"java.lang.Object" } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIRuntimeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIRuntimeAccess.java index 00103a467202..c66bf3e7b4ad 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIRuntimeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIRuntimeAccess.java @@ -32,6 +32,7 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.ReflectionRegistry; import com.oracle.svm.core.SubstrateOptions; @@ -48,11 +49,11 @@ private JNIRuntimeAccess() { } public static void register(Class... classes) { - getSupport().register(classes); + getSupport().register(ConfigurationCondition.objectReachable(), classes); } public static void register(Executable... methods) { - getSupport().register(methods); + getSupport().register(ConfigurationCondition.objectReachable(), methods); } public static void register(Field... fields) { @@ -60,7 +61,7 @@ public static void register(Field... fields) { } public static void register(boolean finalIsWritable, Field... fields) { - getSupport().register(finalIsWritable, fields); + getSupport().register(ConfigurationCondition.objectReachable(), finalIsWritable, fields); } private static JNIRuntimeAccessibilitySupport getSupport() { diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java index 86f4fb554ba8..826ae6ea7f80 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java @@ -110,6 +110,7 @@ import org.graalvm.nativeimage.VMRuntime; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; @@ -338,6 +339,7 @@ UserException error(String format, Object... args) { private static void registerJNIConfiguration(JNIRuntimeAccess.JNIRuntimeAccessibilitySupport registry, ImageClassLoader loader) { try (JNIConfigSource source = new JNIConfigSource(loader)) { Map> classes = new HashMap<>(); + ConfigurationCondition condition = ConfigurationCondition.objectReachable(); for (String line : source.lines) { source.lineNo++; String[] tokens = line.split(" "); @@ -346,8 +348,8 @@ private static void registerJNIConfiguration(JNIRuntimeAccess.JNIRuntimeAccessib Class clazz = classes.get(className); if (clazz == null) { clazz = source.findClass(className); - registry.register(clazz); - registry.register(Array.newInstance(clazz, 0).getClass()); + registry.register(condition, clazz); + registry.register(condition, Array.newInstance(clazz, 0).getClass()); classes.put(className, clazz); } @@ -356,7 +358,7 @@ private static void registerJNIConfiguration(JNIRuntimeAccess.JNIRuntimeAccessib source.check(tokens.length == 4, "Expected 4 tokens for a field"); String fieldName = tokens[2]; try { - registry.register(false, clazz.getDeclaredField(fieldName)); + registry.register(condition, false, clazz.getDeclaredField(fieldName)); } catch (NoSuchFieldException e) { throw source.error("Field %s.%s not found", clazz.getTypeName(), fieldName); } catch (NoClassDefFoundError e) { @@ -375,7 +377,7 @@ private static void registerJNIConfiguration(JNIRuntimeAccess.JNIRuntimeAccessib try { if ("".equals(methodName)) { Constructor cons = clazz.getDeclaredConstructor(parameters); - registry.register(cons); + registry.register(condition, cons); if (Throwable.class.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers())) { if (usedInTranslatedException(parameters)) { RuntimeReflection.register(clazz); @@ -383,7 +385,7 @@ private static void registerJNIConfiguration(JNIRuntimeAccess.JNIRuntimeAccessib } } } else { - registry.register(clazz.getDeclaredMethod(methodName, parameters)); + registry.register(condition, clazz.getDeclaredMethod(methodName, parameters)); } } catch (NoSuchMethodException e) { throw source.error("Method %s.%s%s not found: %e", clazz.getTypeName(), methodName, descriptor, e); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java new file mode 100644 index 000000000000..f01427ef6111 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021, 2021, 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; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +public abstract class ConditionalConfigurationRegistry { + private final Map> pendingReachabilityHandlers = new ConcurrentHashMap<>(); + + protected void registerConditionalConfiguration(ConfigurationCondition condition, Runnable runnable) { + if (ConfigurationCondition.objectReachable().equals(condition)) { + /* analysis optimization to include new types as early as possible */ + runnable.run(); + } else { + List handlers = pendingReachabilityHandlers.computeIfAbsent(condition.getTypeName(), key -> new ArrayList<>()); + handlers.add(runnable); + } + } + + public void flushConditionalConfiguration(Feature.BeforeAnalysisAccess b) { + for (Map.Entry> reachabilityEntry : pendingReachabilityHandlers.entrySet()) { + TypeResult> typeResult = ((FeatureImpl.BeforeAnalysisAccessImpl) b).getImageClassLoader().findClass(reachabilityEntry.getKey()); + b.registerReachabilityHandler(access -> reachabilityEntry.getValue().forEach(Runnable::run), typeResult.get()); + } + pendingReachabilityHandlers.clear(); + } + + public void flushConditionalConfiguration(Feature.DuringAnalysisAccess b) { + if (!pendingReachabilityHandlers.isEmpty()) { + b.requireAnalysisIteration(); + } + flushConditionalConfiguration((Feature.BeforeAnalysisAccess) b); + } + +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java index b658959a3354..f5e2a1b5d56f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java @@ -45,6 +45,7 @@ import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ReflectionConfigurationParser; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.LocatableMultiOptionValue; @@ -57,7 +58,7 @@ public final class ConfigurationParserUtils { - public static ReflectionConfigurationParser> create(ReflectionRegistry registry, ImageClassLoader imageClassLoader) { + public static ReflectionConfigurationParser>> create(ReflectionRegistry registry, ImageClassLoader imageClassLoader) { return new ReflectionConfigurationParser<>(new ReflectionRegistryAdapter(registry, imageClassLoader), NativeImageOptions.AllowIncompleteClasspath.getValue(), ConfigurationFiles.Options.StrictConfiguration.getValue()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index 8a32363ee258..281b3b7c3f2c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -28,16 +28,18 @@ import java.lang.reflect.Method; import java.util.List; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.ReflectionRegistry; import com.oracle.svm.core.TypeResult; +import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.util.ClassUtil; import jdk.vm.ci.meta.MetaUtil; -public class ReflectionRegistryAdapter implements ReflectionConfigurationParserDelegate> { +public class ReflectionRegistryAdapter implements ReflectionConfigurationParserDelegate>> { private final ReflectionRegistry registry; private final ImageClassLoader classLoader; @@ -47,78 +49,86 @@ public ReflectionRegistryAdapter(ReflectionRegistry registry, ImageClassLoader c } @Override - public void registerType(Class type) { - registry.register(type); + public void registerType(ConditionalElement> type) { + registry.register(type.getCondition(), type.getElement()); } @Override - @SuppressWarnings("deprecation") - public Class resolveType(String typeName) { - return resolveTypeResult(typeName).get(); + public TypeResult resolveCondition(String typeName) { + String canonicalizedName = canonicalizeTypeName(typeName); + TypeResult> clazz = classLoader.findClass(canonicalizedName); + return clazz.map(Class::getTypeName) + .map(ConfigurationCondition::create); } @Override - public TypeResult> resolveTypeResult(String typeName) { + public TypeResult>> resolveType(ConfigurationCondition condition, String typeName) { + String name = canonicalizeTypeName(typeName); + TypeResult> clazz = classLoader.findClass(name); + return clazz.map(c -> new ConditionalElement<>(condition, c)); + } + + private static String canonicalizeTypeName(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); + return name; } @Override - public void registerPublicClasses(Class type) { - registry.register(type.getClasses()); + public void registerPublicClasses(ConditionalElement> type) { + registry.register(type.getCondition(), type.getElement().getClasses()); } @Override - public void registerDeclaredClasses(Class type) { - registry.register(type.getDeclaredClasses()); + public void registerDeclaredClasses(ConditionalElement> type) { + registry.register(type.getCondition(), type.getElement().getDeclaredClasses()); } @Override - public void registerPublicFields(Class type) { - registry.register(false, type.getFields()); + public void registerPublicFields(ConditionalElement> type) { + registry.register(type.getCondition(), false, type.getElement().getFields()); } @Override - public void registerDeclaredFields(Class type) { - registry.register(false, type.getDeclaredFields()); + public void registerDeclaredFields(ConditionalElement> type) { + registry.register(type.getCondition(), false, type.getElement().getDeclaredFields()); } @Override - public void registerPublicMethods(Class type) { - registry.register(type.getMethods()); + public void registerPublicMethods(ConditionalElement> type) { + registry.register(type.getCondition(), type.getElement().getMethods()); } @Override - public void registerDeclaredMethods(Class type) { - registry.register(type.getDeclaredMethods()); + public void registerDeclaredMethods(ConditionalElement> type) { + registry.register(type.getCondition(), type.getElement().getDeclaredMethods()); } @Override - public void registerPublicConstructors(Class type) { - registry.register(type.getConstructors()); + public void registerPublicConstructors(ConditionalElement> type) { + registry.register(type.getCondition(), type.getElement().getConstructors()); } @Override - public void registerDeclaredConstructors(Class type) { - registry.register(type.getDeclaredConstructors()); + public void registerDeclaredConstructors(ConditionalElement> type) { + registry.register(type.getCondition(), type.getElement().getDeclaredConstructors()); } @Override - public void registerField(Class type, String fieldName, boolean allowWrite) throws NoSuchFieldException { - registry.register(allowWrite, type.getDeclaredField(fieldName)); + public void registerField(ConditionalElement> type, String fieldName, boolean allowWrite) throws NoSuchFieldException { + registry.register(type.getCondition(), allowWrite, type.getElement().getDeclaredField(fieldName)); } @Override - public boolean registerAllMethodsWithName(Class type, String methodName) { + public boolean registerAllMethodsWithName(ConditionalElement> type, String methodName) { boolean found = false; - Executable[] methods = type.getDeclaredMethods(); + Executable[] methods = type.getElement().getDeclaredMethods(); for (Executable method : methods) { if (method.getName().equals(methodName)) { - registry.register(method); + registry.register(type.getCondition(), method); found = true; } } @@ -126,20 +136,20 @@ public boolean registerAllMethodsWithName(Class type, String methodName) { } @Override - public boolean registerAllConstructors(Class clazz) { - Executable[] methods = clazz.getDeclaredConstructors(); + public boolean registerAllConstructors(ConditionalElement> type) { + Executable[] methods = type.getElement().getDeclaredConstructors(); for (Executable method : methods) { - registry.register(method); + registry.register(type.getCondition(), method); } return methods.length > 0; } @Override - public void registerMethod(Class type, String methodName, List> methodParameterTypes) throws NoSuchMethodException { - Class[] parameterTypesArray = methodParameterTypes.toArray(new Class[0]); + public void registerMethod(ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException { + Class[] parameterTypesArray = getParameterTypes(methodParameterTypes); Method method; try { - method = type.getDeclaredMethod(methodName, parameterTypesArray); + method = type.getElement().getDeclaredMethod(methodName, parameterTypesArray); } catch (NoClassDefFoundError e) { /* * getDeclaredMethod() builds a set of all the declared methods, which can fail when a @@ -150,27 +160,33 @@ public void registerMethod(Class type, String methodName, List> meth * precisely because the application used getMethod() instead of getDeclaredMethod(). */ try { - method = type.getMethod(methodName, parameterTypesArray); + method = type.getElement().getMethod(methodName, parameterTypesArray); } catch (Throwable ignored) { throw e; } } - registry.register(method); + registry.register(type.getCondition(), method); } @Override - public void registerConstructor(Class clazz, List> methodParameterTypes) throws NoSuchMethodException { - Class[] parameterTypesArray = methodParameterTypes.toArray(new Class[0]); - registry.register(clazz.getDeclaredConstructor(parameterTypesArray)); + public void registerConstructor(ConditionalElement> type, List>> methodParameterTypes) throws NoSuchMethodException { + Class[] parameterTypesArray = getParameterTypes(methodParameterTypes); + registry.register(type.getCondition(), type.getElement().getDeclaredConstructor(parameterTypesArray)); + } + + private static Class[] getParameterTypes(List>> methodParameterTypes) { + return methodParameterTypes.stream() + .map(ConditionalElement::getElement) + .toArray(Class[]::new); } @Override - public String getTypeName(Class type) { - return type.getTypeName(); + public String getTypeName(ConditionalElement> type) { + return type.getElement().getTypeName(); } @Override - public String getSimpleName(Class type) { - return ClassUtil.getUnqualifiedName(type); + public String getSimpleName(ConditionalElement> type) { + return ClassUtil.getUnqualifiedName(type.getElement()); } } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java index 0e286e4c63b3..ccf1b7fb023d 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java @@ -36,18 +36,20 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; -import com.oracle.graal.pointsto.BigBang; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.options.Option; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.ReflectionRegistry; +import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; +import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ReflectionConfigurationParser; import com.oracle.svm.core.jni.JNIRuntimeAccess; import com.oracle.svm.core.option.HostedOptionKey; @@ -57,6 +59,7 @@ import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.ConditionalConfigurationRegistry; import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.code.CEntryPointData; import com.oracle.svm.hosted.config.ConfigurationParserUtils; @@ -121,32 +124,37 @@ public void afterRegistration(AfterRegistrationAccess arg) { JNIRuntimeAccessibilitySupportImpl registry = new JNIRuntimeAccessibilitySupportImpl(); ImageSingletons.add(JNIRuntimeAccess.JNIRuntimeAccessibilitySupport.class, registry); - ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(registry, access.getImageClassLoader()); + ReflectionConfigurationParser>> parser = ConfigurationParserUtils.create(registry, access.getImageClassLoader()); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "JNI", ConfigurationFiles.Options.JNIConfigurationFiles, ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()); } - private class JNIRuntimeAccessibilitySupportImpl implements JNIRuntimeAccess.JNIRuntimeAccessibilitySupport, ReflectionRegistry { + private class JNIRuntimeAccessibilitySupportImpl extends ConditionalConfigurationRegistry implements JNIRuntimeAccess.JNIRuntimeAccessibilitySupport, ReflectionRegistry { @Override - public void register(Class... classes) { + public void register(ConfigurationCondition condition, Class... classes) { abortIfSealed(); - newClasses.addAll(Arrays.asList(classes)); + registerConditionalConfiguration(condition, () -> newClasses.addAll(Arrays.asList(classes))); } @Override - public void register(Executable... methods) { + public void register(ConfigurationCondition condition, Executable... methods) { abortIfSealed(); - newMethods.addAll(Arrays.asList(methods)); + registerConditionalConfiguration(condition, () -> newMethods.addAll(Arrays.asList(methods))); } @Override - public void register(boolean finalIsWritable, Field... fields) { + public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { abortIfSealed(); + registerConditionalConfiguration(condition, () -> registerFields(finalIsWritable, fields)); + } + + private void registerFields(boolean finalIsWritable, Field[] fields) { for (Field field : fields) { boolean writable = finalIsWritable || !Modifier.isFinal(field.getModifiers()); newFields.put(field, writable); } } + } @Override @@ -170,6 +178,13 @@ public void beforeAnalysis(BeforeAnalysisAccess arg) { varargsNonvirtualCallTrampolineMethod = createJavaCallTrampoline(access, CallVariant.VARARGS, true); arrayNonvirtualCallTrampolineMethod = createJavaCallTrampoline(access, CallVariant.ARRAY, true); valistNonvirtualCallTrampolineMethod = createJavaCallTrampoline(access, CallVariant.VA_LIST, true); + + /* duplicated to reduce the number of analysis iterations */ + getConditionalConfigurationRegistry().flushConditionalConfiguration(access); + } + + private static ConditionalConfigurationRegistry getConditionalConfigurationRegistry() { + return (ConditionalConfigurationRegistry) ImageSingletons.lookup(JNIRuntimeAccess.JNIRuntimeAccessibilitySupport.class); } private static JNICallTrampolineMethod createJavaCallTrampoline(BeforeAnalysisAccessImpl access, CallVariant variant, boolean nonVirtual) { @@ -221,6 +236,7 @@ private boolean wereElementsAdded() { @Override public void duringAnalysis(DuringAnalysisAccess a) { + getConditionalConfigurationRegistry().flushConditionalConfiguration(a); DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; if (!wereElementsAdded()) { return; diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java index bd67c5dbe672..0c6725d824f6 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java @@ -42,6 +42,7 @@ import java.util.stream.Collectors; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.graal.pointsto.meta.AnalysisType; @@ -52,10 +53,11 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.FeatureAccessImpl; +import com.oracle.svm.hosted.ConditionalConfigurationRegistry; import com.oracle.svm.hosted.substitute.SubstitutionReflectivityFilter; import com.oracle.svm.util.ReflectionUtil; -public class ReflectionDataBuilder implements RuntimeReflectionSupport { +public class ReflectionDataBuilder extends ConditionalConfigurationRegistry implements RuntimeReflectionSupport { public static final Field[] EMPTY_FIELDS = new Field[0]; public static final Method[] EMPTY_METHODS = new Method[0]; @@ -70,7 +72,6 @@ public class ReflectionDataBuilder implements RuntimeReflectionSupport { private final Set reflectionMethods = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set reflectionFields = Collections.newSetFromMap(new ConcurrentHashMap<>()); - /* Keep track of classes already processed for reflection. */ private final Set> processedClasses = new HashSet<>(); private final ReflectionDataAccessors accessors; @@ -108,8 +109,12 @@ private static DynamicHub.ReflectionData getArrayReflectionData() { } @Override - public void register(Class... classes) { + public void register(ConfigurationCondition condition, Class... classes) { checkNotSealed(); + registerConditionalConfiguration(condition, () -> registerClasses(classes)); + } + + private void registerClasses(Class[] classes) { for (Class clazz : classes) { if (reflectionClasses.add(clazz)) { modifiedClasses.add(clazz); @@ -118,8 +123,12 @@ public void register(Class... classes) { } @Override - public void register(Executable... methods) { + public void register(ConfigurationCondition condition, Executable... methods) { checkNotSealed(); + registerConditionalConfiguration(condition, () -> registerMethods(methods)); + } + + private void registerMethods(Executable[] methods) { for (Executable method : methods) { if (reflectionMethods.add(method)) { modifiedClasses.add(method.getDeclaringClass()); @@ -128,8 +137,12 @@ public void register(Executable... methods) { } @Override - public void register(boolean finalIsWritable, Field... fields) { + public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { checkNotSealed(); + registerConditionalConfiguration(condition, () -> registerFields(fields)); + } + + private void registerFields(Field[] fields) { // Unsafe and write accesses are always enabled for fields because accessors use Unsafe. for (Field field : fields) { if (reflectionFields.add(field)) { diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java index 5bb30bc1306a..92a032675d98 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.reflect.hosted; -import com.oracle.svm.core.configure.ConfigurationFile; import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins; import org.graalvm.compiler.phases.util.Providers; @@ -34,7 +33,9 @@ import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; +import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ReflectionConfigurationParser; import com.oracle.svm.core.graal.GraalFeature; import com.oracle.svm.hosted.FallbackFeature; @@ -82,7 +83,7 @@ public void duringSetup(DuringSetupAccess a) { ImageSingletons.add(ReflectionSubstitutionType.Factory.class, new ReflectionSubstitutionType.Factory()); } - ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(reflectionData, access.getImageClassLoader()); + ReflectionConfigurationParser>> parser = ConfigurationParserUtils.create(reflectionData, access.getImageClassLoader()); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "reflection", ConfigurationFiles.Options.ReflectionConfigurationFiles, ConfigurationFiles.Options.ReflectionConfigurationResources, ConfigurationFile.REFLECTION.getFileName()); @@ -91,8 +92,15 @@ public void duringSetup(DuringSetupAccess a) { annotationSubstitutions = ((Inflation) access.getBigBang()).getAnnotationSubstitutionProcessor(); } + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + /* duplicated to reduce the number of analysis iterations */ + reflectionData.flushConditionalConfiguration(access); + } + @Override public void duringAnalysis(DuringAnalysisAccess access) { + reflectionData.flushConditionalConfiguration(access); reflectionData.duringAnalysis(access); } 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 5c4af101599d..c9b48172981f 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 @@ -39,12 +39,11 @@ import java.util.HashMap; 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.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import com.oracle.svm.core.TypeResult; @@ -61,6 +60,7 @@ import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.NativeImageOptions; +import com.oracle.svm.hosted.ConditionalConfigurationRegistry; import com.oracle.svm.hosted.config.ConfigurationParserUtils; import com.oracle.svm.reflect.hosted.ReflectionFeature; import com.oracle.svm.reflect.serialize.SerializationRegistry; @@ -99,9 +99,14 @@ public void duringSetup(DuringSetupAccess a) { ConfigurationFile.SERIALIZATION.getFileName()); } + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + serializationBuilder.flushConditionalConfiguration(access); + } + @Override public void duringAnalysis(DuringAnalysisAccess access) { - serializationBuilder.duringAnalysis(access); + serializationBuilder.flushConditionalConfiguration(access); } @Override @@ -168,19 +173,19 @@ final class SerializationDenyRegistry implements RuntimeSerializationSupport { } @Override - public void register(Class... classes) { + public void register(ConfigurationCondition condition, Class... classes) { for (Class clazz : classes) { - registerWithTargetConstructorClass(clazz, null); + registerWithTargetConstructorClass(condition, clazz, null); } } @Override - public void registerWithTargetConstructorClass(String className, String customTargetConstructorClassName) { - registerWithTargetConstructorClass(typeResolver.resolveType(className), null); + public void registerWithTargetConstructorClass(ConfigurationCondition condition, String className, String customTargetConstructorClassName) { + registerWithTargetConstructorClass(condition, typeResolver.resolveType(className), null); } @Override - public void registerWithTargetConstructorClass(Class clazz, Class customTargetConstructorClazz) { + public void registerWithTargetConstructorClass(ConfigurationCondition condition, Class clazz, Class customTargetConstructorClazz) { if (clazz != null) { deniedClasses.put(clazz, true); } @@ -196,17 +201,7 @@ public boolean isAllowed(Class clazz) { } } -final class SerializationBuilder implements RuntimeSerializationSupport { - - private static final class ClassEntry { - private final Class clazz; - private final Class customTargetConstructorClazz; - - private ClassEntry(Class clazz, Class customTargetConstructorClazz) { - this.clazz = clazz; - this.customTargetConstructorClazz = customTargetConstructorClazz; - } - } +final class SerializationBuilder extends ConditionalConfigurationRegistry implements RuntimeSerializationSupport { private final Object reflectionFactory; private final Method newConstructorForSerializationMethod1; @@ -218,7 +213,6 @@ private ClassEntry(Class clazz, Class customTargetConstructorClazz) { private final SerializationSupport serializationSupport; private final SerializationDenyRegistry denyRegistry; private final SerializationTypeResolver typeResolver; - private final Set newClasses; private boolean sealed; @@ -237,7 +231,6 @@ private ClassEntry(Class clazz, Class customTargetConstructorClazz) { stubConstructor = newConstructorForSerialization(SerializationSupport.StubForAbstractClass.class, null); this.denyRegistry = serializationDenyRegistry; this.typeResolver = typeResolver; - newClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); serializationSupport = new SerializationSupport(stubConstructor); ImageSingletons.add(SerializationRegistry.class, serializationSupport); @@ -248,32 +241,46 @@ private void abortIfSealed() { } @Override - public void register(Class... classes) { + public void register(ConfigurationCondition condition, Class... classes) { for (Class clazz : classes) { - registerWithTargetConstructorClass(clazz, null); + registerWithTargetConstructorClass(condition, clazz, null); } } @Override - public void registerWithTargetConstructorClass(String targetClassName, String customTargetConstructorClassName) { + public void registerWithTargetConstructorClass(ConfigurationCondition condition, String targetClassName, String customTargetConstructorClassName) { abortIfSealed(); + + Class conditionClass = typeResolver.resolveType(condition.getTypeName()); + String msg = "Cannot find condition class %s."; + if (conditionClass == null) { + if (NativeImageOptions.AllowIncompleteClasspath.getValue()) { + // Checkstyle: stop + System.err.println("Warning: " + String.format(msg, condition.getTypeName())); + // Checkstyle: resume + } else { + throw UserError.abort(msg, condition.getTypeName()); + } + } + Class serializationTargetClass = typeResolver.resolveType(targetClassName); - 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." + + UserError.guarantee(serializationTargetClass != null, "Cannot find serialization target class %s. The missing of this class can't be ignored even if --allow-incomplete-classpath is set." + " Please make sure it is in the classpath", targetClassName); + if (customTargetConstructorClassName != null) { Class customTargetConstructorClass = typeResolver.resolveType(customTargetConstructorClassName); UserError.guarantee(customTargetConstructorClass != null, - "Cannot find targetConstructorClass %s. The missing of this class can't be ignored even if -H:+AllowIncompleteClasspath is set." + + "Cannot find targetConstructorClass %s. The missing of this class can't be ignored even if --allow-incomplete-classpath is set." + " Please make sure it is in the classpath", customTargetConstructorClass); - registerWithTargetConstructorClass(serializationTargetClass, customTargetConstructorClass); + registerWithTargetConstructorClass(condition, serializationTargetClass, customTargetConstructorClass); } else { - registerWithTargetConstructorClass(serializationTargetClass, null); + registerWithTargetConstructorClass(condition, serializationTargetClass, null); } } @Override - public void registerWithTargetConstructorClass(Class serializationTargetClass, Class customTargetConstructorClass) { + public void registerWithTargetConstructorClass(ConfigurationCondition condition, Class serializationTargetClass, Class customTargetConstructorClass) { abortIfSealed(); if (!Serializable.class.isAssignableFrom(serializationTargetClass)) { println("Warning: Could not register " + serializationTargetClass.getName() + " for serialization as it does not implement Serializable."); @@ -283,29 +290,15 @@ public void registerWithTargetConstructorClass(Class serializationTargetClass "The given targetConstructorClass %s is not a subclass of the serialization target class %s.", customTargetConstructorClass, serializationTargetClass); } - ClassEntry entry = new ClassEntry(serializationTargetClass, customTargetConstructorClass); - newClasses.add(entry); + registerConditionalConfiguration(condition, () -> { + Class targetConstructor = addConstructorAccessor(serializationTargetClass, customTargetConstructorClass); + addReflections(serializationTargetClass, targetConstructor); + }); } } - public void duringAnalysis(Feature.DuringAnalysisAccess a) { - if (newClasses.isEmpty()) { - return; - } - for (ClassEntry entry : newClasses) { - Class targetConstructor = addConstructorAccessor(entry.clazz, entry.customTargetConstructorClazz); - addReflections(entry.clazz, targetConstructor); - } - newClasses.clear(); - - a.requireAnalysisIteration(); - } - public void afterAnalysis() { sealed = true; - if (!newClasses.isEmpty()) { - abortIfSealed(); - } } private static void addReflections(Class serializationTargetClass, Class targetConstructorClass) {