From 932a8f550584fa4715ff8dbd67c8fa90fdf552c5 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Wed, 10 Sep 2025 15:09:47 +0200 Subject: [PATCH 1/7] Enable --future-defaults=complete-reflection-types --- .../LegacyReflectionConfigurationParser.java | 9 +---- .../svm/core/FutureDefaultsOptions.java | 35 ++++++++++++++----- .../core/configure/ConfigurationFiles.java | 8 ++--- .../svm/core/jdk/SystemPropertiesSupport.java | 3 ++ 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/LegacyReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/LegacyReflectionConfigurationParser.java index fa8ccec6c09c..3f39331b1dc7 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/LegacyReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/LegacyReflectionConfigurationParser.java @@ -48,13 +48,6 @@ final class LegacyReflectionConfigurationParser extends ReflectionConfigur super(conditionResolver, delegate, parserOptions); } - @Override - protected EnumSet supportedOptions() { - EnumSet base = super.supportedOptions(); - base.add(ConfigurationParserOption.TREAT_ALL_NAME_ENTRIES_AS_TYPE); - return base; - } - @Override public void parseAndRegister(Object json, URI origin) { parseClassArray(asList(json, "first level of document must be an array of class descriptors")); @@ -64,7 +57,7 @@ public void parseAndRegister(Object json, URI origin) { protected void parseClass(EconomicMap data) { checkAttributes(data, "reflection class descriptor object", List.of(NAME_KEY), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); - Optional type = parseName(data, checkOption(ConfigurationParserOption.TREAT_ALL_NAME_ENTRIES_AS_TYPE)); + Optional type = parseName(data, true); if (type.isEmpty()) { return; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java index a51b29c30542..2340eea3d8de 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java @@ -41,6 +41,7 @@ import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.StringUtil; import jdk.graal.compiler.options.Option; @@ -79,8 +80,10 @@ public class FutureDefaultsOptions { private static final String RUN_TIME_INITIALIZE_SECURITY_PROVIDERS = "run-time-initialize-security-providers"; private static final String RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS = "run-time-initialize-file-system-providers"; + private static final List ALL_FUTURE_DEFAULTS = List.of(RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, RUN_TIME_INITIALIZE_SECURITY_PROVIDERS); + private static final String COMPLETE_REFLECTION_TYPES = "complete-reflection-types"; - private static final List ALL_FUTURE_DEFAULTS = List.of(RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, RUN_TIME_INITIALIZE_SECURITY_PROVIDERS, COMPLETE_REFLECTION_TYPES); + private static final List RETIRED_FUTURE_DEFAULTS = List.of(COMPLETE_REFLECTION_TYPES); public static final String RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS_REASON = "Initialize JDK classes at run time (--" + OPTION_NAME + " includes " + RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS + ")"; @@ -140,6 +143,15 @@ public static void parseAndVerifyOptions() { getOptionHelpText()); } + if (RETIRED_FUTURE_DEFAULTS.contains(value)) { + LogUtils.warning("The '%s' option from %s contains the value '%s' which is enabled by default in this GraalVM release (%s) and can be removed.", + SubstrateOptionsParser.commandArgument(FutureDefaults, value), + valueWithOrigin.origin(), + value, + VM.getVersion()); + return; + } + if (!getAllValues().contains(value)) { throw UserError.abort("The '%s' option from %s contains invalid value '%s'. It can only contain: %s.%n%nUsage:%n%n%s", SubstrateOptionsParser.commandArgument(FutureDefaults, value), @@ -169,26 +181,31 @@ public static void parseAndVerifyOptions() { /* Set build-time properties for user features */ for (String futureDefault : getFutureDefaults()) { - System.setProperty(FutureDefaultsOptions.SYSTEM_PROPERTY_PREFIX + futureDefault, Boolean.TRUE.toString()); + setSystemProperty(futureDefault, true); } + + for (String retiredFutureDefault : RETIRED_FUTURE_DEFAULTS) { + setSystemProperty(retiredFutureDefault, true); + } + } + + private static void setSystemProperty(String futureDefault, boolean value) { + System.setProperty(FutureDefaultsOptions.SYSTEM_PROPERTY_PREFIX + futureDefault, Boolean.toString(value)); } public static Set getFutureDefaults() { return Collections.unmodifiableSet(Objects.requireNonNull(futureDefaults, "must be initialized before usage")); } - /** - * @see FutureDefaultsOptions#FutureDefaults - */ - public static boolean allFutureDefaults() { - return getFutureDefaults().containsAll(ALL_FUTURE_DEFAULTS); + public static List getRetiredFutureDefaults() { + return RETIRED_FUTURE_DEFAULTS; } /** * @see FutureDefaultsOptions#FutureDefaults */ - public static boolean completeReflectionTypes() { - return getFutureDefaults().contains(COMPLETE_REFLECTION_TYPES); + public static boolean allFutureDefaults() { + return getFutureDefaults().containsAll(ALL_FUTURE_DEFAULTS); } /** 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 f6d399ba0a97..0989c29a5729 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 @@ -41,7 +41,6 @@ import com.oracle.svm.configure.ConfigurationFile; import com.oracle.svm.configure.ConfigurationParserOption; -import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.BundleMember; import com.oracle.svm.core.option.HostedOptionKey; @@ -151,8 +150,8 @@ public static final class Options { @Option(help = "Testing flag: the 'typeReachable' condition is treated as typeReached so the semantics of programs can change.")// public static final HostedOptionKey TreatAllTypeReachableConditionsAsTypeReached = new HostedOptionKey<>(false); - @Option(help = "Testing flag: the 'name' is treated as 'type' in reflection configuration.")// - public static final HostedOptionKey TreatAllNameEntriesAsType = new HostedOptionKey<>(false); + @Option(help = "Deprecated, has no effect.", deprecated = true)// + public static final HostedOptionKey TreatAllNameEntriesAsType = new HostedOptionKey<>(true); @Option(help = "Testing flag: the 'typeReached' condition is always satisfied however it prints the stack trace where it would not be satisfied.")// public static final HostedOptionKey TrackUnsatisfiedTypeReachedConditions = new HostedOptionKey<>(false); @@ -181,9 +180,6 @@ public static EnumSet getConfigurationParserOptions() if (TreatAllTypeReachableConditionsAsTypeReached.getValue()) { result.add(ConfigurationParserOption.TREAT_ALL_TYPE_REACHABLE_CONDITIONS_AS_TYPE_REACHED); } - if (TreatAllNameEntriesAsType.getValue() || FutureDefaultsOptions.completeReflectionTypes()) { - result.add(ConfigurationParserOption.TREAT_ALL_NAME_ENTRIES_AS_TYPE); - } return result; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java index 4fd9a19f4e05..1b6a969bc10e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java @@ -144,6 +144,9 @@ protected SystemPropertiesSupport() { for (String futureDefault : FutureDefaultsOptions.getFutureDefaults()) { initializeProperty(FutureDefaultsOptions.SYSTEM_PROPERTY_PREFIX + futureDefault, Boolean.TRUE.toString()); } + for (String futureDefault : FutureDefaultsOptions.getRetiredFutureDefaults()) { + initializeProperty(FutureDefaultsOptions.SYSTEM_PROPERTY_PREFIX + futureDefault, Boolean.TRUE.toString()); + } ArrayList lazyProperties = new ArrayList<>(); lazyProperties.add(new LazySystemProperty(UserSystemProperty.NAME, this::userNameValue)); From 9ad6541e5431a57c838570c255f7ca4a44cc4628 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Mon, 20 Oct 2025 15:34:58 +0200 Subject: [PATCH 2/7] Bugfixes for the inclusion of all metadata from reflectively-accessed classes --- .../substitutions/GraalSubstitutions.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java index fc9f2b8dc83b..4da73a4a760a 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java @@ -46,6 +46,7 @@ import com.oracle.svm.core.SubstrateTargetDescription; import com.oracle.svm.core.VMInspectionOptions; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -239,6 +240,25 @@ public static void dumpHeap(String outputFile, boolean live) throws IOException, } } +/** + * These field and methods make HotSpot types reachable when + * {@code jdk.graal.compiler.management.JMXServiceProvider} is registered for reflection. + */ +@TargetClass(className = "jdk.graal.compiler.management.JMXServiceProvider", onlyWith = GraalCompilerFeature.IsEnabled.class) +final class Target_jdk_graal_compiler_management_JMXServiceProvider { + @Delete private Target_com_sun_management_HotSpotDiagnosticMXBean hotspotMXBean; + + @Delete + private static native Target_com_sun_management_HotSpotDiagnosticMXBean getHotSpotMXBean(); + + @Delete + private native void initHotSpotMXBean(); +} + +@TargetClass(className = "com.sun.management.HotSpotDiagnosticMXBean", onlyWith = GraalCompilerFeature.IsEnabled.class) +final class Target_com_sun_management_HotSpotDiagnosticMXBean { +} + @TargetClass(className = "jdk.graal.compiler.serviceprovider.GlobalAtomicLong", onlyWith = GraalCompilerFeature.IsEnabled.class) final class Target_jdk_graal_compiler_serviceprovider_GlobalAtomicLong { @Alias// From 51f46c24c31c0e333e5522903775ed3ad782e43d Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Wed, 22 Oct 2025 14:33:51 +0200 Subject: [PATCH 3/7] Adapt Truffle to queried-only methods --- .../org.graalvm.nativeimage/snapshot.sigtest | 1 + .../MissingReflectionRegistrationError.java | 13 +++++ .../impl/ReflectionIntrospector.java | 51 +++++++++++++++++++ .../reflect/ReflectionIntrospectorImpl.java | 50 ++++++++++++++++++ .../serialize/SerializationSupport.java | 12 +---- .../Target_java_lang_reflect_Constructor.java | 2 +- .../Target_java_lang_reflect_Method.java | 2 +- .../svm/hosted/reflect/ReflectionFeature.java | 4 ++ .../truffle/host/HostInteropReflect.java | 18 +++---- .../oracle/truffle/host/HostMethodDesc.java | 31 +++++++++++ 10 files changed, 160 insertions(+), 24 deletions(-) create mode 100644 sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionIntrospector.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionIntrospectorImpl.java diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index cf7f8932ad61..9881b4ad914a 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -257,6 +257,7 @@ meth public java.lang.Class getDeclaringClass() meth public java.lang.Class getElementType() meth public java.lang.Class[] getParameterTypes() meth public java.lang.String getElementName() +meth public static boolean isInvocable(java.lang.reflect.Executable) supr java.lang.LinkageError hfds declaringClass,elementName,elementType,parameterTypes,serialVersionUID diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java index a2a4ee58ed67..7fa86b36f665 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java @@ -42,9 +42,12 @@ import java.io.Serial; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; +import org.graalvm.nativeimage.impl.ReflectionIntrospector; + /** * This exception is thrown when a reflective query (such as * {@link Class#getMethod(String, Class[])}) tries to access an element that was not [] getParameterTypes() { return parameterTypes; } + + /** + * @return Whether the executable can be invoked without throwing a + * {@link MissingReflectionRegistrationError}. + * + * @since 25.1 + */ + public static boolean isInvocable(Executable executable) { + return !ImageSingletons.contains(ReflectionIntrospector.class) || ImageSingletons.lookup(ReflectionIntrospector.class).isInvocable(executable); + } } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionIntrospector.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionIntrospector.java new file mode 100644 index 000000000000..66632494914c --- /dev/null +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionIntrospector.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025, 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.lang.reflect.Executable; + +/** + * Provides an interface to query whether an executable has invocation capabilities, or was only + * registered for reflective queries. + */ +public interface ReflectionIntrospector { + boolean isInvocable(Executable executable); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionIntrospectorImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionIntrospectorImpl.java new file mode 100644 index 000000000000..8ce13a05dd5d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionIntrospectorImpl.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, 2025, 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.reflect; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Constructor; +import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Method; +import org.graalvm.nativeimage.impl.ReflectionIntrospector; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; + +public class ReflectionIntrospectorImpl implements ReflectionIntrospector { + @Override + public boolean isInvocable(Executable executable) { + switch (executable) { + case Method method -> { + Target_java_lang_reflect_Method methodSubstitution = SubstrateUtil.cast(method, Target_java_lang_reflect_Method.class); + return methodSubstitution.methodAccessor != null || methodSubstitution.methodAccessorFromMetadata != null; + } + case Constructor constructor -> { + Target_java_lang_reflect_Constructor constructorSubstitution = SubstrateUtil.cast(constructor, Target_java_lang_reflect_Constructor.class); + return constructorSubstitution.constructorAccessor != null || constructorSubstitution.constructorAccessorFromMetadata != null; + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java index 974c5c40c8fb..86844cc49290 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java @@ -25,8 +25,6 @@ */ package com.oracle.svm.core.reflect.serialize; -import static com.oracle.svm.core.SubstrateOptions.ThrowMissingRegistrationErrors; - import java.io.Serializable; import java.lang.invoke.SerializedLambda; import java.lang.reflect.Constructor; @@ -288,14 +286,8 @@ public static Object getSerializationConstructorAccessor(Class serializationT } String targetConstructorClassName = targetConstructorClass.getName(); - if (ThrowMissingRegistrationErrors.hasBeenSet()) { - MissingSerializationRegistrationUtils.reportSerialization(declaringClass, - "type '" + declaringClass.getTypeName() + "' with target constructor class '" + targetConstructorClassName + "'"); - } else { - throw VMError.unsupportedFeature("SerializationConstructorAccessor class not found for declaringClass: " + declaringClass.getName() + - " (targetConstructorClass: " + targetConstructorClassName + "). Usually adding " + declaringClass.getName() + - " to serialization-config.json fixes the problem."); - } + MissingSerializationRegistrationUtils.reportSerialization(declaringClass, + "type '" + declaringClass.getTypeName() + "' with target constructor class '" + targetConstructorClassName + "'"); return null; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java index 20f71ce91d88..3e5b98a26d61 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java @@ -78,7 +78,7 @@ public final class Target_java_lang_reflect_Constructor { */ @Inject // @RecomputeFieldValue(kind = Kind.Reset) // - Target_jdk_internal_reflect_ConstructorAccessor constructorAccessorFromMetadata; + public Target_jdk_internal_reflect_ConstructorAccessor constructorAccessorFromMetadata; @Alias @TargetElement(name = CONSTRUCTOR_NAME) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java index b9c0970e0f62..3ed40d22307e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java @@ -117,7 +117,7 @@ public final class Target_java_lang_reflect_Method { */ @Inject // @RecomputeFieldValue(kind = Kind.Reset) // - Target_jdk_internal_reflect_MethodAccessor methodAccessorFromMetadata; + public Target_jdk_internal_reflect_MethodAccessor methodAccessorFromMetadata; @Inject // @RecomputeFieldValue(kind = Kind.Custom, declClass = LayerIdComputer.class) // diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index ab6533c29930..d18515312061 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -46,6 +46,7 @@ import org.graalvm.nativeimage.dynamicaccess.AccessCondition; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.AnnotationExtractor; +import org.graalvm.nativeimage.impl.ReflectionIntrospector; import org.graalvm.nativeimage.impl.RuntimeJNIAccessSupport; import org.graalvm.nativeimage.impl.RuntimeProxyRegistrySupport; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; @@ -73,6 +74,7 @@ import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.meta.MethodRef; import com.oracle.svm.core.reflect.ReflectionAccessorHolder; +import com.oracle.svm.core.reflect.ReflectionIntrospectorImpl; import com.oracle.svm.core.reflect.SubstrateAccessor; import com.oracle.svm.core.reflect.SubstrateConstructorAccessor; import com.oracle.svm.core.reflect.SubstrateMethodAccessor; @@ -310,6 +312,8 @@ public void afterRegistration(AfterRegistrationAccess access) { ImageSingletons.add(RuntimeReflectionSupport.class, reflectionData); ImageSingletons.add(ReflectionHostedSupport.class, reflectionData); + ImageSingletons.add(ReflectionIntrospector.class, new ReflectionIntrospectorImpl()); + /* * Querying Object members is allowed to enable these accesses on array classes, since those * don't define any additional members. diff --git a/truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostInteropReflect.java b/truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostInteropReflect.java index a02a07b8e8cc..ec2c833d6058 100644 --- a/truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostInteropReflect.java +++ b/truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostInteropReflect.java @@ -187,20 +187,14 @@ static boolean isModifiable(HostObject object, Class clazz, String name, bool static boolean isInvokable(HostObject object, Class clazz, String name, boolean onlyStatic) { HostClassDesc classDesc = HostClassDesc.forClass(object.context, clazz); HostMethodDesc foundMethod = classDesc.lookupMethod(name, onlyStatic); - if (foundMethod != null) { - return true; - } else if (isSignature(name)) { - foundMethod = classDesc.lookupMethodBySignature(name, onlyStatic); - if (foundMethod != null) { - return true; - } - } else if (isJNIName(name)) { - foundMethod = classDesc.lookupMethodByJNIName(name, onlyStatic); - if (foundMethod != null) { - return true; + if (foundMethod == null) { + if (isSignature(name)) { + foundMethod = classDesc.lookupMethodBySignature(name, onlyStatic); + } else if (isJNIName(name)) { + foundMethod = classDesc.lookupMethodByJNIName(name, onlyStatic); } } - return false; + return foundMethod != null && foundMethod.isInvocable(); } @TruffleBoundary diff --git a/truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostMethodDesc.java b/truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostMethodDesc.java index ece0061ef20b..18275aec8bfe 100644 --- a/truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostMethodDesc.java +++ b/truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostMethodDesc.java @@ -77,6 +77,8 @@ boolean isInternal() { abstract boolean isConstructor(); + abstract boolean isInvocable(); + abstract static class SingleMethod extends HostMethodDesc { static final int[] EMTPY_SCOPED_PARAMETERS = new int[0]; @@ -143,6 +145,25 @@ public boolean isOnlyVisibleFromJniName() { public abstract Executable getReflectionMethod(); + private static final Method isInvocableMethod = TruffleOptions.AOT ? getIsInvocableMethod() : null; + + private static Method getIsInvocableMethod() { + try { + return Class.forName("org.graalvm.nativeimage.MissingReflectionRegistrationError").getMethod("isInvocable", Executable.class); + } catch (ClassNotFoundException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @Override + boolean isInvocable() { + try { + return !TruffleOptions.AOT || (boolean) isInvocableMethod.invoke(null, getReflectionMethod()); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + public final boolean isVarArgs() { return varArgs; } @@ -598,6 +619,16 @@ public boolean isInternal() { } return true; } + + @Override + boolean isInvocable() { + for (SingleMethod overload : overloads) { + if (!overload.isInvocable()) { + return false; + } + } + return true; + } } } From 085b620e6d75a9eb58c406dda622a5136af9cf8a Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Tue, 28 Oct 2025 10:26:25 +0100 Subject: [PATCH 4/7] Fix signer registration --- .../com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 8e6cc1e7a547..be04c14c632a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -379,7 +379,7 @@ public void registerAllSignersQuery(AccessCondition condition, Class clazz) { Object[] signers = clazz.getSigners(); if (signers != null) { for (Object signer : signers) { - metaAccess.lookupJavaType(signer.getClass()).registerAsInstantiated("signer"); + universe.getHeapScanner().rescanObject(signer, new OtherReason("Signer stored in reflection metadata")); } } }); From 4bad9488fd2582e9468fdff84d52ee47721d31a3 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Tue, 28 Oct 2025 14:32:59 +0100 Subject: [PATCH 5/7] Add necessary reachability metadata for Petclinic with complete reflection types --- sdk/mx.sdk/mx_sdk_benchmark.py | 6 +++ .../reachability-metadata.json | 37 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 sdk/mx.sdk/petclinic-config/reachability-metadata.json diff --git a/sdk/mx.sdk/mx_sdk_benchmark.py b/sdk/mx.sdk/mx_sdk_benchmark.py index f71f54ee85ec..a661fb807f12 100644 --- a/sdk/mx.sdk/mx_sdk_benchmark.py +++ b/sdk/mx.sdk/mx_sdk_benchmark.py @@ -5613,6 +5613,12 @@ def version(self): def applicationDist(self): return mx.library("PETCLINIC_" + self.version(), True).get_path(True) + def extra_image_build_argument(self, benchmark, args): + additional_configuration = os.path.join(getattr(self, '.mxsuite').mxDir, "petclinic-config") + return [ + f"-H:ConfigurationFileDirectories={additional_configuration}", + ] + super(BasePetClinicBenchmarkSuite, self).extra_image_build_argument(benchmark, args) + class PetClinicWrkBenchmarkSuite(BasePetClinicBenchmarkSuite, BaseWrkBenchmarkSuite): """PetClinic benchmark suite that measures throughput using Wrk.""" diff --git a/sdk/mx.sdk/petclinic-config/reachability-metadata.json b/sdk/mx.sdk/petclinic-config/reachability-metadata.json new file mode 100644 index 000000000000..0b53082303a8 --- /dev/null +++ b/sdk/mx.sdk/petclinic-config/reachability-metadata.json @@ -0,0 +1,37 @@ +{ + "reflection": [ + { + "type": "org.apache.coyote.AbstractProtocol", + "methods": [ + { + "name": "setProperty", + "parameterTypes": [ + "java.lang.String", + "java.lang.String" + ] + }, + { + "name": "getAddress", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.coyote.http11.AbstractHttp11Protocol", + "methods": [ + { + "name": "isSSLEnabled", + "parameterTypes": [] + } + ] + }, + { + "type": "com.zaxxer.hikari.HikariDataSource", + "fields": [ + { + "name": "pool" + } + ] + } + ] +} \ No newline at end of file From a7a2e0eaa602c3e56e0ae0b719b3276e481243f0 Mon Sep 17 00:00:00 2001 From: Sacha Coppey Date: Wed, 29 Oct 2025 18:26:20 +0100 Subject: [PATCH 6/7] Ensure the list of forbidden modules for layered images is always complete when accessed --- .../pointsto/AbstractAnalysisEngine.java | 2 +- .../com/oracle/graal/pointsto/api/HostVM.java | 2 +- .../src/com/oracle/svm/hosted/SVMHost.java | 103 +++++++++--------- 3 files changed, 55 insertions(+), 52 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java index ab0d62d04e3e..3a20da6cd26c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java @@ -421,7 +421,7 @@ public void tryRegisterNativeMethodsForBaseImage(ResolvedJavaType type) { * Some modules contain native methods that should not be included in the image because they * are hosted only, or because they are currently unsupported. */ - Set forbiddenModules = hostVM.getForbiddenModules(); + Set forbiddenModules = hostVM.getSharedLayerForbiddenModules(); if (forbiddenModules.contains(OriginalClassProvider.getJavaClass(type).getModule())) { return; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java index c122adf8d0ff..b0ee0d3c2ec9 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java @@ -457,7 +457,7 @@ public boolean preventConstantFolding(AnalysisField aField) { return false; } - public Set getForbiddenModules() { + public Set getSharedLayerForbiddenModules() { return Set.of(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 7213e51c2a16..4c4f091aa11e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -247,7 +247,12 @@ public enum UsageKind { private final boolean buildingExtensionLayer = ImageLayerBuildingSupport.buildingExtensionLayer(); // All elements below are from the host VM universe, not the analysis universe - private Set sharedLayerExcludedFields; + private final Set sharedLayerExcludedFields; + /** + * Some modules contain native methods that should never be in the image, as they are either + * hosted only, or currently unsupported in layered images. + */ + protected final Set sharedLayerForbiddenModules; private final ResolvedJavaType optionKeyType; private final ResolvedJavaType featureType; @@ -261,12 +266,6 @@ public enum UsageKind { private final boolean trackDynamicAccess; private DynamicAccessDetectionSupport dynamicAccessDetectionSupport = null; - /** - * Some modules contain native methods that should never be in the image, as they are either - * hosted only, or currently unsupported in layered images. - */ - private final Set forbiddenModules = new HashSet<>(); - @SuppressWarnings("this-escape") public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport, AnnotationSubstitutionProcessor annotationSubstitutions, MissingRegistrationSupport missingRegistrationSupport) { @@ -298,7 +297,11 @@ public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializatio } layerId = buildingImageLayer ? DynamicImageLayerInfo.getCurrentLayerNumber() : 0; if (buildingSharedLayer) { - initializeSharedLayerExcludedFields(); + sharedLayerExcludedFields = initializeSharedLayerExcludedFields(); + sharedLayerForbiddenModules = initializeSharedLayerForbiddenModules(); + } else { + sharedLayerExcludedFields = null; + sharedLayerForbiddenModules = null; } layeredStaticFieldSupport = buildingImageLayer ? LayeredStaticFieldSupport.singleton() : null; @@ -1015,41 +1018,60 @@ private ResolvedJavaField lookupOriginalDeclaredField(Class declaringClass, S return originalMetaAccess.lookupJavaField(ReflectionUtil.lookupField(declaringClass, fieldName)); } - private void initializeSharedLayerExcludedFields() { - sharedLayerExcludedFields = new HashSet<>(); + private Set initializeSharedLayerExcludedFields() { + Set excludedFields = new HashSet<>(); + /* * These fields need to be folded as they are used in snippets, and they must be accessed * without producing reads with side effects. */ - - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "layoutEncoding")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "numClassTypes")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "numIterableInterfaceTypes")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "openTypeWorldTypeCheckSlots")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "openTypeWorldInterfaceHashParam")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "openTypeWorldInterfaceHashTable")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "interfaceID")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "typeIDDepth")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "typeID")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "monitorOffset")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "hubType")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "companion")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHubCompanion.class, "arrayHub")); - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHubCompanion.class, "additionalFlags")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "layoutEncoding")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "numClassTypes")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "numIterableInterfaceTypes")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "openTypeWorldTypeCheckSlots")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "openTypeWorldInterfaceHashParam")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "openTypeWorldInterfaceHashTable")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "interfaceID")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "typeIDDepth")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "typeID")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "monitorOffset")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "hubType")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "companion")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHubCompanion.class, "arrayHub")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHubCompanion.class, "additionalFlags")); /* Needs to be immutable for correct lowering of SubstrateIdentityHashCodeNode. */ - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "identityHashOffset")); + excludedFields.add(lookupOriginalDeclaredField(DynamicHub.class, "identityHashOffset")); /* * Including this field makes ThreadLocalAllocation.getTlabDescriptorSize reachable through * ThreadLocalAllocation.regularTLAB which is accessed with * FastThreadLocalBytes.getSizeSupplier */ - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(VMThreadLocalInfo.class, "sizeSupplier")); + excludedFields.add(lookupOriginalDeclaredField(VMThreadLocalInfo.class, "sizeSupplier")); /* This field cannot be written to (see documentation) */ - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(Counter.Group.class, "enabled")); + excludedFields.add(lookupOriginalDeclaredField(Counter.Group.class, "enabled")); /* This field can contain a reference to a Thread, which is not allowed in the heap */ - sharedLayerExcludedFields.add(lookupOriginalDeclaredField(NativeLibraries.class, "nativeLibraryLockMap")); + excludedFields.add(lookupOriginalDeclaredField(NativeLibraries.class, "nativeLibraryLockMap")); + + return excludedFields; + } + + protected Set initializeSharedLayerForbiddenModules() { + Set forbiddenModules = new HashSet<>(); + forbiddenModules.add(JVMCI.class.getModule()); + addForbiddenModule(forbiddenModules, "com.oracle.svm.shadowed.org.bytedeco.llvm.global.LLVM"); + addForbiddenModule(forbiddenModules, "com.oracle.svm.shadowed.org.bytedeco.javacpp.presets.javacpp"); + addForbiddenModule(forbiddenModules, "com.oracle.truffle.polyglot.JDKSupport"); + addForbiddenModule(forbiddenModules, "com.oracle.truffle.runtime.hotspot.libgraal.LibGraal"); + return forbiddenModules; + } + + protected static void addForbiddenModule(Set sharedLayerForbiddenModules, String className) { + Class clazz = ReflectionUtil.lookupClass(true, className); + if (clazz != null) { + sharedLayerForbiddenModules.add(clazz.getModule()); + } } /** If it's not one of the known builder types it must be an original VM type. */ @@ -1549,26 +1571,7 @@ public ConstantExpressionRegistry getConstantExpressionRegistry() { } @Override - public Set getForbiddenModules() { - if (forbiddenModules.isEmpty()) { - forbiddenModules.add(JVMCI.class.getModule()); - Class llvm = ReflectionUtil.lookupClass(true, "com.oracle.svm.shadowed.org.bytedeco.llvm.global.LLVM"); - if (llvm != null) { - forbiddenModules.add(llvm.getModule()); - } - Class javacpp = ReflectionUtil.lookupClass(true, "com.oracle.svm.shadowed.org.bytedeco.javacpp.presets.javacpp"); - if (javacpp != null) { - forbiddenModules.add(javacpp.getModule()); - } - Class truffle = ReflectionUtil.lookupClass(true, "com.oracle.truffle.polyglot.JDKSupport"); - if (truffle != null) { - forbiddenModules.add(truffle.getModule()); - } - Class libGraal = ReflectionUtil.lookupClass(true, "com.oracle.truffle.runtime.hotspot.libgraal.LibGraal"); - if (libGraal != null) { - forbiddenModules.add(libGraal.getModule()); - } - } - return forbiddenModules; + public Set getSharedLayerForbiddenModules() { + return sharedLayerForbiddenModules; } } From 344d603421fc17cc090280a535ec9db7fc195c0b Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Fri, 31 Oct 2025 15:32:00 +0100 Subject: [PATCH 7/7] Fix preserved elements support with complete reflection types --- .../svm/hosted/InternalReflectiveAccess.java | 4 +- .../hosted/reflect/ReflectionDataBuilder.java | 99 ++++++++++++------- .../svm/hosted/reflect/ReflectionFeature.java | 2 +- 3 files changed, 66 insertions(+), 39 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InternalReflectiveAccess.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InternalReflectiveAccess.java index 511bfbdd86c7..54c74f44fa91 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InternalReflectiveAccess.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InternalReflectiveAccess.java @@ -55,7 +55,7 @@ public static InternalReflectiveAccess singleton() { public void register(AccessCondition condition, Class... classes) { for (Class clazz : classes) { rrsInstance.register(condition, clazz); - rrsInstance.registerClassMetadata(condition, clazz); + rrsInstance.registerClassMetadata(condition, clazz, false); } } @@ -89,7 +89,7 @@ public void register(AccessCondition condition, Field... fields) { public void registerForSerialization(AccessCondition condition, Class... classes) { RuntimeSerializationSupport.singleton().register(condition, classes); for (Class clazz : classes) { - rrsInstance.registerClassMetadata(condition, clazz); + rrsInstance.registerClassMetadata(condition, clazz, false); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index be04c14c632a..e266fabe3b17 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -86,7 +86,6 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.MissingRegistrationUtils; import com.oracle.svm.core.configure.ConditionalRuntimeValue; import com.oracle.svm.core.configure.RuntimeDynamicAccessMetadata; @@ -149,12 +148,15 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl */ private final Map, Set>> innerClasses = new ConcurrentHashMap<>(); private final Map, Integer> enabledQueriesFlags = new ConcurrentHashMap<>(); - private final Map>> registeredFields = new ConcurrentHashMap<>(); + private final Map>> registeredFields = new ConcurrentHashMap<>(); private final Set hidingFields = ConcurrentHashMap.newKeySet(); - private final Map>> registeredMethods = new ConcurrentHashMap<>(); + private final Map>> registeredMethods = new ConcurrentHashMap<>(); private final Map methodAccessors = new ConcurrentHashMap<>(); private final Set hidingMethods = ConcurrentHashMap.newKeySet(); + private record RegisteredMemberData(ConditionalRuntimeValue member, boolean queriedOnly) { + } + // Heap reflection data private final Set heapDynamicHubs = ConcurrentHashMap.newKeySet(); private final Map heapFields = new ConcurrentHashMap<>(); @@ -221,9 +223,7 @@ public void register(AccessCondition condition, boolean preserved, Class claz Objects.requireNonNull(clazz, () -> nullErrorMessage("class", "reflection")); runConditionalInAnalysisTask(condition, (cnd) -> { registerClass(cnd, clazz, true, preserved); - if (FutureDefaultsOptions.completeReflectionTypes()) { - registerClassMetadata(cnd, clazz); - } + registerClassMetadata(cnd, clazz, preserved); }); } @@ -245,18 +245,18 @@ public void registerAllClassesQuery(AccessCondition condition, boolean preserved }); } - public void registerClassMetadata(AccessCondition condition, Class clazz) { - registerAllDeclaredFieldsQuery(condition, true, false, clazz); - registerAllFieldsQuery(condition, true, false, clazz); - registerAllDeclaredMethodsQuery(condition, true, false, clazz); - registerAllMethodsQuery(condition, true, false, clazz); - registerAllDeclaredConstructorsQuery(condition, true, false, clazz); - registerAllConstructorsQuery(condition, true, false, clazz); - registerAllDeclaredClassesQuery(condition, false, clazz); - registerAllClassesQuery(condition, false, clazz); + public void registerClassMetadata(AccessCondition condition, Class clazz, boolean preserved) { + registerAllDeclaredFieldsQuery(condition, true, preserved, clazz); + registerAllFieldsQuery(condition, true, preserved, clazz); + registerAllDeclaredMethodsQuery(condition, true, preserved, clazz); + registerAllMethodsQuery(condition, true, preserved, clazz); + registerAllDeclaredConstructorsQuery(condition, true, preserved, clazz); + registerAllConstructorsQuery(condition, true, preserved, clazz); + registerAllDeclaredClassesQuery(condition, preserved, clazz); + registerAllClassesQuery(condition, preserved, clazz); registerAllRecordComponentsQuery(condition, clazz); - registerAllPermittedSubclassesQuery(condition, false, clazz); - registerAllNestMembersQuery(condition, false, clazz); + registerAllPermittedSubclassesQuery(condition, preserved, clazz); + registerAllNestMembersQuery(condition, preserved, clazz); registerAllSignersQuery(condition, clazz); } @@ -465,24 +465,29 @@ private void registerMethod(AccessCondition cnd, boolean queriedOnly, boolean pr var classMethods = registeredMethods.computeIfAbsent(declaringType, _ -> new ConcurrentHashMap<>()); var shouldRegisterReachabilityHandler = classMethods.isEmpty(); - boolean registered = false; - ConditionalRuntimeValue conditionalValue = classMethods.get(analysisMethod); - if (conditionalValue == null) { - var newConditionalValue = new ConditionalRuntimeValue<>(RuntimeDynamicAccessMetadata.emptySet(preserved), reflectExecutable); - conditionalValue = classMethods.putIfAbsent(analysisMethod, newConditionalValue); - if (conditionalValue == null) { - conditionalValue = newConditionalValue; - registered = true; + boolean exists = classMethods.containsKey(analysisMethod); + ConditionalRuntimeValue conditionalValue = classMethods.compute(analysisMethod, (_, methodData) -> { + if (methodData == null || (methodData.queriedOnly() && !queriedOnly)) { + /* + * The dynamic access metadata needs to be reset when registering a queried-only + * element as accessed. + */ + return new RegisteredMemberData<>(new ConditionalRuntimeValue<>(RuntimeDynamicAccessMetadata.emptySet(preserved), reflectExecutable), queriedOnly); + } else { + if (!preserved && methodData.queriedOnly() == queriedOnly) { + var value = methodData.member(); + value.getDynamicAccessMetadata().setNotPreserved(); + } + return methodData; } - } else if (!preserved) { - conditionalValue.getDynamicAccessMetadata().setNotPreserved(); - } + }).member(); + if (!queriedOnly) { /* queryOnly methods are conditioned by the type itself */ conditionalValue.getDynamicAccessMetadata().addCondition(cnd); } - if (registered) { + if (!exists) { registerTypesForMethod(analysisMethod, reflectExecutable); Class declaringClass = declaringType.getJavaClass(); @@ -641,12 +646,24 @@ private void registerField(AccessCondition cnd, boolean queriedOnly, boolean pre var classFields = registeredFields.computeIfAbsent(declaringClass, _ -> new ConcurrentHashMap<>()); boolean exists = classFields.containsKey(analysisField); boolean shouldRegisterReachabilityHandler = classFields.isEmpty(); - var cndValue = classFields.computeIfAbsent(analysisField, _ -> new ConditionalRuntimeValue<>(RuntimeDynamicAccessMetadata.emptySet(preserved), reflectField)); - if (exists) { - if (!preserved) { - cndValue.getDynamicAccessMetadata().setNotPreserved(); + + ConditionalRuntimeValue cndValue = classFields.compute(analysisField, (_, fieldData) -> { + if (fieldData == null || (fieldData.queriedOnly() && !queriedOnly)) { + /* + * The dynamic access metadata needs to be reset when registering an element as + * accessed + */ + return new RegisteredMemberData<>(new ConditionalRuntimeValue<>(RuntimeDynamicAccessMetadata.emptySet(preserved), reflectField), queriedOnly); + } else { + if (!preserved && fieldData.queriedOnly() == queriedOnly) { + var value = fieldData.member(); + value.getDynamicAccessMetadata().setNotPreserved(); + } + return fieldData; } - } else { + }).member(); + + if (!exists) { registerTypesForField(analysisField, reflectField, queriedOnly); /* @@ -1267,13 +1284,23 @@ public int getEnabledReflectionQueries(Class clazz) { @Override public Map>> getReflectionFields() { assert isSealed(); - return Collections.unmodifiableMap(registeredFields); + return filterRegisteredMemberData(registeredFields); } @Override public Map>> getReflectionExecutables() { assert isSealed(); - return Collections.unmodifiableMap(registeredMethods); + return filterRegisteredMemberData(registeredMethods); + } + + private static Map>> filterRegisteredMemberData(Map>> map) { + /* + * Return only the ConditionalRuntimeValue, the queriedOnly boolean is not relevant once the + * builder is sealed. + */ + return map.entrySet().stream().collect( + Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream().collect( + Collectors.toUnmodifiableMap(Map.Entry::getKey, e2 -> e2.getValue().member())))); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index d18515312061..f9c43e828aff 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -318,7 +318,7 @@ public void afterRegistration(AfterRegistrationAccess access) { * Querying Object members is allowed to enable these accesses on array classes, since those * don't define any additional members. */ - reflectionData.registerClassMetadata(AccessCondition.unconditional(), Object.class); + reflectionData.registerClassMetadata(AccessCondition.unconditional(), Object.class, false); } @Override