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) {