From 690c759dfab283e94741560d0b94f42c89e1ef14 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Thu, 30 Nov 2023 11:54:15 +0100 Subject: [PATCH] Introduce more flexible type descriptors for JSON configuration --- .../native-image/ReachabilityMetadata.md | 4 +- .../config-condition-schema-v1.0.0.json | 16 +++ .../assets/config-type-schema-v1.0.0.json | 6 + .../assets/jni-config-schema-v1.1.0.json | 104 ++++++++++++++++++ .../assets/reflect-config-schema-v1.1.0.json | 104 ++++++++++++++++++ .../test/config/OmitPreviousConfigTests.java | 6 +- .../configure/config/ConfigurationMethod.java | 4 +- .../configure/config/ConfigurationType.java | 44 +++++--- .../config/ConfigurationTypeDescriptor.java | 91 +++++++++++++++ .../config/ParserConfigurationAdapter.java | 8 +- .../configure/config/TypeConfiguration.java | 25 +++-- .../ConditionalConfigurationPredicate.java | 21 +++- .../core/configure/ConfigurationFiles.java | 6 +- .../core/configure/ConfigurationParser.java | 19 +++- .../ReflectionConfigurationParser.java | 37 +++++-- ...ReflectionConfigurationParserDelegate.java | 4 +- .../ReflectionConfigurationFilesHelp.txt | 2 +- .../config/ReflectionRegistryAdapter.java | 6 +- .../svm/hosted/config/RegistryAdapter.java | 4 +- 19 files changed, 451 insertions(+), 60 deletions(-) create mode 100644 docs/reference-manual/native-image/assets/config-condition-schema-v1.0.0.json create mode 100644 docs/reference-manual/native-image/assets/config-type-schema-v1.0.0.json create mode 100644 docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json create mode 100644 docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationTypeDescriptor.java diff --git a/docs/reference-manual/native-image/ReachabilityMetadata.md b/docs/reference-manual/native-image/ReachabilityMetadata.md index 31ac83047959c..4b19a3ecadb66 100644 --- a/docs/reference-manual/native-image/ReachabilityMetadata.md +++ b/docs/reference-manual/native-image/ReachabilityMetadata.md @@ -147,7 +147,7 @@ Integer.class.getMethod("parseInt", params2); ### Specifying Reflection Metadata in JSON Reflection metadata should be specified in a _reflect-config.json_ file and conform to the JSON schema defined in -[reflect-config-schema-v1.0.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.0.0.json). +[reflect-config-schema-v1.1.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json). The schema also includes further details and explanations how this configuration works. Here is the example of the reflect-config.json: ```json [ @@ -209,7 +209,7 @@ It is not possible to specify JNI metadata in code. ### JNI Metadata in JSON JNI metadata should be specified in a _jni-config.json_ file and conform to the JSON schema defined in -[jni-config-schema-v1.0.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/jni-config-schema-v1.0.0.json). +[jni-config-schema-v1.1.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json). The schema also includes further details and explanations how this configuration works. The example of jni-config.json is the same as the example of reflect-config.json described above. diff --git a/docs/reference-manual/native-image/assets/config-condition-schema-v1.0.0.json b/docs/reference-manual/native-image/assets/config-condition-schema-v1.0.0.json new file mode 100644 index 0000000000000..9cd3109221f62 --- /dev/null +++ b/docs/reference-manual/native-image/assets/config-condition-schema-v1.0.0.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/config-condition-schema-v1.0.0.json", + "title": "JSON schema for the conditions used in GraalVM Native Image configuration files", + "properties": { + "typeReachable": { + "type": "string", + "title": "Fully qualified name of the class that must be reachable in order to register the type for reflection" + } + }, + "required": [ + "typeReachable" + ], + "additionalProperties": false, + "type": "object" +} \ No newline at end of file diff --git a/docs/reference-manual/native-image/assets/config-type-schema-v1.0.0.json b/docs/reference-manual/native-image/assets/config-type-schema-v1.0.0.json new file mode 100644 index 0000000000000..16a50dc888a42 --- /dev/null +++ b/docs/reference-manual/native-image/assets/config-type-schema-v1.0.0.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/config-type-schema-v1.0.0.json", + "type": "string", + "title": "JSON schema for the type descriptors GraalVM Native Image configuration files use" +} \ No newline at end of file diff --git a/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json b/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json new file mode 100644 index 0000000000000..7440390bb6980 --- /dev/null +++ b/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json @@ -0,0 +1,104 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json", + "default": [], + "items": { + "properties": { + "condition": { + "$ref": "config-condition-schema-v1.0.0.json", + "title": "Condition under which the class should be registered for access through JNI" + }, + "type": { + "$ref": "config-type-schema-v1.0.0.json", + "title": "Type descriptor of the class that should be registered for access through JNI" + }, + "methods": { + "default": [], + "items": { + "properties": { + "name": { + "type": "string", + "title": "Method name that should be registered for this class" + }, + "parameterTypes": { + "default": [], + "items": { + "type": "string", + "title": "List of the method's parameter types" + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "additionalProperties": false, + "type": "object", + "title": "List of methods from this class that are registered for access through JNI" + }, + "type": "array", + "title": "List of methods that should be registered for the class declared in " + }, + "fields": { + "default": [], + "items": { + "properties": { + "name": { + "type": "string", + "title": "Name of the field that should be registered for access through JNI" + } + }, + "required": [ + "name" + ], + "additionalProperties": false, + "type": "object" + }, + "type": "array", + "title": "List of fields that should be registered for the class declared in " + }, + "allDeclaredMethods": { + "default": false, + "type": "boolean", + "title": "Register methods which would be returned by the java.lang.Class#getDeclaredMethods call" + }, + "allDeclaredFields": { + "default": false, + "type": "boolean", + "title": "Register fields which would be returned by the java.lang.Class#getDeclaredFields call" + }, + "allDeclaredConstructors": { + "default": false, + "type": "boolean", + "title": "Register constructors which would be returned by the java.lang.Class#getDeclaredConstructors call" + }, + "allPublicMethods": { + "default": false, + "type": "boolean", + "title": "Register all public methods which would be returned by the java.lang.Class#getMethods call" + }, + "allPublicFields": { + "default": false, + "type": "boolean", + "title": "Register all public fields which would be returned by the java.lang.Class#getFields call" + }, + "allPublicConstructors": { + "default": false, + "type": "boolean", + "title": "Register all public constructors which would be returned by the java.lang.Class#getConstructors call" + }, + "unsafeAllocated": { + "default": false, + "type": "boolean", + "title": "Allow objects of this class to be instantiated with a call to jdk.internal.misc.Unsafe#allocateInstance" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "type": "object" + }, + "type": "array", + "title": "JSON schema for the JNI configuration that GraalVM Native Image uses" +} \ No newline at end of file diff --git a/docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json b/docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json new file mode 100644 index 0000000000000..8065104f622d2 --- /dev/null +++ b/docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json @@ -0,0 +1,104 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json", + "default": [], + "items": { + "properties": { + "condition": { + "$ref": "config-condition-schema-v1.0.0.json", + "title": "Condition under which the class should be registered for reflection" + }, + "type": { + "$ref": "config-type-schema-v1.0.0.json", + "title": "Type descriptor of the class that should be registered for reflection" + }, + "methods": { + "default": [], + "items": { + "properties": { + "name": { + "type": "string", + "title": "Method name that should be registered for this class" + }, + "parameterTypes": { + "default": [], + "items": { + "type": "string", + "title": "List of the method's parameter types" + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "additionalProperties": false, + "type": "object", + "title": "List of methods from this class that are registered for reflection" + }, + "type": "array", + "title": "List of methods that should be registered for the type declared in " + }, + "fields": { + "default": [], + "items": { + "properties": { + "name": { + "type": "string", + "title": "Name of the field that should be registered for reflection" + } + }, + "required": [ + "name" + ], + "additionalProperties": false, + "type": "object" + }, + "type": "array", + "title": "List of class fields that can be looked up, read, or modified for the type declared in " + }, + "allDeclaredMethods": { + "default": false, + "type": "boolean", + "title": "Register methods which would be returned by the java.lang.Class#getDeclaredMethods call" + }, + "allDeclaredFields": { + "default": false, + "type": "boolean", + "title": "Register fields which would be returned by the java.lang.Class#getDeclaredFields call" + }, + "allDeclaredConstructors": { + "default": false, + "type": "boolean", + "title": "Register constructors which would be returned by the java.lang.Class#getDeclaredConstructors call" + }, + "allPublicMethods": { + "default": false, + "type": "boolean", + "title": "Register all public methods which would be returned by the java.lang.Class#getMethods call" + }, + "allPublicFields": { + "default": false, + "type": "boolean", + "title": "Register all public fields which would be returned by the java.lang.Class#getFields call" + }, + "allPublicConstructors": { + "default": false, + "type": "boolean", + "title": "Register all public constructors which would be returned by the java.lang.Class#getConstructors call" + }, + "unsafeAllocated": { + "default": false, + "type": "boolean", + "title": "Allow objects of this class to be instantiated with a call to jdk.internal.misc.Unsafe#allocateInstance" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "type": "object" + }, + "type": "array", + "title": "JSON schema for the reflection configuration that GraalVM Native Image uses" +} \ No newline at end of file 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 361845eeaf990..0d5b6bb266b58 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, 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 @@ -260,11 +260,11 @@ Map getMethodsMap(Configura } void populateConfig() { - ConfigurationType oldType = new ConfigurationType(UnresolvedConfigurationCondition.alwaysTrue(), getTypeName()); + ConfigurationType oldType = new ConfigurationType(UnresolvedConfigurationCondition.alwaysTrue(), getTypeName(), true); setFlags(oldType); previousConfig.add(oldType); - ConfigurationType newType = new ConfigurationType(UnresolvedConfigurationCondition.alwaysTrue(), getTypeName()); + ConfigurationType newType = new ConfigurationType(UnresolvedConfigurationCondition.alwaysTrue(), getTypeName(), true); for (Map.Entry methodEntry : methodsThatMustExist.entrySet()) { newType.addMethod(methodEntry.getKey().getName(), methodEntry.getKey().getInternalSignature(), methodEntry.getValue()); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMethod.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMethod.java index ea2b7ec525d22..19460cbf111d7 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMethod.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -43,7 +43,7 @@ public static boolean isConstructorName(String name) { public static String toInternalParamsSignature(List types) { StringBuilder sb = new StringBuilder("("); for (ConfigurationType type : types) { - sb.append(MetaUtil.toInternalName(type.getQualifiedJavaName())); + sb.append(MetaUtil.toInternalName(((NamedConfigurationTypeDescriptor) type.getTypeDescriptor()).name())); } sb.append(')'); // we are missing the return type, so this is only a partial signature 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 bfce9e7d7f64a..a457f620abcfc 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -60,8 +60,7 @@ static ConfigurationType copyAndSubtract(ConfigurationType type, ConfigurationTy return copy; } - assert type.getCondition().equals(subtractType.getCondition()); - assert type.getQualifiedJavaName().equals(subtractType.getQualifiedJavaName()); + assert type.sameTypeAndCondition(subtractType); copy.removeAll(subtractType); return copy.isEmpty() ? null : copy; @@ -73,8 +72,7 @@ static ConfigurationType copyAndIntersect(ConfigurationType type, ConfigurationT return copy; } - assert type.getCondition().equals(toIntersect.getCondition()); - assert type.getQualifiedJavaName().equals(toIntersect.getQualifiedJavaName()); + assert type.sameTypeAndCondition(toIntersect); copy.intersectWith(toIntersect); return copy; } @@ -86,7 +84,7 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType } private final UnresolvedConfigurationCondition condition; - private final String qualifiedJavaName; + private final ConfigurationTypeDescriptor typeDescriptor; private Map fields; private Map methods; @@ -105,17 +103,23 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType private ConfigurationMemberAccessibility allDeclaredConstructorsAccess = ConfigurationMemberAccessibility.NONE; private ConfigurationMemberAccessibility allPublicConstructorsAccess = ConfigurationMemberAccessibility.NONE; - public ConfigurationType(UnresolvedConfigurationCondition 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[]"; + public ConfigurationType(UnresolvedConfigurationCondition condition, String qualifiedJavaName, boolean includeAllElements) { + this(condition, new NamedConfigurationTypeDescriptor(qualifiedJavaName), includeAllElements); + } + + public ConfigurationType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean includeAllElements) { this.condition = condition; - this.qualifiedJavaName = qualifiedJavaName; + this.typeDescriptor = typeDescriptor; + allDeclaredClasses = allPublicClasses = allDeclaredFields = allPublicFields = allRecordComponents = allPermittedSubclasses = allNestMembers = allSigners = includeAllElements; + allDeclaredMethodsAccess = allPublicMethodsAccess = allDeclaredConstructorsAccess = allPublicConstructorsAccess = includeAllElements + ? ConfigurationMemberAccessibility.QUERIED + : ConfigurationMemberAccessibility.NONE; } ConfigurationType(ConfigurationType other, UnresolvedConfigurationCondition condition) { // Our object is not yet published, so it is sufficient to take only the other object's lock synchronized (other) { - qualifiedJavaName = other.qualifiedJavaName; + typeDescriptor = other.typeDescriptor; this.condition = condition; mergeFrom(other); } @@ -126,13 +130,16 @@ public ConfigurationType(UnresolvedConfigurationCondition condition, String qual } void mergeFrom(ConfigurationType other) { - assert condition.equals(other.condition); - assert qualifiedJavaName.equals(other.qualifiedJavaName); + assert sameTypeAndCondition(other); mergeFlagsFrom(other); mergeFieldsFrom(other); mergeMethodsFrom(other); } + private boolean sameTypeAndCondition(ConfigurationType otherType) { + return condition.equals(otherType.condition) && typeDescriptor.equals(otherType.typeDescriptor); + } + private void mergeFlagsFrom(ConfigurationType other) { setFlagsFromOther(other, (our, their) -> our || their, ConfigurationMemberAccessibility::combine); } @@ -232,8 +239,7 @@ private void intersectMethods(ConfigurationType other) { } private void removeAll(ConfigurationType other) { - assert condition.equals(other.condition); - assert qualifiedJavaName.equals(other.qualifiedJavaName); + assert sameTypeAndCondition(other); removeFlags(other); removeFields(other); removeMethods(other); @@ -293,8 +299,8 @@ private boolean allFlagsFalse() { allDeclaredConstructorsAccess != ConfigurationMemberAccessibility.NONE || allPublicConstructorsAccess != ConfigurationMemberAccessibility.NONE); } - public String getQualifiedJavaName() { - return qualifiedJavaName; + public ConfigurationTypeDescriptor getTypeDescriptor() { + return typeDescriptor; } public synchronized void addField(String name, ConfigurationMemberDeclaration declaration, boolean finalButWritable) { @@ -439,7 +445,9 @@ public synchronized void setAllPublicConstructors(ConfigurationMemberAccessibili public synchronized void printJson(JsonWriter writer) throws IOException { writer.append('{').indent().newline(); ConfigurationConditionPrintable.printConditionAttribute(condition, writer); - writer.quote("name").append(':').quote(qualifiedJavaName); + /* GR-50385: Replace with "type" (and delete unnecessary entries below) */ + writer.quote("name").append(":"); + typeDescriptor.printJson(writer); optionallyPrintJsonBoolean(writer, allDeclaredFields, "allDeclaredFields"); optionallyPrintJsonBoolean(writer, allPublicFields, "allPublicFields"); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationTypeDescriptor.java new file mode 100644 index 0000000000000..e2aa423a8c2f0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationTypeDescriptor.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.configure.config; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; + +import com.oracle.svm.core.util.json.JsonPrintable; +import com.oracle.svm.core.util.json.JsonWriter; + +public interface ConfigurationTypeDescriptor extends Comparable, JsonPrintable { + enum Kind { + NAMED; + } + + Kind getDescriptorType(); + + @Override + String toString(); + + /** + * Returns the qualified names of all named Java types (excluding proxy classes, lambda classes + * and similar anonymous classes) required for this type descriptor to properly describe its + * type. This is used to filter configurations based on a String-based class filter. + */ + Collection getAllQualifiedJavaNames(); + + static void checkQualifiedJavaName(String javaName) { + assert javaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; + assert !javaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]"; + } +} + +record NamedConfigurationTypeDescriptor(String name) implements ConfigurationTypeDescriptor { + + public NamedConfigurationTypeDescriptor { + ConfigurationTypeDescriptor.checkQualifiedJavaName(name); + } + + @Override + public Kind getDescriptorType() { + return Kind.NAMED; + } + + @Override + public String toString() { + return name; + } + + @Override + public Collection getAllQualifiedJavaNames() { + return Collections.singleton(name); + } + + @Override + public int compareTo(ConfigurationTypeDescriptor other) { + if (other instanceof NamedConfigurationTypeDescriptor namedOther) { + return name.compareTo(namedOther.name); + } else { + return getDescriptorType().compareTo(other.getDescriptorType()); + } + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.quote(name); + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java index fc050fbe872bd..a86b816632011 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -43,9 +43,9 @@ public ParserConfigurationAdapter(TypeConfiguration configuration) { } @Override - public TypeResult resolveType(UnresolvedConfigurationCondition condition, String typeName, boolean allowPrimitives) { + public TypeResult resolveType(UnresolvedConfigurationCondition condition, String typeName, boolean allowPrimitives, boolean includeAllElements) { ConfigurationType type = configuration.get(condition, typeName); - ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeName); + ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeName, includeAllElements); return TypeResult.forType(typeName, result); } @@ -170,7 +170,7 @@ public void registerDeclaredConstructors(UnresolvedConfigurationCondition condit @Override public String getTypeName(ConfigurationType type) { - return type.getQualifiedJavaName(); + return type.getTypeDescriptor().toString(); } @Override 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 b9aa5d048f1bc..51afbd82fb602 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -44,7 +44,7 @@ public final class TypeConfiguration extends ConfigurationBase { - private final ConcurrentMap, ConfigurationType> types = new ConcurrentHashMap<>(); + private final ConcurrentMap, ConfigurationType> types = new ConcurrentHashMap<>(); public TypeConfiguration() { } @@ -95,18 +95,22 @@ protected void removeIf(Predicate predicate) { } public ConfigurationType get(UnresolvedConfigurationCondition condition, String qualifiedJavaName) { - return types.get(new ConditionalElement<>(condition, qualifiedJavaName)); + return get(condition, new NamedConfigurationTypeDescriptor(qualifiedJavaName)); + } + + private ConfigurationType get(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor) { + return types.get(new ConditionalElement<>(condition, typeDescriptor)); } public void add(ConfigurationType type) { - ConfigurationType previous = types.putIfAbsent(new ConditionalElement<>(type.getCondition(), type.getQualifiedJavaName()), type); + ConfigurationType previous = types.putIfAbsent(new ConditionalElement<>(type.getCondition(), type.getTypeDescriptor()), type); if (previous != null && previous != type) { VMError.shouldNotReachHere("Cannot replace existing type " + previous + " with " + type); } } public void addOrMerge(ConfigurationType type) { - types.compute(new ConditionalElement<>(type.getCondition(), type.getQualifiedJavaName()), (key, value) -> { + types.compute(new ConditionalElement<>(type.getCondition(), type.getTypeDescriptor()), (key, value) -> { if (value == null) { return type; } else { @@ -117,7 +121,11 @@ public void addOrMerge(ConfigurationType type) { } public ConfigurationType getOrCreateType(UnresolvedConfigurationCondition condition, String qualifiedForNameString) { - return types.computeIfAbsent(new ConditionalElement<>(condition, qualifiedForNameString), p -> new ConfigurationType(p.condition(), p.element())); + return getOrCreateType(condition, new NamedConfigurationTypeDescriptor(qualifiedForNameString)); + } + + private ConfigurationType getOrCreateType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor) { + return types.computeIfAbsent(new ConditionalElement<>(condition, typeDescriptor), p -> new ConfigurationType(p.condition(), p.element(), false)); } @Override @@ -130,7 +138,7 @@ public void mergeConditional(UnresolvedConfigurationCondition condition, TypeCon @Override public void printJson(JsonWriter writer) throws IOException { List typesList = new ArrayList<>(this.types.values()); - typesList.sort(Comparator.comparing(ConfigurationType::getQualifiedJavaName).thenComparing(ConfigurationType::getCondition)); + typesList.sort(Comparator.comparing(ConfigurationType::getTypeDescriptor).thenComparing(ConfigurationType::getCondition)); writer.append('['); String prefix = ""; @@ -171,7 +179,6 @@ public int hashCode() { public interface Predicate { - boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type); - + boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type); } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java index 49702dd122029..eb29d3454c62a 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -27,8 +27,11 @@ import java.util.List; import java.util.regex.Pattern; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; + import com.oracle.svm.configure.config.ConfigurationPredefinedClass; import com.oracle.svm.configure.config.ConfigurationType; +import com.oracle.svm.configure.config.ConfigurationTypeDescriptor; import com.oracle.svm.configure.config.PredefinedClassesConfiguration; import com.oracle.svm.configure.config.ProxyConfiguration; import com.oracle.svm.configure.config.ResourceConfiguration; @@ -49,8 +52,8 @@ public ConditionalConfigurationPredicate(ComplexFilter filter) { } @Override - public boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type) { - return !filter.includes(conditionalElement.condition().getTypeName()) || !filter.includes(type.getQualifiedJavaName()); + public boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type) { + return testTypeDescriptor(conditionalElement.condition(), type.getTypeDescriptor()); } @Override @@ -82,4 +85,16 @@ public boolean testLambdaSerializationType(SerializationConfigurationLambdaCaptu public boolean testPredefinedClass(ConfigurationPredefinedClass clazz) { return clazz.getNameInfo() != null && !filter.includes(clazz.getNameInfo()); } + + private boolean testTypeDescriptor(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor) { + if (!filter.includes(condition.getTypeName())) { + return true; + } + for (String typeName : typeDescriptor.getAllQualifiedJavaNames()) { + if (!filter.includes(typeName)) { + return true; + } + } + return false; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index ff7323be53a61..2c6f5fa185707 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -95,11 +95,13 @@ public static final class Options { "Use a resource-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// public static final HostedOptionKey ResourceConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @Option(help = {"Files describing program elements to be made accessible via JNI (for syntax, see ReflectionConfigurationFiles)", + @Option(help = {"Files describing program elements to be made accessible via JNI according to the schema at " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json", "Use a jni-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey JNIConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = {"Resources describing program elements to be made accessible via JNI (see JNIConfigurationFiles).", + @Option(help = {"Resources describing program elements to be made accessible via JNI according to the schema at " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json", "Use a jni-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// public static final HostedOptionKey JNIConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); 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 beefc34476a45..370b680d1a6c2 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, 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 @@ -130,6 +130,23 @@ protected void checkAttributes(EconomicMap map, String type, Col } } + protected void checkHasExactlyOneAttribute(EconomicMap map, String type, Collection alternativeAttributes) { + boolean attributeFound = false; + for (String key : map.getKeys()) { + if (alternativeAttributes.contains(key)) { + if (attributeFound) { + String message = "Exactly one of [" + String.join(", ", alternativeAttributes) + "] must be set in " + type; + throw new JSONParserException(message); + } + attributeFound = true; + } + } + if (!attributeFound) { + String message = "Exactly one of [" + String.join(", ", alternativeAttributes) + "] must be set in " + type; + throw new JSONParserException(message); + } + } + /** * Used to warn about schema errors in configuration files. Should never be used if the type is * missing. 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 2632343f0a5ae..95edc68f319e6 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, 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 @@ -48,7 +48,7 @@ public final class ReflectionConfigurationParser extends ConfigurationPars private final ConfigurationConditionResolver conditionResolver; private final ReflectionConfigurationParserDelegate delegate; - private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", + private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("name", "type", "allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", "allDeclaredClasses", "allRecordComponents", "allPermittedSubclasses", "allNestMembers", "allSigners", "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, @@ -75,22 +75,43 @@ private void parseClassArray(List classes) { } private void parseClass(EconomicMap data) { - checkAttributes(data, "reflection class descriptor object", Collections.singleton("name"), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); - - Object classObject = data.get("name"); - String className = asString(classObject, "name"); + checkAttributes(data, "reflection class descriptor object", Collections.emptyList(), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); + checkHasExactlyOneAttribute(data, "reflection class descriptor object", List.of("name", "type")); TypeResult conditionResult = conditionResolver.resolveCondition(parseCondition(data)); if (!conditionResult.isPresent()) { return; } + String className; + Object typeObject = data.get("type"); + /* + * Classes registered using the old ("class") syntax will require elements (fields, methods, + * constructors, ...) to be registered for runtime queries, whereas the new ("type") syntax + * will automatically register all elements as queried. + */ + if (typeObject != null) { + if (typeObject instanceof String stringValue) { + className = stringValue; + } else { + /* + * We warn if we find a future version of a type descriptor (as a JSON object) + * instead of failing parsing. + */ + asMap(typeObject, "type descriptor should be a string or object"); + handleMissingElement("Unsupported type descriptor of type object"); + return; + } + } else { + className = asString(data.get("name"), "class name should be a string"); + } + /* * Even if primitives cannot be queried through Class.forName, they can be registered to * allow getDeclaredMethods() and similar bulk queries at run time. */ C condition = conditionResult.get(); - TypeResult result = delegate.resolveType(condition, className, true); + TypeResult result = delegate.resolveType(condition, className, true, false); if (!result.isPresent()) { handleMissingElement("Could not resolve class " + className + " for reflection configuration.", result.getException()); return; @@ -277,7 +298,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.resolveType(conditionResolver.alwaysTrue(), typeName, true); + TypeResult typeResult = delegate.resolveType(conditionResolver.alwaysTrue(), typeName, true, false); if (!typeResult.isPresent()) { handleMissingElement("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", typeResult.getException()); return null; 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 b4b29c58c5254..c561d8a509dad 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -30,7 +30,7 @@ public interface ReflectionConfigurationParserDelegate { - TypeResult resolveType(C condition, String typeName, boolean allowPrimitives); + TypeResult resolveType(C condition, String typeName, boolean allowPrimitives, boolean includeAllElements); void registerType(C condition, T type); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt index f55641cebe0a3..1e2a5871659b1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt @@ -3,7 +3,7 @@ Use a reflect-config.json in your META-INF/native-image// d The JSON object schema is described at: - https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.0.0.json + https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json Example: 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 2f2c5755e5bf2..fbe6f8cc78212 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -43,8 +43,8 @@ public class ReflectionRegistryAdapter extends RegistryAdapter { } @Override - public TypeResult> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) { - TypeResult> result = super.resolveType(condition, typeName, allowPrimitives); + public TypeResult> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives, boolean includeAllElements) { + TypeResult> result = super.resolveType(condition, typeName, allowPrimitives, includeAllElements); if (!result.isPresent()) { Throwable classLookupException = result.getException(); if (classLookupException instanceof LinkageError) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java index 18dcdeeaceafa..dc08e634cde8d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -63,7 +63,7 @@ public void registerType(ConfigurationCondition condition, Class type) { } @Override - public TypeResult> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) { + public TypeResult> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives, boolean includeAllElements) { String name = canonicalizeTypeName(typeName); return classLoader.findClass(name, allowPrimitives); }