From dd0cb60a6ffa6efee50fda42ea392ea47ec1d6d8 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Thu, 23 Feb 2023 09:58:52 +0100 Subject: [PATCH] Throw missing reflection registration errors --- .../org.graalvm.nativeimage/snapshot.sigtest | 34 ++ .../MissingReflectionRegistrationError.java | 142 ++++++ .../nativeimage/hosted/RuntimeReflection.java | 180 +++++++- .../nativeimage/impl/ReflectionRegistry.java | 6 +- .../impl/RuntimeReflectionSupport.java | 35 +- .../pointsto/standalone/StandaloneHost.java | 4 + .../com/oracle/graal/pointsto/api/HostVM.java | 7 + .../pointsto/meta/AnalysisMetaAccess.java | 11 + .../graal/pointsto/meta/AnalysisMethod.java | 3 + .../graal/pointsto/meta/AnalysisUniverse.java | 1 + .../svm/agent/BreakpointInterceptor.java | 67 ++- .../configure/config/ConfigurationType.java | 35 +- .../config/ParserConfigurationAdapter.java | 15 + .../configure/trace/ReflectionProcessor.java | 12 + .../com/oracle/svm/core/SubstrateOptions.java | 3 + .../ReflectionConfigurationParser.java | 18 +- ...ReflectionConfigurationParserDelegate.java | 6 + .../svm/core/heap/dump/HeapDumpWriter.java | 2 +- .../svm/core/hub/ClassForNameSupport.java | 57 +-- .../com/oracle/svm/core/hub/DynamicHub.java | 405 +++++++++++++----- .../MissingReflectionRegistrationUtils.java | 128 ++++++ .../reflect/ReflectionMetadataDecoder.java | 4 + .../target/ReflectionMetadataDecoderImpl.java | 90 +++- .../Target_java_lang_reflect_Constructor.java | 5 +- .../Target_java_lang_reflect_Method.java | 5 +- .../src/com/oracle/svm/hosted/SVMHost.java | 12 + .../annotation/TypeAnnotationValue.java | 3 +- .../config/ConfigurationParserUtils.java | 2 +- .../config/ReflectionRegistryAdapter.java | 185 +++----- .../svm/hosted/config/RegistryAdapter.java | 232 ++++++++++ .../hosted/image/NativeImageCodeCache.java | 109 +++-- .../svm/hosted/meta/HostedUniverse.java | 11 + .../hosted/reflect/ReflectionDataBuilder.java | 238 +++++++++- .../reflect/ReflectionHostedSupport.java | 7 + .../hosted/reflect/ReflectionMetadata.java | 80 ++-- .../ReflectionMetadataEncoderImpl.java | 104 +++-- 36 files changed, 1804 insertions(+), 454 deletions(-) create mode 100644 sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index e612ff3293ecc..ac531e7431e59 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -37,6 +37,15 @@ meth public static <%0 extends java.lang.Enum<{%%0}>> {%%0} valueOf(java.lang.Cl supr java.lang.Object hfds name,ordinal +CLSS public java.lang.Error +cons protected init(java.lang.String,java.lang.Throwable,boolean,boolean) +cons public init() +cons public init(java.lang.String) +cons public init(java.lang.String,java.lang.Throwable) +cons public init(java.lang.Throwable) +supr java.lang.Throwable +hfds serialVersionUID + CLSS public java.lang.Exception cons protected init(java.lang.String,java.lang.Throwable,boolean,boolean) cons public init() @@ -222,6 +231,15 @@ meth public abstract void fatalError() meth public abstract void flush() meth public abstract void log(org.graalvm.nativeimage.c.type.CCharPointer,org.graalvm.word.UnsignedWord) +CLSS public final org.graalvm.nativeimage.MissingReflectionRegistrationError +cons public init(java.lang.String,java.lang.Class,java.lang.Class,java.lang.String,java.lang.Class[]) +meth public java.lang.Class getElementType() +meth public java.lang.Class getDeclaringClass() +meth public java.lang.String getElementName() +meth public java.lang.Class[] getParameterTypes() +supr java.lang.Error +hfds serialVersionUID + CLSS public abstract interface org.graalvm.nativeimage.ObjectHandle intf org.graalvm.word.ComparableWord @@ -1075,10 +1093,26 @@ meth public !varargs static void register(boolean,boolean,java.lang.reflect.Fiel meth public !varargs static void register(boolean,java.lang.reflect.Field[]) anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="21.1") meth public !varargs static void register(java.lang.Class[]) +meth public static void registerClassLookup(java.lang.String) meth public !varargs static void register(java.lang.reflect.Executable[]) +meth public !varargs static void registerMethodLookup(java.lang.Class,java.lang.String,java.lang.Class[]) +meth public !varargs static void registerConstructorLookup(java.lang.Class,java.lang.Class[]) meth public !varargs static void register(java.lang.reflect.Field[]) +meth public static void registerFieldLookup(java.lang.Class,java.lang.String) meth public !varargs static void registerAsQueried(java.lang.reflect.Executable[]) meth public !varargs static void registerForReflectiveInstantiation(java.lang.Class[]) +meth public static void registerAllClasses(java.lang.Class) +meth public static void registerAllDeclaredClasses(java.lang.Class) +meth public static void registerAllConstructors(java.lang.Class) +meth public static void registerAllDeclaredConstructors(java.lang.Class) +meth public static void registerAllFields(java.lang.Class) +meth public static void registerAllDeclaredFields(java.lang.Class) +meth public static void registerAllMethods(java.lang.Class) +meth public static void registerAllDeclaredMethods(java.lang.Class) +meth public static void registerAllNestMembers(java.lang.Class) +meth public static void registerAllPermittedSubclasses(java.lang.Class) +meth public static void registerAllRecordComponents(java.lang.Class) +meth public static void registerAllSigners(java.lang.Class) supr java.lang.Object CLSS public final org.graalvm.nativeimage.hosted.RuntimeResourceAccess 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 new file mode 100644 index 0000000000000..47567d0daac5a --- /dev/null +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023, 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; + +import java.io.Serial; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * This exception is thrown when a reflective query (such as + * {@link Class#getMethod(String, Class[])}) tries to access an element that was not registered + * for reflection in the program. When an element is not registered, the exception will be + * thrown both for elements that exist and elements that do not exist on the given classpath. + *

+ * The purpose of this exception is to easily discover unregistered elements and to assure that all + * reflective operations for registered elements have the expected behaviour. + *

+ * We distinguish between two types of reflective queries: bulk queries and individual queries. + *

    + *
  1. Bulk queries are methods like {@link Class#getFields()} which return a complete list of + * corresponding elements. Those queries need to be explicitly registered for reflection in order to + * be called. If that is not the case, a {@link MissingReflectionRegistrationError} will be + * thrown.
  2. + *
  3. Individual queries are methods like {@link Class#getField(String)} which return a single + * element. Those queries will succeed (or throw the expected {@link ReflectiveOperationException} + * if either the element was individually registered for reflection, or the corresponding bulk query + * was registered for reflection. If that is not the case, a + * {@link MissingReflectionRegistrationError} will be thrown. Some individual queries, like + * {@link Class#forName(String)}, do not have a corresponding bulk query and as such need their + * arguments to be individually registered for reflection in order to behave correctly.
  4. + *
+ * Examples: + *

+ * Registration: {@code "queryAllDeclaredMethods": true}
+ * {@code declaringClass.getDeclaredMethods()} will succeed.
+ * {@code declaringClass.getDeclaredMethod("existingMethod")} will return the expected method.
+ * {@code declaringClass.getDeclaredMethod("nonexistentMethod")} will throw a + * {@link NoSuchMethodException}. + *

+ * Registration: {@code "fields": [{"name": "registeredField"}, {"name": + * "registeredNonexistentField"}]}
+ * {@code declaringClass.getDeclaredFields()} will throw a + * {@link MissingReflectionRegistrationError}.
+ * {@code declaringClass.getField("registeredField")} will return the expected field.
+ * {@code declaringClass.getField("registeredNonexistentField")} will throw a + * {@link NoSuchFieldException}.
+ * {@code declaringClass.getField("unregisteredField")} will throw a + * {@link MissingReflectionRegistrationError}.
+ * {@code declaringClass.getField("unregisteredNonexistentField")} will throw a + * {@link MissingReflectionRegistrationError}.
+ */ +public final class MissingReflectionRegistrationError extends Error { + @Serial private static final long serialVersionUID = 2764341882856270640L; + + private final Class elementType; + + private final Class declaringClass; + + private final String elementName; + + private final Class[] parameterTypes; + + public MissingReflectionRegistrationError(String message, Class elementType, Class declaringClass, String elementName, Class[] parameterTypes) { + super(message); + this.elementType = elementType; + this.declaringClass = declaringClass; + this.elementName = elementName; + this.parameterTypes = parameterTypes; + } + + /** + * @return The type of the element trying to be queried ({@link Class}, {@link Method}, + * {@link Field} or {@link Constructor}), or null if the query is a bulk query (like + * {@link Class#getMethods()}). + */ + public Class getElementType() { + return elementType; + } + + /** + * @return The class on which the missing query was tried, or null on static queries (e.g. + * {@link Class#forName(String)}). + */ + public Class getDeclaringClass() { + return declaringClass; + } + + /** + * @return The name of the queried element, or bulk query method (e.g. {@code "getMethods"}). + */ + public String getElementName() { + return elementName; + } + + /** + * @return The parameter types passed to the query, or null if the query doesn't take parameter + * types as argument. + */ + public Class[] getParameterTypes() { + return parameterTypes; + } +} diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java index c12349d5ac5d8..07ef05fedfffa 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2023, 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 @@ -72,10 +72,21 @@ public static void register(Class... classes) { ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), classes); } + /** + * Makes the provided class available for reflection at run time. A call to + * {@link Class#forName} for the name of the class will return the class (if it exists) or a + * {@link ClassNotFoundException} at run time. + * + * @since 23.0 + */ + public static void registerClassLookup(String className) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerClassLookup(ConfigurationCondition.alwaysTrue(), className); + } + /** * Makes the provided methods available for reflection at run time. The methods will be returned - * by {@link Class#getMethod}, {@link Class#getMethods},and all the other methods on - * {@link Class} that return a single or a list of methods. + * by {@link Class#getMethod}, {@link Class#getDeclaredMethod(String, Class[])}, and all the + * other methods on {@link Class} that return a single method. * * @since 19.0 */ @@ -85,9 +96,9 @@ public static void register(Executable... methods) { /** * Makes the provided methods available for reflection queries at run time. The methods will be - * returned by {@link Class#getMethod}, {@link Class#getMethods}, and all the other methods on - * {@link Class} that return a single or a list of methods, but will not be invocable and will - * not be considered reachable. + * returned by {@link Class#getMethod}, {@link Class#getDeclaredMethod(String, Class[])}, and + * all the other methods on {@link Class} that return a single method, but will not be invocable + * and will not be considered reachable. * * @since 21.3 */ @@ -95,10 +106,37 @@ public static void registerAsQueried(Executable... methods) { ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), true, methods); } + /** + * Makes the provided method available for reflection queries at run time. The method will be + * returned by {@link Class#getMethod}, {@link Class#getDeclaredMethod(String, Class[])}, and + * all the other methods on {@link Class} that return a single method, but will not be invocable + * and will not be considered reachable. If the method doesn't exist a + * {@link NoSuchMethodException} will be thrown when calling these methods at run-time. + * + * @since 23.0 + */ + public static void registerMethodLookup(Class declaringClass, String methodName, Class... parameterTypes) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerMethodLookup(ConfigurationCondition.alwaysTrue(), declaringClass, methodName, parameterTypes); + } + + /** + * Makes the provided constructor available for reflection queries at run time. The constructor + * will be returned by {@link Class#getConstructor}, + * {@link Class#getDeclaredConstructor(Class[])}, and all the other methods on {@link Class} + * that return a single constructor, but will not be invocable and will not be considered + * reachable. If the constructor doesn't exist a {@link NoSuchMethodException} will be thrown + * when calling these methods at run-time. + * + * @since 23.0 + */ + public static void registerConstructorLookup(Class declaringClass, Class... parameterTypes) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerConstructorLookup(ConfigurationCondition.alwaysTrue(), declaringClass, parameterTypes); + } + /** * Makes the provided fields available for reflection at run time. The fields will be returned - * by {@link Class#getField}, {@link Class#getFields},and all the other methods on {@link Class} - * that return a single or a list of fields. + * by {@link Class#getField}, {@link Class#getDeclaredField(String)},and all the other methods + * on {@link Class} that return a single field. * * @since 19.0 */ @@ -106,6 +144,132 @@ public static void register(Field... fields) { ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), false, fields); } + /** + * Makes the provided field available for reflection at run time. The field will be returned by + * {@link Class#getField}, {@link Class#getDeclaredField(String)}, and all the other methods on + * {@link Class} that return a single field. If the field doesn't exist a + * {@link NoSuchFieldException} will be thrown when calling these methods at run-time. + * + * @since 19.0 + */ + public static void registerFieldLookup(Class declaringClass, String fieldName) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerFieldLookup(ConfigurationCondition.alwaysTrue(), declaringClass, fieldName); + } + + /** + * Allows calling {@link Class#getClasses()} on the provided class at run time. + * + * @since 23.0 + */ + public static void registerAllClasses(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllClassesQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + } + + /** + * Allows calling {@link Class#getDeclaredClasses()} on the provided class at run time. + * + * @since 23.0 + */ + public static void registerAllDeclaredClasses(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredClassesQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + } + + /** + * Allows calling {@link Class#getMethods()} on the provided class at run time. The methods will + * also be registered for individual queries. + * + * @since 23.0 + */ + public static void registerAllMethods(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllMethodsQuery(ConfigurationCondition.alwaysTrue(), true, declaringClass); + } + + /** + * Allows calling {@link Class#getDeclaredMethods()} on the provided class at run time. The + * methods will also be registered for individual queries. + * + * @since 23.0 + */ + public static void registerAllDeclaredMethods(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredMethodsQuery(ConfigurationCondition.alwaysTrue(), true, declaringClass); + } + + /** + * Allows calling {@link Class#getConstructors()} on the provided class at run time. The + * constructors will also be registered for individual queries. + * + * @since 23.0 + */ + public static void registerAllConstructors(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllConstructorsQuery(ConfigurationCondition.alwaysTrue(), true, declaringClass); + } + + /** + * Allows calling {@link Class#getDeclaredConstructors()} on the provided class at run time. The + * constructors will also be registered for individual queries. + * + * @since 23.0 + */ + public static void registerAllDeclaredConstructors(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredConstructorsQuery(ConfigurationCondition.alwaysTrue(), true, declaringClass); + } + + /** + * Allows calling {@link Class#getFields()} on the provided class at run time. The fields will + * also be registered for individual queries. + * + * @since 23.0 + */ + public static void registerAllFields(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + } + + /** + * Allows calling {@link Class#getDeclaredFields()} on the provided class at run time. The + * fields will also be registered for individual queries. + * + * @since 23.0 + */ + public static void registerAllDeclaredFields(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + } + + /** + * Allows calling {@link Class#getNestMembers()} on the provided class at run time. + * + * @since 23.0 + */ + public static void registerAllNestMembers(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllNestMembersQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + } + + /** + * Allows calling {@link Class#getPermittedSubclasses()} on the provided class at run time. + * + * @since 23.0 + */ + public static void registerAllPermittedSubclasses(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllPermittedSubclassesQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + } + + /** + * Allows calling {@link Class#getRecordComponents()} on the provided class at run time. + * + * @since 23.0 + */ + public static void registerAllRecordComponents(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllRecordComponentsQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + } + + /** + * Allows calling {@link Class#getSigners()} on the provided class at run time. + * + * @since 23.0 + */ + public static void registerAllSigners(Class declaringClass) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllSignersQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + } + /** * @deprecated Use {@link #register(Field...)} instead. Parameter {@code finalIsWritable} no * longer serves a purpose. diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java index 9ece13ae678f7..4198a895ec067 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2023, 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 @@ -55,8 +55,4 @@ default void register(ConfigurationCondition condition, Class... classes) { void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields); - @SuppressWarnings("unused") - default void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t) { - } - } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java index b4862955d1cc6..416b5fe7cdbd9 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2023, 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 @@ -42,4 +42,37 @@ public interface RuntimeReflectionSupport extends ReflectionRegistry { // needed as reflection-specific ImageSingletons key + void registerAllMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz); + + void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz); + + void registerAllFieldsQuery(ConfigurationCondition condition, Class clazz); + + void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class clazz); + + void registerAllConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz); + + void registerAllDeclaredConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz); + + void registerAllClassesQuery(ConfigurationCondition condition, Class clazz); + + void registerAllDeclaredClassesQuery(ConfigurationCondition condition, Class clazz); + + void registerAllRecordComponentsQuery(ConfigurationCondition condition, Class clazz); + + void registerAllPermittedSubclassesQuery(ConfigurationCondition condition, Class clazz); + + void registerAllNestMembersQuery(ConfigurationCondition condition, Class clazz); + + void registerAllSignersQuery(ConfigurationCondition condition, Class clazz); + + void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t); + + void registerClassLookup(ConfigurationCondition condition, String typeName); + + void registerFieldLookup(ConfigurationCondition condition, Class declaringClass, String fieldName); + + void registerMethodLookup(ConfigurationCondition condition, Class declaringClass, String methodName, Class... parameterTypes); + + void registerConstructorLookup(ConfigurationCondition condition, Class declaringClass, Class... parameterTypes); } diff --git a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/StandaloneHost.java b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/StandaloneHost.java index 36282c6838a4b..ac03dabbbe7e2 100644 --- a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/StandaloneHost.java +++ b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/StandaloneHost.java @@ -80,6 +80,10 @@ public void onTypeReachable(AnalysisType type) { */ } + @Override + public void onTypeInstantiated(AnalysisType newValue) { + } + @Override public GraphBuilderPhase.Instance createGraphBuilderPhase(HostedProviders providers, GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext) { 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 a31bd4b33955e..04a0d4b43b2d1 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 @@ -152,6 +152,13 @@ public void checkType(ResolvedJavaType type, AnalysisUniverse universe) { */ public abstract void onTypeReachable(AnalysisType newValue); + /** + * Run initialization tasks for a newly instantiated {@link AnalysisType}. + * + * @param newValue the type to initialize + */ + public abstract void onTypeInstantiated(AnalysisType newValue); + /** * Check if an {@link AnalysisType} is initialized. */ diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java index 3f3a613f31bc8..4ddcd19579d01 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java @@ -52,6 +52,17 @@ public AnalysisType lookupJavaType(Class clazz) { return (AnalysisType) super.lookupJavaType(clazz); } + @Override + public AnalysisType[] lookupJavaTypes(Class[] classes) { + AnalysisType[] result = new AnalysisType[classes.length]; + + for (int i = 0; i < result.length; ++i) { + result[i] = this.lookupJavaType(classes[i]); + } + + return result; + } + public Optional optionalLookupJavaType(Class clazz) { AnalysisType result = (AnalysisType) getTypeCacheEntry(clazz); if (result != null) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java index 9b8c5a908fd49..fd8155507c0a2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java @@ -97,6 +97,9 @@ public abstract class AnalysisMethod extends AnalysisElement implements WrappedJ private static final AtomicReferenceFieldUpdater isInlinedUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisMethod.class, Object.class, "isInlined"); + public record Signature(String name, AnalysisType[] parameterTypes) { + } + public final ResolvedJavaMethod wrapped; private final int id; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java index bceed7310194e..f436e891dc7b7 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java @@ -692,6 +692,7 @@ public void onFieldAccessed(AnalysisField field) { } public void onTypeInstantiated(AnalysisType type, UsageKind usage) { + hostVM.onTypeInstantiated(type); bb.onTypeInstantiated(type, usage); } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 03990983980b9..2593ec4ac82d7 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -213,40 +213,7 @@ private static boolean forName(JNIEnvironment jni, JNIObjectHandle thread, Break if (className == null) { return false; /* No point in tracing this. */ } - - boolean classLoaderValid = true; - WordPointer classLoaderPtr = StackValue.get(WordPointer.class); - if (bp.method == agent.handles().javaLangClassForName3) { - assert thread.notEqual(nullHandle()) || Support.jvmtiVersion() != JvmtiInterface.JVMTI_VERSION_19 : "JDK-8292657"; - classLoaderValid = (jvmtiFunctions().GetLocalObject().invoke(jvmtiEnv(), thread, 0, 2, classLoaderPtr) == JvmtiError.JVMTI_ERROR_NONE); - } else { - classLoaderPtr.write(nullHandle()); - if (callerClass.notEqual(nullHandle())) { - /* - * NOTE: we use our direct caller class, but this class might be skipped over by - * Class.forName(nameOnly) in its security stackwalk for @CallerSensitive, leading - * to different behavior of our call and the original call. - */ - classLoaderValid = (jvmtiFunctions().GetClassLoader().invoke(jvmtiEnv(), callerClass, classLoaderPtr) == JvmtiError.JVMTI_ERROR_NONE); - } - } - Object result = Tracer.UNKNOWN_VALUE; - if (classLoaderValid) { - /* - * Even if the original call requested class initialization, disable it because - * recursion checks keep us from seeing events of interest during initialization. - */ - int initialize = 0; - Support.callStaticObjectMethodLIL(jni, bp.clazz, agent.handles().javaLangClassForName3, name, initialize, classLoaderPtr.read()); - JNIObjectHandle exception = handleException(jni, true); - /* - * To throw the right exceptions at run time, we need to ensure that the image builder - * sees them, so we trace all calls except those that throw a ClassNotFoundException. - */ - result = exception.equal(nullHandle()) || !jniFunctions().getIsInstanceOf().invoke(jni, exception, agent.handles().javaLangClassNotFoundException); - } - - traceReflectBreakpoint(jni, bp.clazz, nullHandle(), callerClass, bp.specification.methodName, result, state.getFullStackTraceOrNull(), className); + traceReflectBreakpoint(jni, bp.clazz, nullHandle(), callerClass, bp.specification.methodName, null, state.getFullStackTraceOrNull(), className); return true; } @@ -298,10 +265,22 @@ private static boolean getDeclaredClasses(JNIEnvironment jni, JNIObjectHandle th return handleGetClasses(jni, thread, bp, state); } + private static boolean getRecordComponents(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + return handleGetClasses(jni, thread, bp, state); + } + private static boolean getPermittedSubclasses(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { return handleGetClasses(jni, thread, bp, state); } + private static boolean getNestMembers(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + return handleGetClasses(jni, thread, bp, state); + } + + private static boolean getSigners(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + return handleGetClasses(jni, thread, bp, state); + } + private static boolean handleGetClasses(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { JNIObjectHandle callerClass = state.getDirectCallerClass(); JNIObjectHandle self = getReceiver(thread); @@ -332,7 +311,7 @@ private static boolean handleGetField(JNIEnvironment jni, JNIObjectHandle thread declaring = nullHandle(); } } - traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), getClassOrSingleProxyInterface(jni, declaring), callerClass, bp.specification.methodName, result.notEqual(nullHandle()), + traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), getClassOrSingleProxyInterface(jni, declaring), callerClass, bp.specification.methodName, name.notEqual(nullHandle()), state.getFullStackTraceOrNull(), fromJniString(jni, name)); return true; } @@ -395,12 +374,8 @@ private static boolean getConstructor(JNIEnvironment jni, JNIObjectHandle thread JNIObjectHandle callerClass = state.getDirectCallerClass(); JNIObjectHandle self = getReceiver(thread); JNIObjectHandle paramTypesHandle = getObjectArgument(thread, 1); - JNIObjectHandle result = Support.callObjectMethodL(jni, self, bp.method, paramTypesHandle); - if (clearException(jni)) { - result = nullHandle(); - } Object paramTypes = getClassArrayNames(jni, paramTypesHandle); - traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), nullHandle(), callerClass, bp.specification.methodName, nullHandle().notEqual(result), state.getFullStackTraceOrNull(), + traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), nullHandle(), callerClass, bp.specification.methodName, true, state.getFullStackTraceOrNull(), paramTypes); return true; } @@ -431,8 +406,8 @@ private static boolean handleGetMethod(JNIEnvironment jni, JNIObjectHandle threa } String name = fromJniString(jni, nameHandle); Object paramTypes = getClassArrayNames(jni, paramTypesHandle); - traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), getClassOrSingleProxyInterface(jni, declaring), callerClass, bp.specification.methodName, result.notEqual(nullHandle()), - state.getFullStackTraceOrNull(), name, paramTypes); + traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), getClassOrSingleProxyInterface(jni, declaring), callerClass, bp.specification.methodName, + nameHandle.notEqual(nullHandle()), state.getFullStackTraceOrNull(), name, paramTypes); return true; } @@ -1799,8 +1774,14 @@ private interface BreakpointHandler { optionalBrk("java/lang/invoke/MethodType", "fromMethodDescriptorString", "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/invoke/MethodType;", BreakpointInterceptor::methodTypeFromDescriptor), + optionalBrk("java/lang/Class", "getRecordComponents", "()[Ljava/lang/reflect/RecordComponent;", + BreakpointInterceptor::getRecordComponents), optionalBrk("java/lang/Class", "getPermittedSubclasses", "()[Ljava/lang/Class;", - BreakpointInterceptor::getPermittedSubclasses) + BreakpointInterceptor::getPermittedSubclasses), + optionalBrk("java/lang/Class", "getNestMembers", "()[Ljava/lang/Class;", + BreakpointInterceptor::getNestMembers), + optionalBrk("java/lang/Class", "getSigners", "()[Ljava/lang/Object;", + BreakpointInterceptor::getSigners) }; private static boolean allocateInstance(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { 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 87b874c339fa1..4d90efc89f149 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 @@ -92,7 +92,10 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType private Map methods; private boolean allDeclaredClasses; + private boolean allRecordComponents; private boolean allPermittedSubclasses; + private boolean allNestMembers; + private boolean allSigners; private boolean allPublicClasses; private boolean allDeclaredFields; private boolean allPublicFields; @@ -266,7 +269,10 @@ private void removeMethods(ConfigurationType other) { private void setFlagsFromOther(ConfigurationType other, BiPredicate flagPredicate, BiFunction accessCombiner) { allDeclaredClasses = flagPredicate.test(allDeclaredClasses, other.allDeclaredClasses); + allRecordComponents = flagPredicate.test(allRecordComponents, other.allRecordComponents); allPermittedSubclasses = flagPredicate.test(allPermittedSubclasses, other.allPermittedSubclasses); + allNestMembers = flagPredicate.test(allNestMembers, other.allNestMembers); + allSigners = flagPredicate.test(allSigners, other.allSigners); allPublicClasses = flagPredicate.test(allPublicClasses, other.allPublicClasses); allDeclaredFields = flagPredicate.test(allDeclaredFields, other.allDeclaredFields); allPublicFields = flagPredicate.test(allPublicFields, other.allPublicFields); @@ -282,7 +288,7 @@ private boolean isEmpty() { } private boolean allFlagsFalse() { - return !(allDeclaredClasses || allPermittedSubclasses || allPublicClasses || allDeclaredFields || allPublicFields || + return !(allDeclaredClasses || allRecordComponents || allPermittedSubclasses || allNestMembers || allSigners || allPublicClasses || allDeclaredFields || allPublicFields || allDeclaredMethodsAccess != ConfigurationMemberAccessibility.NONE || allPublicMethodsAccess != ConfigurationMemberAccessibility.NONE || allDeclaredConstructorsAccess != ConfigurationMemberAccessibility.NONE || allPublicConstructorsAccess != ConfigurationMemberAccessibility.NONE); } @@ -367,10 +373,22 @@ public synchronized void setAllDeclaredClasses() { allDeclaredClasses = true; } + public synchronized void setAllRecordComponents() { + allRecordComponents = true; + } + public synchronized void setAllPermittedSubclasses() { allPermittedSubclasses = true; } + public synchronized void setAllNestMembers() { + allNestMembers = true; + } + + public synchronized void setAllSigners() { + allSigners = true; + } + public synchronized void setAllPublicClasses() { allPublicClasses = true; } @@ -430,7 +448,10 @@ public synchronized void printJson(JsonWriter writer) throws IOException { optionallyPrintJsonBoolean(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredConstructors"); optionallyPrintJsonBoolean(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicConstructors"); optionallyPrintJsonBoolean(writer, allDeclaredClasses, "allDeclaredClasses"); + optionallyPrintJsonBoolean(writer, allRecordComponents, "allRecordComponents"); optionallyPrintJsonBoolean(writer, allPermittedSubclasses, "allPermittedSubclasses"); + optionallyPrintJsonBoolean(writer, allNestMembers, "allNestMembers"); + optionallyPrintJsonBoolean(writer, allSigners, "allSigners"); optionallyPrintJsonBoolean(writer, allPublicClasses, "allPublicClasses"); optionallyPrintJsonBoolean(writer, allDeclaredMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredMethods"); optionallyPrintJsonBoolean(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicMethods"); @@ -531,10 +552,22 @@ public static boolean haveAllDeclaredClasses(ConfigurationType type) { return type.allDeclaredClasses; } + public static boolean haveAllRecordComponents(ConfigurationType type) { + return type.allRecordComponents; + } + public static boolean haveAllPermittedSubclasses(ConfigurationType type) { return type.allPermittedSubclasses; } + public static boolean haveAllNestMembers(ConfigurationType type) { + return type.allNestMembers; + } + + public static boolean haveAllSigners(ConfigurationType type) { + return type.allSigners; + } + public static boolean haveAllPublicClasses(ConfigurationType type) { return type.allPublicClasses; } 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 2562e45fe102d..62a199c3a15ef 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 @@ -103,11 +103,26 @@ public void registerDeclaredClasses(ConfigurationType type) { type.setAllDeclaredClasses(); } + @Override + public void registerRecordComponents(ConfigurationType type) { + type.setAllRecordComponents(); + } + @Override public void registerPermittedSubclasses(ConfigurationType type) { type.setAllPermittedSubclasses(); } + @Override + public void registerNestMembers(ConfigurationType type) { + type.setAllNestMembers(); + } + + @Override + public void registerSigners(ConfigurationType type) { + type.setAllSigners(); + } + @Override public void registerPublicFields(ConfigurationType type) { type.setAllPublicFields(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index 74d5d4098ca5a..dca5db3a563b4 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -149,10 +149,22 @@ public void processEntry(EconomicMap entry, ConfigurationSet configur configuration.getOrCreateType(condition, clazz).setAllDeclaredClasses(); break; } + case "getRecordComponents": { + configuration.getOrCreateType(condition, clazz).setAllRecordComponents(); + break; + } case "getPermittedSubclasses": { configuration.getOrCreateType(condition, clazz).setAllPermittedSubclasses(); break; } + case "getNestMembers": { + configuration.getOrCreateType(condition, clazz).setAllNestMembers(); + break; + } + case "getSigners": { + configuration.getOrCreateType(condition, clazz).setAllSigners(); + break; + } case "getClasses": { configuration.getOrCreateType(condition, clazz).setAllPublicClasses(); break; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 00af5a6bcc7cd..bad68b19b8a08 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -891,4 +891,7 @@ public Boolean getValueOrDefault(UnmodifiableEconomicMap, Object> v @Option(help = "Instead of abort, only warn if image builder classes are found on the image class-path.", type = OptionType.Debug)// public static final HostedOptionKey AllowDeprecatedBuilderClassesOnImageClasspath = new HostedOptionKey<>(false); + @Option(help = "Throw Native Image-specific exceptions when encountering an unregistered reflection call.", type = OptionType.User)// + public static final HostedOptionKey ThrowMissingRegistrationErrors = new HostedOptionKey<>(false); + } 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 471eb18c50560..a952e1a348c22 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 @@ -48,7 +48,8 @@ public final class ReflectionConfigurationParser extends ConfigurationParser private final ReflectionConfigurationParserDelegate delegate; private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", - "allDeclaredClasses", "allPermittedSubclasses", "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, + "allDeclaredClasses", "allRecordComponents", "allPermittedSubclasses", "allNestMembers", "allSigners", + "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated"); public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate) { @@ -132,11 +133,26 @@ private void parseClass(EconomicMap data) { delegate.registerDeclaredClasses(clazz); } break; + case "allRecordComponents": + if (asBoolean(value, "allRecordComponents")) { + delegate.registerRecordComponents(clazz); + } + break; case "allPermittedSubclasses": if (asBoolean(value, "allPermittedSubclasses")) { delegate.registerPermittedSubclasses(clazz); } break; + case "allNestMembers": + if (asBoolean(value, "allNestMembers")) { + delegate.registerNestMembers(clazz); + } + break; + case "allSigners": + if (asBoolean(value, "allSigners")) { + delegate.registerSigners(clazz); + } + break; case "allPublicClasses": if (asBoolean(value, "allPublicClasses")) { delegate.registerPublicClasses(clazz); 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 6440147fe11ef..7cbce8f012f54 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 @@ -42,8 +42,14 @@ public interface ReflectionConfigurationParserDelegate { void registerDeclaredClasses(T type); + void registerRecordComponents(T type); + void registerPermittedSubclasses(T type); + void registerNestMembers(T type); + + void registerSigners(T type); + void registerPublicFields(T type); void registerDeclaredFields(T type); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java index fed71c167c924..3f988fea77b1c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java @@ -654,7 +654,7 @@ private void writeClassDumpRecord(ClassInfo classInfo) { writeInt(DUMMY_STACK_TRACE_ID); writeClassId(clazz.getSuperclass()); writeObjectId(getClassLoader(clazz)); - writeObjectId(clazz.getSigners()); + writeObjectId(null); // signers writeObjectId(null); // protection domain writeObjectId(null); // reserved field writeObjectId(null); // reserved field diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index 67c55f445b972..76bdeb6945420 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -24,26 +24,21 @@ */ package com.oracle.svm.core.hub; +import static com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils.throwMissingRegistrationErrors; + import org.graalvm.collections.EconomicMap; -import org.graalvm.compiler.options.Option; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; -import com.oracle.svm.core.option.HostedOptionKey; -import com.oracle.svm.core.util.ExitStatus; +import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.VMError; @AutomaticallyRegisteredImageSingleton public final class ClassForNameSupport { - public static class Options { - @Option(help = "Enable termination caused by missing metadata.")// - public static final HostedOptionKey ExitOnUnknownClassLoadingFailure = new HostedOptionKey<>(false); - } - static ClassForNameSupport singleton() { return ImageSingletons.lookup(ClassForNameSupport.class); } @@ -51,6 +46,8 @@ static ClassForNameSupport singleton() { /** The map used to collect registered classes. */ private final EconomicMap knownClasses = ImageHeapMap.create(); + private static final Object NEGATIVE_QUERY = new Object(); + @Platforms(Platform.HOSTED_ONLY.class) public static void registerClass(Class clazz) { assert !clazz.isPrimitive() : "primitive classes cannot be looked up by name"; @@ -67,6 +64,11 @@ public static void registerExceptionForClass(String className, Throwable t) { singleton().knownClasses.put(className, t); } + @Platforms(Platform.HOSTED_ONLY.class) + public static void registerNegativeQuery(String className) { + singleton().knownClasses.put(className, NEGATIVE_QUERY); + } + public static Class forNameOrNull(String className, ClassLoader classLoader) { try { return forName(className, classLoader, true); @@ -84,6 +86,9 @@ private static Class forName(String className, ClassLoader classLoader, boole return null; } Object result = singleton().knownClasses.get(className); + if (result == NEGATIVE_QUERY) { + result = new ClassNotFoundException(className); + } if (result == null) { result = PredefinedClassesSupport.getLoadedForNameOrNull(className, classLoader); } @@ -91,29 +96,31 @@ private static Class forName(String className, ClassLoader classLoader, boole // TODO rewrite stack traces (GR-42813) if (result instanceof Class) { return (Class) result; - } else if (returnNullOnException && (result instanceof Throwable || result == null)) { - return null; + } else if (result instanceof Throwable) { + if (returnNullOnException) { + return null; + } + + if (result instanceof Error) { + throw (Error) result; + } else if (result instanceof ClassNotFoundException) { + throw (ClassNotFoundException) result; + } } else if (result == null) { - if (ClassForNameSupport.Options.ExitOnUnknownClassLoadingFailure.getValue()) { - terminateUnconfigured(className); + if (throwMissingRegistrationErrors()) { + throw MissingReflectionRegistrationUtils.forClass(className); + } + + if (returnNullOnException) { + return null; + } else { + throw new ClassNotFoundException(className); } - throw new ClassNotFoundException(className); - } else if (result instanceof Error) { - throw (Error) result; - } else if (result instanceof ClassNotFoundException) { - throw (ClassNotFoundException) result; - } else { - throw VMError.shouldNotReachHere("Class.forName result should be Class, ClassNotFoundException or Error: " + result); } + throw VMError.shouldNotReachHere("Class.forName result should be Class, ClassNotFoundException or Error: " + result); } public static int count() { return singleton().knownClasses.size(); } - - private static void terminateUnconfigured(String className) { - System.out.println("Missing metadata error: Unable to process Class.forName invocation for class name " + className); - new ClassNotFoundException(className).printStackTrace(System.out); - System.exit(ExitStatus.MISSING_METADATA.getValue()); - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index f319d4a66d00b..f2f7993d6ae48 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -24,7 +24,21 @@ */ package com.oracle.svm.core.hub; +import static com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils.throwMissingRegistrationErrors; import static com.oracle.svm.core.reflect.ReflectionMetadataDecoder.NO_DATA; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_CLASSES_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_CONSTRUCTORS_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_DECLARED_CLASSES_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_DECLARED_CONSTRUCTORS_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_DECLARED_FIELDS_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_DECLARED_METHODS_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_FIELDS_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_METHODS_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_NEST_MEMBERS_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_PERMITTED_SUBCLASSES_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_RECORD_COMPONENTS_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_SIGNERS_FLAG; +import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.CLASS_ACCESS_FLAGS_MASK; import java.io.InputStream; import java.io.Serializable; @@ -45,6 +59,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.net.URL; +import java.security.PrivilegedAction; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; @@ -85,6 +100,7 @@ import com.oracle.svm.core.jdk.JDK19OrLater; import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.reflect.ReflectionMetadataDecoder; import com.oracle.svm.core.reflect.ReflectionMetadataDecoder.ConstructorDescriptor; import com.oracle.svm.core.reflect.ReflectionMetadataDecoder.FieldDescriptor; @@ -97,6 +113,7 @@ import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; import jdk.internal.misc.Unsafe; +import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; import sun.reflect.annotation.AnnotationType; @@ -497,13 +514,25 @@ public String getSignature() { } @Platforms(Platform.HOSTED_ONLY.class) - public void setHubMetadata(int enclosingMethodInfoIndex, int annotationsIndex, int typeAnnotationsIndex, int classesEncodingIndex, int permittedSubclassesEncodingIndex) { - this.hubMetadata = new DynamicHubMetadata(enclosingMethodInfoIndex, annotationsIndex, typeAnnotationsIndex, classesEncodingIndex, permittedSubclassesEncodingIndex); + public void setHubMetadata(int enclosingMethodInfoIndex, int annotationsIndex, int typeAnnotationsIndex, int classesEncodingIndex, int permittedSubclassesEncodingIndex, + int nestMembersEncodingIndex, int signersEncodingIndex) { + this.hubMetadata = new DynamicHubMetadata(enclosingMethodInfoIndex, annotationsIndex, typeAnnotationsIndex, classesEncodingIndex, permittedSubclassesEncodingIndex, nestMembersEncodingIndex, + signersEncodingIndex); } @Platforms(Platform.HOSTED_ONLY.class) - public void setReflectionMetadata(int fieldsEncodingIndex, int methodsEncodingIndex, int constructorsEncodingIndex, int recordComponentsEncodingIndex, int classAccessFlags) { - this.reflectionMetadata = new ReflectionMetadata(fieldsEncodingIndex, methodsEncodingIndex, constructorsEncodingIndex, recordComponentsEncodingIndex, classAccessFlags); + public void setReflectionMetadata(int fieldsEncodingIndex, int methodsEncodingIndex, int constructorsEncodingIndex, int recordComponentsEncodingIndex, int classFlags) { + this.reflectionMetadata = new ReflectionMetadata(fieldsEncodingIndex, methodsEncodingIndex, constructorsEncodingIndex, recordComponentsEncodingIndex, classFlags); + } + + private void checkClassFlag(int mask, String methodName) { + if (throwMissingRegistrationErrors() && !isClassFlagSet(mask)) { + throw MissingReflectionRegistrationUtils.forBulkQuery(DynamicHub.toClass(this), methodName); + } + } + + private boolean isClassFlagSet(int mask) { + return (reflectionMetadata != null && (reflectionMetadata.classFlags & mask) != 0); } /** Executed at runtime. */ @@ -695,7 +724,7 @@ public int getModifiers() { } public int getClassAccessFlags() { - return reflectionMetadata != null ? reflectionMetadata.classAccessFlags : modifiers; + return reflectionMetadata != null ? (reflectionMetadata.classFlags & CLASS_ACCESS_FLAGS_MASK) : modifiers; } @Substitute @@ -935,71 +964,193 @@ public T[] getAnnotationsByType(Class annotationClass) // Checkstyle: disallow direct annotation access - @KeepOriginal - private native Field[] getFields(); + @Substitute + private Field[] getFields() { + checkClassFlag(ALL_FIELDS_FLAG, "getFields"); + return copyFields(privateGetPublicFields()); + } - @KeepOriginal - private native Method[] getMethods(); + @Substitute + @CallerSensitive + public Method[] getMethods() throws SecurityException { + checkClassFlag(ALL_METHODS_FLAG, "getMethods"); + return copyMethods(privateGetPublicMethods()); + } - @KeepOriginal - private native Constructor[] getConstructors(); + @Substitute + private Constructor[] getConstructors() { + checkClassFlag(ALL_CONSTRUCTORS_FLAG, "getConstructors"); + return copyConstructors(privateGetDeclaredConstructors(true)); + } @Substitute public Field getField(String fieldName) throws NoSuchFieldException, SecurityException { Objects.requireNonNull(fieldName); - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true); - } Field field = getField0(fieldName); - if (field == null || ImageSingletons.lookup(ReflectionMetadataDecoder.class).isHiding(field.getModifiers())) { + checkField(fieldName, field, true); + return getReflectionFactory().copyField(field); + } + + private void checkField(String fieldName, Field field, boolean publicOnly) throws NoSuchFieldException { + boolean throwMissingErrors = throwMissingRegistrationErrors(); + boolean noSuchField = false; + boolean missingRegistration = false; + if (field == null) { + if (throwMissingErrors) { + if (isClassFlagSet(ALL_DECLARED_FIELDS_FLAG) || (publicOnly && isClassFlagSet(ALL_FIELDS_FLAG))) { + /* + * If getDeclaredFields (or getFields for a public field) is registered, we know + * for sure that the field does indeed not exist if we don't find it. + */ + noSuchField = true; + } else { + missingRegistration = true; + } + } else { + noSuchField = true; + } + } else { + ReflectionMetadataDecoder decoder = ImageSingletons.lookup(ReflectionMetadataDecoder.class); + int fieldModifiers = field.getModifiers(); + if (decoder.isNegative(fieldModifiers)) { + noSuchField = true; + } else if (decoder.isHiding(fieldModifiers)) { + if (throwMissingErrors) { + missingRegistration = true; + } else { + noSuchField = true; + } + } + } + VMError.guarantee(!missingRegistration || !noSuchField, "Either a MissingRegistrationError or a NoSuchFieldException should be thrown, not both"); + if (missingRegistration) { + throw MissingReflectionRegistrationUtils.forField(DynamicHub.toClass(this), fieldName); + } else if (noSuchField) { throw new NoSuchFieldException(fieldName); } - return getReflectionFactory().copyField(field); } - @KeepOriginal - private native Method getMethod(@SuppressWarnings("hiding") String name, Class... parameterTypes) throws NoSuchMethodException; + @Substitute + private Method getMethod(String methodName, Class... parameterTypes) throws NoSuchMethodException { + Objects.requireNonNull(methodName); + Method method = getMethod0(methodName, parameterTypes); + checkMethod(methodName, parameterTypes, method, true); + return getReflectionFactory().copyMethod(method); + } + + private void checkMethod(String methodName, Class[] parameterTypes, Executable method, boolean publicOnly) throws NoSuchMethodException { + boolean throwMissingErrors = throwMissingRegistrationErrors(); + boolean noSuchMethod = false; + boolean missingRegistration = false; + if (method == null) { + if (throwMissingErrors) { + if (isClassFlagSet(ALL_DECLARED_METHODS_FLAG) || (publicOnly && isClassFlagSet(ALL_METHODS_FLAG))) { + /* + * If getDeclaredMethods (or getMethods for a public method) is registered, we + * know for sure that the method does indeed not exist if we don't find it. + */ + noSuchMethod = true; + } else { + missingRegistration = true; + } + } else { + noSuchMethod = true; + } + } else { + ReflectionMetadataDecoder decoder = ImageSingletons.lookup(ReflectionMetadataDecoder.class); + int methodModifiers = method.getModifiers(); + if (decoder.isNegative(methodModifiers)) { + noSuchMethod = true; + } else if (decoder.isHiding(methodModifiers)) { + if (throwMissingErrors) { + missingRegistration = true; + } else { + noSuchMethod = true; + } + } + } + VMError.guarantee(!missingRegistration || !noSuchMethod, "Either a MissingRegistrationError or a NoSuchMethodException should be thrown, not both"); + if (missingRegistration) { + throw MissingReflectionRegistrationUtils.forMethod(DynamicHub.toClass(this), methodName, parameterTypes); + } else if (noSuchMethod) { + throw new NoSuchMethodException(methodToString(methodName, parameterTypes)); + } + } @KeepOriginal private native Constructor getConstructor(Class... parameterTypes); - @KeepOriginal - private native Class[] getDeclaredClasses(); + @Substitute + public Class[] getDeclaredClasses() throws SecurityException { + checkClassFlag(ALL_DECLARED_CLASSES_FLAG, "getDeclaredClasses"); + return getDeclaredClasses0(); + } - @KeepOriginal - private native Class[] getClasses(); + @Substitute + @SuppressWarnings("deprecation") + public Class[] getClasses() { + checkClassFlag(ALL_CLASSES_FLAG, "getClasses"); - @KeepOriginal - private native Field[] getDeclaredFields(); + // Privileged so this implementation can look at DECLARED classes, + // something the caller might not have privilege to do. The code here + // is allowed to look at DECLARED classes because (1) it does not hand + // out anything other than public members and (2) public member access + // has already been ok'd by the SecurityManager. - @KeepOriginal - private native Method[] getDeclaredMethods(); + return java.security.AccessController.doPrivileged( + (PrivilegedAction[]>) () -> { + List> list = new ArrayList<>(); + DynamicHub currentClass = DynamicHub.this; + while (currentClass != null) { + for (Class m : currentClass.getDeclaredClasses0()) { + if (Modifier.isPublic(m.getModifiers())) { + list.add(m); + } + } + currentClass = currentClass.getSuperHub(); + } + return list.toArray(new Class[0]); + }); + } - @KeepOriginal - private native Constructor[] getDeclaredConstructors(); + @Substitute + private Field[] getDeclaredFields() { + checkClassFlag(ALL_DECLARED_FIELDS_FLAG, "getDeclaredFields"); + return copyFields(privateGetDeclaredFields(false)); + } + + @Substitute + @CallerSensitive + public Method[] getDeclaredMethods() throws SecurityException { + checkClassFlag(ALL_DECLARED_METHODS_FLAG, "getDeclaredMethods"); + return copyMethods(privateGetDeclaredMethods(false)); + } + + @Substitute + private Constructor[] getDeclaredConstructors() { + checkClassFlag(ALL_DECLARED_CONSTRUCTORS_FLAG, "getDeclaredConstructors"); + return copyConstructors(privateGetDeclaredConstructors(false)); + } /** - * @see #filterHidingFields(Field...) + * @see #filterFields(Field...) */ @Substitute public Field getDeclaredField(String fieldName) throws NoSuchFieldException, SecurityException { Objects.requireNonNull(fieldName); - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true); - } Field field = searchFields(privateGetDeclaredFields(false), fieldName); - if (field == null || ImageSingletons.lookup(ReflectionMetadataDecoder.class).isHiding(field.getModifiers())) { - throw new NoSuchFieldException(fieldName); - } + checkField(fieldName, field, false); return getReflectionFactory().copyField(field); } - @KeepOriginal - private native Method getDeclaredMethod(@SuppressWarnings("hiding") String name, Class... parameterTypes); + @Substitute + @CallerSensitive + public Method getDeclaredMethod(String methodName, Class... parameterTypes) throws NoSuchMethodException, SecurityException { + Objects.requireNonNull(methodName); + Method method = searchMethods(privateGetDeclaredMethods(false), methodName, parameterTypes); + checkMethod(methodName, parameterTypes, method, false); + return getReflectionFactory().copyMethod(method); + } @KeepOriginal private native Constructor getDeclaredConstructor(Class... parameterTypes); @@ -1026,6 +1177,7 @@ public Field getDeclaredField(String fieldName) throws NoSuchFieldException, Sec @Substitute @TargetElement(onlyWith = JDK17OrLater.class) private Target_java_lang_reflect_RecordComponent[] getRecordComponents0() { + checkClassFlag(ALL_RECORD_COMPONENTS_FLAG, "getRecordComponents"); if (reflectionMetadata == null || reflectionMetadata.recordComponentsEncodingIndex == NO_DATA) { /* See ReflectionDataBuilder.buildRecordComponents() for details. */ throw VMError.unsupportedFeature("Record components not available for record class " + getTypeName() + ". " + @@ -1058,35 +1210,33 @@ private static ReflectionFactory getReflectionFactory() { @KeepOriginal private static native Field searchFields(Field[] fields, String name); - /** - * @see #filterHidingMethods(Method...) - */ + @KeepOriginal + private static native Method searchMethods(Method[] allMethods, String name, Class[] parameterTypes); + @Substitute - private static Method searchMethods(Method[] allMethods, String name, Class[] parameterTypes) { - Method[] methods = filterHidingMethods(allMethods); + private Constructor getConstructor0(Class[] parameterTypes, int which) throws NoSuchMethodException { ReflectionFactory fact = getReflectionFactory(); - Method res = null; - for (Method m : methods) { - if (m.getName().equals(name) && arrayContentsEq(parameterTypes, fact.getExecutableSharedParameterTypes(m)) && - (res == null || (res.getReturnType() != m.getReturnType() && res.getReturnType().isAssignableFrom(m.getReturnType())))) { - res = m; + Constructor[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC)); + Constructor candidate = null; + for (Constructor constructor : constructors) { + if (arrayContentsEq(parameterTypes, + fact.getExecutableSharedParameterTypes(constructor))) { + candidate = constructor; } } - return res; + checkMethod("", parameterTypes, candidate, which == Member.PUBLIC); + return candidate; } - @KeepOriginal - private native Constructor getConstructor0(Class[] parameterTypes, int which); - @KeepOriginal private static native boolean arrayContentsEq(Object[] a1, Object[] a2); /** - * @see #filterHidingFields(Field...) + * @see #filterFields(Field...) */ @Substitute private static Field[] copyFields(Field[] original) { - Field[] arg = filterHidingFields(original); + Field[] arg = filterFields(original); Field[] out = new Field[arg.length]; ReflectionFactory fact = getReflectionFactory(); for (int i = 0; i < arg.length; i++) { @@ -1096,11 +1246,11 @@ private static Field[] copyFields(Field[] original) { } /** - * @see #filterHidingMethods(Method...) + * @see #filterMethods(Method...) */ @Substitute private static Method[] copyMethods(Method[] original) { - Method[] arg = filterHidingMethods(original); + Method[] arg = filterMethods(original); Method[] out = new Method[arg.length]; ReflectionFactory fact = getReflectionFactory(); for (int i = 0; i < arg.length; i++) { @@ -1109,8 +1259,19 @@ private static Method[] copyMethods(Method[] original) { return out; } - @KeepOriginal - private static native Constructor[] copyConstructors(Constructor[] arg); + /** + * @see #filterConstructors(Constructor[]) + */ + @Substitute + private static Constructor[] copyConstructors(Constructor[] original) { + Constructor[] arg = filterConstructors(original); + Constructor[] out = new Constructor[arg.length]; + ReflectionFactory fact = getReflectionFactory(); + for (int i = 0; i < arg.length; i++) { + out[i] = fact.copyConstructor(arg[i]); + } + return out; + } @KeepOriginal @Override @@ -1241,7 +1402,14 @@ public String toString() { @Substitute public Object[] getSigners() { - return null; + if (isPrimitive()) { + return null; + } + checkClassFlag(ALL_SIGNERS_FLAG, "getSigners"); + if (hubMetadata == null || hubMetadata.signersEncodingIndex == NO_DATA) { + return null; + } + return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseObjects(hubMetadata.signersEncodingIndex); } @Substitute @@ -1296,12 +1464,14 @@ private String getSimpleBinaryName0() { } /** - * @see #filterHidingMethods(Method...) + * @see #filterMethods(Method...) */ @Substitute // @SuppressWarnings({"unused"}) List getDeclaredPublicMethods(String methodName, Class... parameterTypes) { - Method[] methods = filterHidingMethods(privateGetDeclaredMethods(/* publicOnly */ true)); + checkClassFlag(ALL_METHODS_FLAG | ALL_DECLARED_METHODS_FLAG, "getMethods or getDeclaredMethods"); + + Method[] methods = filterMethods(privateGetDeclaredMethods(/* publicOnly */ true)); ReflectionFactory factory = getReflectionFactory(); List result = new ArrayList<>(); for (Method method : methods) { @@ -1322,19 +1492,8 @@ public boolean isNestmateOf(Class c) { return nestHost == DynamicHub.fromClass(c).nestHost; } - @Substitute - private Class[] getNestMembers() { - /* - * Supporting all nest members is not as easy as supporting only the nest host. It is not - * enough to just preserve the result of Class.getNestMembers() returned during image - * generation. This would significantly worsen the static analysis quality, because it would - * make all nest members reachable if only a single class of the nest is reachable. A full - * solution would need to filter the nest members based on reachability, i.e., only add nest - * members when they are reachable by the static analysis. If necessary, this can be - * implemented though. - */ - throw VMError.unsupportedFeature("Class.getNestMembers is not supported yet"); - } + @KeepOriginal + public native Class[] getNestMembers(); @Substitute @TargetElement(onlyWith = JDK17OrLater.class) @@ -1347,6 +1506,12 @@ public DynamicHub componentType() { @TargetElement(onlyWith = JDK17OrLater.class) @Override public DynamicHub arrayType() { + if (toClass(this) == void.class) { + throw new UnsupportedOperationException(new IllegalArgumentException()); + } + if (arrayHub == null) { + throw MissingReflectionRegistrationUtils.forClass(getTypeName() + "[]"); + } return arrayHub; } @@ -1490,8 +1655,18 @@ private Class[] getDeclaredClasses0() { @Delete private native Class getNestHost0(); - @Delete - private native Class[] getNestMembers0(); + @Substitute + private Class[] getNestMembers0() { + checkClassFlag(ALL_NEST_MEMBERS_FLAG, "getNestMembers"); + if (hubMetadata == null || hubMetadata.nestMembersEncodingIndex == NO_DATA) { + return new Class[]{DynamicHub.toClass(this)}; + } + Class[] nestMembers = ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseClasses(hubMetadata.nestMembersEncodingIndex); + for (Class clazz : nestMembers) { + PredefinedClassesSupport.throwIfUnresolvable(clazz, getClassLoader0()); + } + return nestMembers; + } @Delete private native String initClassName(); @@ -1532,9 +1707,13 @@ private native Target_java_lang_Class_ReflectionData newReflectionData(So @Substitute @TargetElement(onlyWith = JDK17OrLater.class) private Class[] getPermittedSubclasses0() { - if (hubMetadata == null || hubMetadata.permittedSubclassesEncodingIndex == NO_DATA) { + if (!isSealed()) { return null; } + checkClassFlag(ALL_PERMITTED_SUBCLASSES_FLAG, "getPermittedSubclasses"); + if (hubMetadata == null || hubMetadata.permittedSubclassesEncodingIndex == NO_DATA) { + return new Class[0]; + } Class[] permittedSubclasses = ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseClasses(hubMetadata.permittedSubclassesEncodingIndex); for (Class clazz : permittedSubclasses) { PredefinedClassesSupport.throwIfUnresolvable(clazz, getClassLoader0()); @@ -1571,30 +1750,45 @@ private Class[] getPermittedSubclasses0() { native boolean casAnnotationType(AnnotationType oldType, AnnotationType newType); /* - * We need to filter out hiding elements at the last moment. This ensures that the JDK internals - * see them as regular methods and fields and ensure their visibility is correct, but they - * should not be returned to application code. + * We need to filter out hiding and negative elements at the last moment. This ensures that the + * JDK internals see them as regular methods and fields and ensure their visibility is correct, + * but they should not be returned to application code. */ - private static Field[] filterHidingFields(Field... fields) { + private static Field[] filterFields(Field... fields) { List filtered = new ArrayList<>(); + ReflectionMetadataDecoder decoder = ImageSingletons.lookup(ReflectionMetadataDecoder.class); for (Field field : fields) { - if (!ImageSingletons.lookup(ReflectionMetadataDecoder.class).isHiding(field.getModifiers())) { + int modifiers = field.getModifiers(); + if (!decoder.isHiding(modifiers) && !decoder.isNegative(modifiers)) { filtered.add(field); } } return filtered.toArray(new Field[0]); } - private static Method[] filterHidingMethods(Method... methods) { + private static Method[] filterMethods(Method... methods) { List filtered = new ArrayList<>(); + ReflectionMetadataDecoder decoder = ImageSingletons.lookup(ReflectionMetadataDecoder.class); for (Method method : methods) { - if (!ImageSingletons.lookup(ReflectionMetadataDecoder.class).isHiding(method.getModifiers())) { + int modifiers = method.getModifiers(); + if (!decoder.isHiding(modifiers) && !decoder.isNegative(modifiers)) { filtered.add(method); } } return filtered.toArray(new Method[0]); } + private static Constructor[] filterConstructors(Constructor... constructors) { + List> filtered = new ArrayList<>(); + ReflectionMetadataDecoder decoder = ImageSingletons.lookup(ReflectionMetadataDecoder.class); + for (Constructor constructor : constructors) { + if (!decoder.isNegative(constructor.getModifiers())) { + filtered.add(constructor); + } + } + return filtered.toArray(new Constructor[0]); + } + public void setJrfEventConfiguration(Object configuration) { companion.setJfrEventConfiguration(configuration); } @@ -1674,12 +1868,19 @@ private static final class DynamicHubMetadata { @TargetElement(onlyWith = JDK17OrLater.class)// final int permittedSubclassesEncodingIndex; - private DynamicHubMetadata(int enclosingMethodInfoIndex, int annotationsIndex, int typeAnnotationsIndex, int classesEncodingIndex, int permittedSubclassesEncodingIndex) { + final int nestMembersEncodingIndex; + + final int signersEncodingIndex; + + private DynamicHubMetadata(int enclosingMethodInfoIndex, int annotationsIndex, int typeAnnotationsIndex, int classesEncodingIndex, int permittedSubclassesEncodingIndex, + int nestMembersEncodingIndex, int signersEncodingIndex) { this.enclosingMethodInfoIndex = enclosingMethodInfoIndex; this.annotationsIndex = annotationsIndex; this.typeAnnotationsIndex = typeAnnotationsIndex; this.classesEncodingIndex = classesEncodingIndex; this.permittedSubclassesEncodingIndex = permittedSubclassesEncodingIndex; + this.nestMembersEncodingIndex = nestMembersEncodingIndex; + this.signersEncodingIndex = signersEncodingIndex; } } @@ -1693,14 +1894,14 @@ private static final class ReflectionMetadata { @TargetElement(onlyWith = JDK17OrLater.class)// final int recordComponentsEncodingIndex; - final int classAccessFlags; + final int classFlags; - private ReflectionMetadata(int fieldsEncodingIndex, int methodsEncodingIndex, int constructorsEncodingIndex, int recordComponentsEncodingIndex, int classAccessFlags) { + private ReflectionMetadata(int fieldsEncodingIndex, int methodsEncodingIndex, int constructorsEncodingIndex, int recordComponentsEncodingIndex, int classFlags) { this.fieldsEncodingIndex = fieldsEncodingIndex; this.methodsEncodingIndex = methodsEncodingIndex; this.constructorsEncodingIndex = constructorsEncodingIndex; this.recordComponentsEncodingIndex = recordComponentsEncodingIndex; - this.classAccessFlags = classAccessFlags; + this.classFlags = classFlags; } } @@ -1814,28 +2015,6 @@ final class Target_java_lang_Class_AnnotationData { @TargetClass(className = "java.lang.PublicMethods", innerClass = "MethodList") final class Target_java_lang_PublicMethods_MethodList { - @Alias // - Method method; - - @Alias // - Target_java_lang_PublicMethods_MethodList next; - - @Substitute - Method getMostSpecific() { - Method m = method; - Class rt = m.getReturnType(); - for (Target_java_lang_PublicMethods_MethodList ml = next; ml != null; ml = ml.next) { - Method m2 = ml.method; - Class rt2 = m2.getReturnType(); - if (rt2 != rt && rt.isAssignableFrom(rt2)) { - // found more specific return type - m = m2; - rt = rt2; - } - } - /* Filter out hiding methods after the retursive lookup is done */ - return ImageSingletons.lookup(ReflectionMetadataDecoder.class).isHiding(m.getModifiers()) ? null : m; - } } @TargetClass(className = "java.lang.Class", innerClass = "Atomic") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java new file mode 100644 index 0000000000000..bb3072cefed13 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022, 2022, 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 java.io.Serial; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.StringJoiner; + +import org.graalvm.compiler.options.Option; +import org.graalvm.nativeimage.MissingReflectionRegistrationError; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.util.ExitStatus; + +public final class MissingReflectionRegistrationUtils { + public static class Options { + @Option(help = "Enable termination caused by missing metadata.")// + public static final HostedOptionKey ExitOnMissingReflectionRegistration = new HostedOptionKey<>(false); + + @Option(help = "Simulate exiting the program with an exception instead of calling System.exit() (for testing)")// + public static final HostedOptionKey ExitWithExceptionOnMissingReflectionRegistration = new HostedOptionKey<>(false); + } + + public static boolean throwMissingRegistrationErrors() { + return SubstrateOptions.ThrowMissingRegistrationErrors.getValue(); + } + + public static MissingReflectionRegistrationError forClass(String className) { + MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access class", className), + Class.class, null, className, null); + if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) { + exitOnMissingMetadata(exception); + } + return exception; + } + + public static MissingReflectionRegistrationError forField(Class declaringClass, String fieldName) { + MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access field", + declaringClass.getTypeName() + "#" + fieldName), + Field.class, declaringClass, fieldName, null); + if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) { + exitOnMissingMetadata(exception); + } + return exception; + } + + public static MissingReflectionRegistrationError forMethod(Class declaringClass, String methodName, Class[] paramTypes) { + StringJoiner paramTypeNames = new StringJoiner(", ", "(", ")"); + for (Class paramType : paramTypes) { + paramTypeNames.add(paramType.getTypeName()); + } + MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access method", + declaringClass.getTypeName() + "#" + methodName + paramTypeNames), + Method.class, declaringClass, methodName, paramTypes); + if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) { + exitOnMissingMetadata(exception); + } + return exception; + } + + public static MissingReflectionRegistrationError forQueriedOnlyExecutable(Executable executable) { + MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("invoke method", executable.toString()), + executable.getClass(), executable.getDeclaringClass(), executable.getName(), executable.getParameterTypes()); + if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) { + exitOnMissingMetadata(exception); + } + return exception; + } + + public static MissingReflectionRegistrationError forBulkQuery(Class declaringClass, String methodName) { + MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access", + declaringClass.getTypeName() + "." + methodName + "()"), + null, declaringClass, methodName, null); + if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) { + exitOnMissingMetadata(exception); + } + return exception; + } + + private static String errorMessage(String failedAction, String elementDescriptor) { + return "The program tried to reflectively " + failedAction + " " + elementDescriptor + + " without it being registered for runtime reflection. Add it to the reflection metadata to solve this problem. " + + "See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection for help."; + } + + private static void exitOnMissingMetadata(MissingReflectionRegistrationError exception) { + if (Options.ExitWithExceptionOnMissingReflectionRegistration.getValue()) { + throw new ExitException(exception); + } else { + exception.printStackTrace(System.out); + System.exit(ExitStatus.MISSING_METADATA.getValue()); + } + } + + public static final class ExitException extends Error { + @Serial// + private static final long serialVersionUID = -3638940737396726143L; + + private ExitException(Throwable cause) { + super(cause); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionMetadataDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionMetadataDecoder.java index 7c1b821603ec4..3b79c10e0180f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionMetadataDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionMetadataDecoder.java @@ -51,6 +51,8 @@ public interface ReflectionMetadataDecoder { Target_java_lang_reflect_RecordComponent[] parseRecordComponents(DynamicHub declaringType, int index); + Object[] parseObjects(int index); + Parameter[] parseReflectParameters(Executable executable, byte[] encoding); Object[] parseEnclosingMethod(int index); @@ -59,6 +61,8 @@ public interface ReflectionMetadataDecoder { boolean isHiding(int modifiers); + boolean isNegative(int modifiers); + long getMetadataByteLength(); class ElementDescriptor { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java index 63b735513ebaf..7b1535dea04e9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java @@ -72,7 +72,27 @@ public class ReflectionMetadataDecoderImpl implements ReflectionMetadataDecoder public static final int IN_HEAP_FLAG_MASK = 1 << IN_HEAP_FLAG_INDEX; public static final int HIDING_FLAG_INDEX = 29; public static final int HIDING_FLAG_MASK = 1 << HIDING_FLAG_INDEX; - public static final int ALL_FLAGS_MASK = COMPLETE_FLAG_MASK | IN_HEAP_FLAG_MASK | HIDING_FLAG_MASK; + public static final int NEGATIVE_FLAG_INDEX = 28; + public static final int NEGATIVE_FLAG_MASK = 1 << NEGATIVE_FLAG_INDEX; + /* single lookup flags are filled before encoding */ + public static final int ALL_FLAGS_MASK = COMPLETE_FLAG_MASK | IN_HEAP_FLAG_MASK | HIDING_FLAG_MASK | NEGATIVE_FLAG_MASK; + + public static final int ALL_FIELDS_FLAG = 1 << 16; + public static final int ALL_DECLARED_FIELDS_FLAG = 1 << 17; + public static final int ALL_METHODS_FLAG = 1 << 18; + public static final int ALL_DECLARED_METHODS_FLAG = 1 << 19; + public static final int ALL_CONSTRUCTORS_FLAG = 1 << 20; + public static final int ALL_DECLARED_CONSTRUCTORS_FLAG = 1 << 21; + public static final int ALL_CLASSES_FLAG = 1 << 22; + public static final int ALL_DECLARED_CLASSES_FLAG = 1 << 23; + public static final int ALL_RECORD_COMPONENTS_FLAG = 1 << 24; + public static final int ALL_PERMITTED_SUBCLASSES_FLAG = 1 << 25; + public static final int ALL_NEST_MEMBERS_FLAG = 1 << 26; + public static final int ALL_SIGNERS_FLAG = 1 << 27; + public static final int ALL_ENABLED_QUERIES_FLAGS_MASK = ALL_FIELDS_FLAG | ALL_DECLARED_FIELDS_FLAG | ALL_METHODS_FLAG | ALL_DECLARED_METHODS_FLAG | ALL_CONSTRUCTORS_FLAG | + ALL_DECLARED_CONSTRUCTORS_FLAG | ALL_CLASSES_FLAG | ALL_DECLARED_CLASSES_FLAG | ALL_RECORD_COMPONENTS_FLAG | ALL_PERMITTED_SUBCLASSES_FLAG | ALL_NEST_MEMBERS_FLAG | + ALL_SIGNERS_FLAG; + public static final int CLASS_ACCESS_FLAGS_MASK = ~ALL_ENABLED_QUERIES_FLAGS_MASK; static byte[] getEncoding() { return ImageSingletons.lookup(ReflectionMetadataEncoding.class).getEncoding(); @@ -161,6 +181,19 @@ public Target_java_lang_reflect_RecordComponent[] parseRecordComponents(DynamicH return decodeArray(reader, Target_java_lang_reflect_RecordComponent.class, (i) -> decodeRecordComponent(reader, DynamicHub.toClass(declaringType))); } + /** + * Object array encoding. + * + *

+     * ObjectIndex[] objects
+     * 
+ */ + @Override + public Object[] parseObjects(int index) { + UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); + return decodeArray(reader, Object.class, (i) -> decodeObject(reader)); + } + /** * Parameters encoding for executables. * @@ -208,6 +241,11 @@ public boolean isHiding(int modifiers) { return (modifiers & HIDING_FLAG_MASK) != 0; } + @Override + public boolean isNegative(int modifiers) { + return (modifiers & NEGATIVE_FLAG_MASK) != 0; + } + @Override public long getMetadataByteLength() { return ImageSingletons.lookup(ReflectionMetadataEncoding.class).getEncoding().length; @@ -267,6 +305,15 @@ private static void decodeAndThrowError(int index) throws * *
      * ReachableFieldEncoding : FieldMetadata {
+     *     int         modifiers (including EXISTS flag)
+     *     StringIndex name
+     * }
+     * 
+ * + * Negative query field encoding. + * + *
+     * NegativeQueryFieldEncoding : FieldMetadata {
      *     int         modifiers (always zero)
      *     StringIndex name
      * }
@@ -289,16 +336,18 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class declaringC
         }
         boolean hiding = (modifiers & HIDING_FLAG_MASK) != 0;
         assert !(complete && hiding);
+        boolean negative = (modifiers & NEGATIVE_FLAG_MASK) != 0;
+        assert !(negative && (complete || hiding));
         modifiers &= ~COMPLETE_FLAG_MASK;
 
         String name = decodeName(buf);
         Class type = (complete || hiding) ? decodeType(buf) : null;
         if (!complete) {
-            if (reflectOnly != hiding) {
+            if (reflectOnly != (hiding || negative)) {
                 /*
-                 * When querying for reflection fields, we want the hiding fields but not the
-                 * reachable fields. When querying for reachable fields, we want the reachable
-                 * fields but not the hiding fields.
+                 * When querying for reflection fields, we want the hiding fields and negative
+                 * queries but not the reachable fields. When querying for reachable fields, we want
+                 * the reachable fields but not the hiding fields and negative queries.
                  */
                 return null;
             }
@@ -307,9 +356,9 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class declaringC
             }
             Target_java_lang_reflect_Field field = new Target_java_lang_reflect_Field();
             if (JavaVersionUtil.JAVA_SPEC >= 17) {
-                field.constructorJDK17OrLater(declaringClass, name, type, modifiers, false, -1, null, null);
+                field.constructorJDK17OrLater(declaringClass, name, negative ? Object.class : type, modifiers, false, -1, null, null);
             } else {
-                field.constructorJDK11OrEarlier(declaringClass, name, type, modifiers, -1, null, null);
+                field.constructorJDK11OrEarlier(declaringClass, name, negative ? Object.class : type, modifiers, -1, null, null);
             }
             return SubstrateUtil.cast(field, Field.class);
         }
@@ -379,6 +428,16 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class declaringC
      *
      * 
      * ReachableMethodMetadata : MethodMetadata {
+     *     int          modifiers      (including EXISTS flag)
+     *     StringIndex  name
+     *     ClassIndex[] parameterTypes
+     * }
+     * 
+ * + * Negative query method encoding. + * + *
+     * NegativeQueryMethodMetadata : MethodMetadata {
      *     int          modifiers      (always zero)
      *     StringIndex  name
      *     ClassIndex[] parameterTypes
@@ -413,6 +472,15 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class declaringC
      *
      * 
      * ReachableConstructorMetadata : ConstructorMetadata {
+     *     int          modifiers      (including EXISTS flag)
+     *     ClassIndex[] parameterTypes
+     * }
+     * 
+ * + * Negative query constructor encoding. + * + *
+     * NegativeQueryConstructorMetadata : ConstructorMetadata {
      *     int          modifiers      (always zero)
      *     ClassIndex[] parameterTypes
      * }
@@ -441,18 +509,20 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class decla
         }
         boolean hiding = (modifiers & HIDING_FLAG_MASK) != 0;
         assert !(complete && hiding);
+        boolean negative = (modifiers & NEGATIVE_FLAG_MASK) != 0;
+        assert !(negative && (complete || hiding));
         modifiers &= ~COMPLETE_FLAG_MASK;
 
         String name = isMethod ? decodeName(buf) : null;
         Object[] parameterTypes;
-        if (complete || hiding) {
+        if (complete || hiding || negative) {
             parameterTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf));
         } else {
             parameterTypes = decodeArray(buf, String.class, (i) -> decodeName(buf));
         }
         Class returnType = isMethod && (complete || hiding) ? decodeType(buf) : null;
         if (!complete) {
-            if (reflectOnly != hiding) {
+            if (reflectOnly != (hiding || negative)) {
                 /*
                  * When querying for reflection methods, we want the hiding methods but not the
                  * reachable methods. When querying for reachable methods, we want the reachable
@@ -465,7 +535,7 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class decla
                     return new MethodDescriptor(declaringClass, name, (String[]) parameterTypes);
                 }
                 Target_java_lang_reflect_Method method = new Target_java_lang_reflect_Method();
-                method.constructor(declaringClass, name, (Class[]) parameterTypes, returnType, null, modifiers, -1, null, null, null, null);
+                method.constructor(declaringClass, name, (Class[]) parameterTypes, negative ? Object.class : returnType, null, modifiers, -1, null, null, null, null);
                 return SubstrateUtil.cast(method, Executable.class);
             } else {
                 if (!reflectOnly) {
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 dc45967966e25..dc0d9b700f976 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
@@ -32,13 +32,14 @@
 
 import org.graalvm.nativeimage.ImageSingletons;
 
+import com.oracle.svm.core.SubstrateUtil;
 import com.oracle.svm.core.annotate.Alias;
 import com.oracle.svm.core.annotate.RecomputeFieldValue;
 import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind;
 import com.oracle.svm.core.annotate.Substitute;
 import com.oracle.svm.core.annotate.TargetClass;
 import com.oracle.svm.core.annotate.TargetElement;
-import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils;
 
 import sun.reflect.generics.repository.ConstructorRepository;
 
@@ -70,7 +71,7 @@ public native void constructor(Class declaringClass, Class[] parameterType
     @Substitute
     Target_jdk_internal_reflect_ConstructorAccessor acquireConstructorAccessor() {
         if (constructorAccessor == null) {
-            throw VMError.unsupportedFeature("Runtime reflection is not supported for " + this);
+            throw MissingReflectionRegistrationUtils.forQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
         }
         return constructorAccessor;
     }
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 934c718888550..bd90ba19007b7 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
@@ -32,13 +32,14 @@
 
 import org.graalvm.nativeimage.ImageSingletons;
 
+import com.oracle.svm.core.SubstrateUtil;
 import com.oracle.svm.core.annotate.Alias;
 import com.oracle.svm.core.annotate.RecomputeFieldValue;
 import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind;
 import com.oracle.svm.core.annotate.Substitute;
 import com.oracle.svm.core.annotate.TargetClass;
 import com.oracle.svm.core.annotate.TargetElement;
-import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils;
 
 import sun.reflect.generics.repository.MethodRepository;
 
@@ -73,7 +74,7 @@ public native void constructor(Class declaringClass, String name, Class[]
     @Substitute
     public Target_jdk_internal_reflect_MethodAccessor acquireMethodAccessor() {
         if (methodAccessor == null) {
-            throw VMError.unsupportedFeature("Runtime reflection is not supported for " + this);
+            throw MissingReflectionRegistrationUtils.forQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
         }
         return methodAccessor;
     }
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 5f246677ca850..845fae7fdc60c 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
@@ -70,6 +70,8 @@
 import org.graalvm.nativeimage.Platform;
 import org.graalvm.nativeimage.Platforms;
 import org.graalvm.nativeimage.c.function.RelocatedPointer;
+import org.graalvm.nativeimage.impl.ConfigurationCondition;
+import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
 
 import com.oracle.graal.pointsto.BigBang;
 import com.oracle.graal.pointsto.PointsToAnalysis;
@@ -148,6 +150,7 @@ public class SVMHost extends HostVM {
     private final LinkAtBuildTimeSupport linkAtBuildTimeSupport;
     private final HostedStringDeduplication stringTable;
     private final UnsafeAutomaticSubstitutionProcessor automaticSubstitutions;
+    private final RuntimeReflectionSupport reflectionSupport;
 
     /**
      * Optionally keep the Graal graphs alive during analysis. This increases the memory footprint
@@ -183,6 +186,7 @@ public SVMHost(OptionValues options, ClassLoader classLoader, ClassInitializatio
             ImageSingletons.add(HostVM.MultiMethodAnalysisPolicy.class, DEFAULT_MULTIMETHOD_ANALYSIS_POLICY);
             multiMethodAnalysisPolicy = DEFAULT_MULTIMETHOD_ANALYSIS_POLICY;
         }
+        this.reflectionSupport = ImageSingletons.lookup(RuntimeReflectionSupport.class);
     }
 
     private static Map> setupForbiddenTypes(OptionValues options) {
@@ -309,6 +313,14 @@ public void onTypeReachable(AnalysisType analysisType) {
         automaticSubstitutions.computeSubstitutions(this, GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaType(analysisType.getJavaClass()));
     }
 
+    @Override
+    public void onTypeInstantiated(AnalysisType newValue) {
+        if (newValue.isAnnotation()) {
+            /* getDeclaredMethods is called in the AnnotationType constructor */
+            reflectionSupport.registerAllDeclaredMethodsQuery(ConfigurationCondition.alwaysTrue(), true, newValue.getJavaClass());
+        }
+    }
+
     @Override
     public boolean isInitialized(AnalysisType type) {
         boolean shouldInitializeAtRuntime = classInitializationSupport.shouldInitializeAtRuntime(type);
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/TypeAnnotationValue.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/TypeAnnotationValue.java
index 750f30e9ddd03..5dbccfa154b4f 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/TypeAnnotationValue.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/TypeAnnotationValue.java
@@ -24,7 +24,6 @@
  */
 package com.oracle.svm.hosted.annotation;
 
-import java.lang.annotation.AnnotationFormatError;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Objects;
@@ -108,7 +107,7 @@ private static byte[] extractTargetInfo(ByteBuffer buf) {
                 buf.get();
                 break;
             default:
-                throw new AnnotationFormatError("Could not parse bytes for type annotations");
+                throw new IllegalArgumentException("Invalid target info code");
         }
         int endPos = buf.position();
         byte[] targetInfo = new byte[endPos - startPos];
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java
index e1c3e9d606af6..06b12f6968772 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java
@@ -55,7 +55,7 @@
 public final class ConfigurationParserUtils {
 
     public static ReflectionConfigurationParser>> create(ReflectionRegistry registry, ImageClassLoader imageClassLoader) {
-        return new ReflectionConfigurationParser<>(new ReflectionRegistryAdapter(registry, imageClassLoader),
+        return new ReflectionConfigurationParser<>(RegistryAdapter.create(registry, imageClassLoader),
                         ConfigurationFiles.Options.StrictConfiguration.getValue());
     }
 
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java
index 7ceeb4899080c..700961f041e4e 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) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, 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
@@ -24,200 +24,135 @@
  */
 package com.oracle.svm.hosted.config;
 
-import java.lang.reflect.Executable;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import static com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils.throwMissingRegistrationErrors;
+
 import java.util.List;
 
 import org.graalvm.nativeimage.impl.ConfigurationCondition;
-import org.graalvm.nativeimage.impl.ReflectionRegistry;
+import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
 
 import com.oracle.svm.core.TypeResult;
 import com.oracle.svm.core.configure.ConditionalElement;
-import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate;
-import com.oracle.svm.core.hub.ClassForNameSupport;
-import com.oracle.svm.core.jdk.SealedClassSupport;
 import com.oracle.svm.hosted.ImageClassLoader;
-import com.oracle.svm.util.ClassUtil;
-
-import jdk.vm.ci.meta.MetaUtil;
 
-public class ReflectionRegistryAdapter implements ReflectionConfigurationParserDelegate>> {
-    private final ReflectionRegistry registry;
-    private final ImageClassLoader classLoader;
+public class ReflectionRegistryAdapter extends RegistryAdapter {
+    private final RuntimeReflectionSupport reflectionSupport;
 
-    public ReflectionRegistryAdapter(ReflectionRegistry registry, ImageClassLoader classLoader) {
-        this.registry = registry;
-        this.classLoader = classLoader;
+    ReflectionRegistryAdapter(RuntimeReflectionSupport reflectionSupport, ImageClassLoader classLoader) {
+        super(reflectionSupport, classLoader);
+        this.reflectionSupport = reflectionSupport;
     }
 
     @Override
-    public void registerType(ConditionalElement> type) {
-        registry.register(type.getCondition(), type.getElement());
+    public TypeResult>> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) {
+        TypeResult>> result = super.resolveType(condition, typeName, allowPrimitives);
+        if (!result.isPresent()) {
+            Throwable classLookupException = result.getException();
+            if (classLookupException instanceof LinkageError) {
+                reflectionSupport.registerClassLookupException(condition, typeName, classLookupException);
+            } else if (throwMissingRegistrationErrors() && classLookupException instanceof ClassNotFoundException) {
+                reflectionSupport.registerClassLookup(condition, typeName);
+            }
+        }
+        return result;
     }
 
     @Override
-    public TypeResult resolveCondition(String typeName) {
-        String canonicalizedName = canonicalizeTypeName(typeName);
-        TypeResult> clazz = classLoader.findClass(canonicalizedName);
-        return clazz.map(Class::getTypeName)
-                        .map(ConfigurationCondition::create);
+    public void registerPublicClasses(ConditionalElement> type) {
+        reflectionSupport.registerAllClassesQuery(type.getCondition(), type.getElement());
     }
 
     @Override
-    public TypeResult>> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) {
-        String name = canonicalizeTypeName(typeName);
-        TypeResult> clazz = classLoader.findClass(name, allowPrimitives);
-        if (!clazz.isPresent()) {
-            Throwable classLookupException = clazz.getException();
-            if (classLookupException instanceof LinkageError || ClassForNameSupport.Options.ExitOnUnknownClassLoadingFailure.getValue()) {
-                registry.registerClassLookupException(condition, typeName, classLookupException);
-            }
-        }
-        return clazz.map(c -> new ConditionalElement<>(condition, c));
+    public void registerDeclaredClasses(ConditionalElement> type) {
+        reflectionSupport.registerAllDeclaredClassesQuery(type.getCondition(), type.getElement());
     }
 
-    private static String canonicalizeTypeName(String typeName) {
-        String name = typeName;
-        if (name.indexOf('[') != -1) {
-            /* accept "int[][]", "java.lang.String[]" */
-            name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true);
-        }
-        return name;
+    @Override
+    public void registerRecordComponents(ConditionalElement> type) {
+        reflectionSupport.registerAllRecordComponentsQuery(type.getCondition(), type.getElement());
     }
 
     @Override
-    public void registerPublicClasses(ConditionalElement> type) {
-        registry.register(type.getCondition(), type.getElement().getClasses());
+    public void registerPermittedSubclasses(ConditionalElement> type) {
+        reflectionSupport.registerAllPermittedSubclassesQuery(type.getCondition(), type.getElement());
     }
 
     @Override
-    public void registerDeclaredClasses(ConditionalElement> type) {
-        registry.register(type.getCondition(), type.getElement().getDeclaredClasses());
+    public void registerNestMembers(ConditionalElement> type) {
+        reflectionSupport.registerAllNestMembersQuery(type.getCondition(), type.getElement());
     }
 
     @Override
-    public void registerPermittedSubclasses(ConditionalElement> type) {
-        Class[] classes = SealedClassSupport.singleton().getPermittedSubclasses(type.getElement());
-        if (classes != null) {
-            registry.register(type.getCondition(), classes);
-        }
+    public void registerSigners(ConditionalElement> type) {
+        reflectionSupport.registerAllSignersQuery(type.getCondition(), type.getElement());
     }
 
     @Override
     public void registerPublicFields(ConditionalElement> type) {
-        registry.register(type.getCondition(), false, type.getElement().getFields());
+        reflectionSupport.registerAllFieldsQuery(type.getCondition(), type.getElement());
     }
 
     @Override
     public void registerDeclaredFields(ConditionalElement> type) {
-        registry.register(type.getCondition(), false, type.getElement().getDeclaredFields());
+        reflectionSupport.registerAllDeclaredFieldsQuery(type.getCondition(), type.getElement());
     }
 
     @Override
     public void registerPublicMethods(boolean queriedOnly, ConditionalElement> type) {
-        registry.register(type.getCondition(), queriedOnly, type.getElement().getMethods());
+        reflectionSupport.registerAllMethodsQuery(type.getCondition(), queriedOnly, type.getElement());
     }
 
     @Override
     public void registerDeclaredMethods(boolean queriedOnly, ConditionalElement> type) {
-        registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredMethods());
+        reflectionSupport.registerAllDeclaredMethodsQuery(type.getCondition(), queriedOnly, type.getElement());
     }
 
     @Override
     public void registerPublicConstructors(boolean queriedOnly, ConditionalElement> type) {
-        registry.register(type.getCondition(), queriedOnly, type.getElement().getConstructors());
+        reflectionSupport.registerAllConstructorsQuery(type.getCondition(), queriedOnly, type.getElement());
     }
 
     @Override
     public void registerDeclaredConstructors(boolean queriedOnly, ConditionalElement> type) {
-        registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructors());
+        reflectionSupport.registerAllDeclaredConstructorsQuery(type.getCondition(), queriedOnly, type.getElement());
     }
 
     @Override
     public void registerField(ConditionalElement> type, String fieldName, boolean allowWrite) throws NoSuchFieldException {
-        registry.register(type.getCondition(), allowWrite, type.getElement().getDeclaredField(fieldName));
-    }
-
-    @Override
-    public boolean registerAllMethodsWithName(boolean queriedOnly, ConditionalElement> type, String methodName) {
-        boolean found = false;
-        Executable[] methods = type.getElement().getDeclaredMethods();
-        for (Executable method : methods) {
-            if (method.getName().equals(methodName)) {
-                registerExecutable(type.getCondition(), queriedOnly, method);
-                found = true;
+        try {
+            super.registerField(type, fieldName, allowWrite);
+        } catch (NoSuchFieldException e) {
+            if (throwMissingRegistrationErrors()) {
+                reflectionSupport.registerFieldLookup(type.getCondition(), type.getElement(), fieldName);
+            } else {
+                throw e;
             }
         }
-        return found;
-    }
-
-    @Override
-    public boolean registerAllConstructors(boolean queriedOnly, ConditionalElement> type) {
-        Executable[] methods = type.getElement().getDeclaredConstructors();
-        registerExecutable(type.getCondition(), queriedOnly, methods);
-        return methods.length > 0;
-    }
-
-    @Override
-    public void registerUnsafeAllocated(ConditionalElement> clazz) {
-        Class type = clazz.getElement();
-        if (!type.isArray() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
-            registry.register(clazz.getCondition(), true, clazz.getElement());
-            /*
-             * Ignore otherwise as the implementation of allocateInstance will anyhow throw an
-             * exception.
-             */
-        }
     }
 
     @Override
     public void registerMethod(boolean queriedOnly, ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException {
-        Class[] parameterTypesArray = getParameterTypes(methodParameterTypes);
-        Method method;
         try {
-            method = type.getElement().getDeclaredMethod(methodName, parameterTypesArray);
-        } catch (NoClassDefFoundError e) {
-            /*
-             * getDeclaredMethod() builds a set of all the declared methods, which can fail when a
-             * symbolic reference from another method to a type (via parameters, return value)
-             * cannot be resolved. getMethod() builds a different set of methods and can still
-             * succeed. This case must be handled for predefined classes when, during the run
-             * observed by the agent, a referenced class was not loaded and is not available now
-             * precisely because the application used getMethod() instead of getDeclaredMethod().
-             */
-            try {
-                method = type.getElement().getMethod(methodName, parameterTypesArray);
-            } catch (Throwable ignored) {
+            super.registerMethod(queriedOnly, type, methodName, methodParameterTypes);
+        } catch (NoSuchMethodException e) {
+            if (throwMissingRegistrationErrors()) {
+                reflectionSupport.registerMethodLookup(type.getCondition(), type.getElement(), methodName, getParameterTypes(methodParameterTypes));
+            } else {
                 throw e;
             }
         }
-        registerExecutable(type.getCondition(), queriedOnly, method);
     }
 
     @Override
     public void registerConstructor(boolean queriedOnly, ConditionalElement> type, List>> methodParameterTypes) throws NoSuchMethodException {
-        Class[] parameterTypesArray = getParameterTypes(methodParameterTypes);
-        registerExecutable(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructor(parameterTypesArray));
-    }
-
-    private static Class[] getParameterTypes(List>> methodParameterTypes) {
-        return methodParameterTypes.stream()
-                        .map(ConditionalElement::getElement)
-                        .toArray(Class[]::new);
-    }
-
-    private void registerExecutable(ConfigurationCondition condition, boolean queriedOnly, Executable... executable) {
-        registry.register(condition, queriedOnly, executable);
-    }
-
-    @Override
-    public String getTypeName(ConditionalElement> type) {
-        return type.getElement().getTypeName();
-    }
-
-    @Override
-    public String getSimpleName(ConditionalElement> type) {
-        return ClassUtil.getUnqualifiedName(type.getElement());
+        try {
+            super.registerConstructor(queriedOnly, type, methodParameterTypes);
+        } catch (NoSuchMethodException e) {
+            if (throwMissingRegistrationErrors()) {
+                reflectionSupport.registerConstructorLookup(type.getCondition(), type.getElement(), getParameterTypes(methodParameterTypes));
+            } else {
+                throw e;
+            }
+        }
     }
 }
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
new file mode 100644
index 0000000000000..1725a1cc77524
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.svm.hosted.config;
+
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+import org.graalvm.nativeimage.impl.ConfigurationCondition;
+import org.graalvm.nativeimage.impl.ReflectionRegistry;
+import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
+
+import com.oracle.svm.core.TypeResult;
+import com.oracle.svm.core.configure.ConditionalElement;
+import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate;
+import com.oracle.svm.hosted.ImageClassLoader;
+import com.oracle.svm.util.ClassUtil;
+
+import jdk.vm.ci.meta.MetaUtil;
+
+public class RegistryAdapter implements ReflectionConfigurationParserDelegate>> {
+    private final ReflectionRegistry registry;
+    private final ImageClassLoader classLoader;
+
+    public static RegistryAdapter create(ReflectionRegistry registry, ImageClassLoader classLoader) {
+        if (registry instanceof RuntimeReflectionSupport) {
+            return new ReflectionRegistryAdapter((RuntimeReflectionSupport) registry, classLoader);
+        } else {
+            return new RegistryAdapter(registry, classLoader);
+        }
+    }
+
+    RegistryAdapter(ReflectionRegistry registry, ImageClassLoader classLoader) {
+        this.registry = registry;
+        this.classLoader = classLoader;
+    }
+
+    @Override
+    public void registerType(ConditionalElement> type) {
+        registry.register(type.getCondition(), type.getElement());
+    }
+
+    @Override
+    public TypeResult resolveCondition(String typeName) {
+        String canonicalizedName = canonicalizeTypeName(typeName);
+        TypeResult> clazz = classLoader.findClass(canonicalizedName);
+        return clazz.map(Class::getTypeName)
+                        .map(ConfigurationCondition::create);
+    }
+
+    @Override
+    public TypeResult>> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) {
+        String name = canonicalizeTypeName(typeName);
+        TypeResult> clazz = classLoader.findClass(name, allowPrimitives);
+        return clazz.map(c -> new ConditionalElement<>(condition, c));
+    }
+
+    private static String canonicalizeTypeName(String typeName) {
+        String name = typeName;
+        if (name.indexOf('[') != -1) {
+            /* accept "int[][]", "java.lang.String[]" */
+            name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true);
+        }
+        return name;
+    }
+
+    @Override
+    public void registerPublicClasses(ConditionalElement> type) {
+        registry.register(type.getCondition(), type.getElement().getClasses());
+    }
+
+    @Override
+    public void registerDeclaredClasses(ConditionalElement> type) {
+        registry.register(type.getCondition(), type.getElement().getDeclaredClasses());
+    }
+
+    @Override
+    public void registerRecordComponents(ConditionalElement> type) {
+    }
+
+    @Override
+    public void registerPermittedSubclasses(ConditionalElement> type) {
+    }
+
+    @Override
+    public void registerNestMembers(ConditionalElement> type) {
+    }
+
+    @Override
+    public void registerSigners(ConditionalElement> type) {
+    }
+
+    @Override
+    public void registerPublicFields(ConditionalElement> type) {
+        registry.register(type.getCondition(), false, type.getElement().getFields());
+    }
+
+    @Override
+    public void registerDeclaredFields(ConditionalElement> type) {
+        registry.register(type.getCondition(), false, type.getElement().getDeclaredFields());
+    }
+
+    @Override
+    public void registerPublicMethods(boolean queriedOnly, ConditionalElement> type) {
+        registry.register(type.getCondition(), queriedOnly, type.getElement().getMethods());
+    }
+
+    @Override
+    public void registerDeclaredMethods(boolean queriedOnly, ConditionalElement> type) {
+        registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredMethods());
+    }
+
+    @Override
+    public void registerPublicConstructors(boolean queriedOnly, ConditionalElement> type) {
+        registry.register(type.getCondition(), queriedOnly, type.getElement().getConstructors());
+    }
+
+    @Override
+    public void registerDeclaredConstructors(boolean queriedOnly, ConditionalElement> type) {
+        registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructors());
+    }
+
+    @Override
+    public void registerField(ConditionalElement> type, String fieldName, boolean allowWrite) throws NoSuchFieldException {
+        registry.register(type.getCondition(), allowWrite, type.getElement().getDeclaredField(fieldName));
+    }
+
+    @Override
+    public boolean registerAllMethodsWithName(boolean queriedOnly, ConditionalElement> type, String methodName) {
+        boolean found = false;
+        Executable[] methods = type.getElement().getDeclaredMethods();
+        for (Executable method : methods) {
+            if (method.getName().equals(methodName)) {
+                registerExecutable(type.getCondition(), queriedOnly, method);
+                found = true;
+            }
+        }
+        return found;
+    }
+
+    @Override
+    public boolean registerAllConstructors(boolean queriedOnly, ConditionalElement> type) {
+        Executable[] methods = type.getElement().getDeclaredConstructors();
+        registerExecutable(type.getCondition(), queriedOnly, methods);
+        return methods.length > 0;
+    }
+
+    @Override
+    public void registerUnsafeAllocated(ConditionalElement> clazz) {
+        Class type = clazz.getElement();
+        if (!type.isArray() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
+            registry.register(clazz.getCondition(), true, clazz.getElement());
+            /*
+             * Ignore otherwise as the implementation of allocateInstance will anyhow throw an
+             * exception.
+             */
+        }
+    }
+
+    @Override
+    public void registerMethod(boolean queriedOnly, ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException {
+        Class[] parameterTypesArray = getParameterTypes(methodParameterTypes);
+        Method method;
+        try {
+            method = type.getElement().getDeclaredMethod(methodName, parameterTypesArray);
+        } catch (NoClassDefFoundError e) {
+            /*
+             * getDeclaredMethod() builds a set of all the declared methods, which can fail when a
+             * symbolic reference from another method to a type (via parameters, return value)
+             * cannot be resolved. getMethod() builds a different set of methods and can still
+             * succeed. This case must be handled for predefined classes when, during the run
+             * observed by the agent, a referenced class was not loaded and is not available now
+             * precisely because the application used getMethod() instead of getDeclaredMethod().
+             */
+            try {
+                method = type.getElement().getMethod(methodName, parameterTypesArray);
+            } catch (Throwable ignored) {
+                throw e;
+            }
+        }
+        registerExecutable(type.getCondition(), queriedOnly, method);
+    }
+
+    @Override
+    public void registerConstructor(boolean queriedOnly, ConditionalElement> type, List>> methodParameterTypes) throws NoSuchMethodException {
+        Class[] parameterTypesArray = getParameterTypes(methodParameterTypes);
+        registerExecutable(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructor(parameterTypesArray));
+    }
+
+    static Class[] getParameterTypes(List>> methodParameterTypes) {
+        return methodParameterTypes.stream()
+                        .map(ConditionalElement::getElement)
+                        .toArray(Class[]::new);
+    }
+
+    private void registerExecutable(ConfigurationCondition condition, boolean queriedOnly, Executable... executable) {
+        registry.register(condition, queriedOnly, executable);
+    }
+
+    @Override
+    public String getTypeName(ConditionalElement> type) {
+        return type.getElement().getTypeName();
+    }
+
+    @Override
+    public String getSimpleName(ConditionalElement> type) {
+        return ClassUtil.getUnqualifiedName(type.getElement());
+    }
+}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java
index 1ef40573259a3..2ec6df5486d7f 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java
@@ -24,6 +24,7 @@
  */
 package com.oracle.svm.hosted.image;
 
+import static com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils.throwMissingRegistrationErrors;
 import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
 
 import java.lang.reflect.AccessibleObject;
@@ -61,6 +62,7 @@
 import com.oracle.graal.pointsto.infrastructure.WrappedElement;
 import com.oracle.graal.pointsto.meta.AnalysisField;
 import com.oracle.graal.pointsto.meta.AnalysisMethod;
+import com.oracle.graal.pointsto.meta.AnalysisType;
 import com.oracle.objectfile.ObjectFile;
 import com.oracle.svm.common.meta.MultiMethod;
 import com.oracle.svm.core.SubstrateOptions;
@@ -268,93 +270,116 @@ public void buildRuntimeMetadata(CFunctionPointer firstMethod, UnsignedWord code
             }
         }
 
-        Set includedFields = new HashSet<>();
-        Set includedMethods = new HashSet<>();
+        Set includedFields = new HashSet<>();
+        Set includedMethods = new HashSet<>();
         Map configurationFields = reflectionSupport.getReflectionFields();
         Map configurationExecutables = reflectionSupport.getReflectionExecutables();
 
         reflectionSupport.getHeapReflectionFields().forEach(((analysisField, reflectField) -> {
-            HostedField hostedField = hUniverse.lookup(analysisField);
-            if (!includedFields.contains(hostedField)) {
+            if (includedFields.add(analysisField)) {
+                HostedField hostedField = hUniverse.lookup(analysisField);
                 reflectionMetadataEncoder.addHeapAccessibleObjectMetadata(hMetaAccess, hostedField, reflectField, configurationFields.containsKey(analysisField));
-                includedFields.add(hostedField);
             }
         }));
 
         reflectionSupport.getHeapReflectionExecutables().forEach(((analysisMethod, reflectMethod) -> {
-            HostedMethod hostedMethod = hUniverse.lookup(analysisMethod);
-            if (!includedMethods.contains(hostedMethod)) {
+            if (includedMethods.add(analysisMethod)) {
+                HostedMethod hostedMethod = hUniverse.lookup(analysisMethod);
                 reflectionMetadataEncoder.addHeapAccessibleObjectMetadata(hMetaAccess, hostedMethod, reflectMethod, configurationExecutables.containsKey(analysisMethod));
-                includedMethods.add(hostedMethod);
             }
         }));
 
         configurationFields.forEach(((analysisField, reflectField) -> {
-            HostedField hostedField = hUniverse.lookup(analysisField);
-            if (!includedFields.contains(hostedField)) {
+            if (includedFields.add(analysisField)) {
+                HostedField hostedField = hUniverse.lookup(analysisField);
                 reflectionMetadataEncoder.addReflectionFieldMetadata(hMetaAccess, hostedField, reflectField);
-                includedFields.add(hostedField);
             }
         }));
 
         configurationExecutables.forEach(((analysisMethod, reflectMethod) -> {
-            HostedMethod method = hUniverse.lookup(analysisMethod);
-            if (!includedMethods.contains(method)) {
+            if (includedMethods.add(analysisMethod)) {
+                HostedMethod method = hUniverse.lookup(analysisMethod);
                 Object accessor = reflectionSupport.getAccessor(analysisMethod);
                 reflectionMetadataEncoder.addReflectionExecutableMetadata(hMetaAccess, method, reflectMethod, accessor);
-                includedMethods.add(method);
             }
         }));
 
         for (Object field : reflectionSupport.getHidingReflectionFields()) {
-            AnalysisField hidingField = (AnalysisField) field;
-            HostedField hostedField = hUniverse.optionalLookup(hidingField);
-            if (hostedField == null || !includedFields.contains(hostedField)) {
-                HostedType declaringType = hUniverse.lookup(hidingField.getDeclaringClass());
-                String name = hidingField.getName();
-                HostedType type = hUniverse.lookup(hidingField.getType());
-                int modifiers = hidingField.getModifiers();
-                reflectionMetadataEncoder.addHidingFieldMetadata(hidingField, declaringType, name, type, modifiers);
-                if (hostedField != null) {
-                    includedFields.add(hostedField);
-                }
+            AnalysisField analysisField = (AnalysisField) field;
+            if (includedFields.add(analysisField)) {
+                HostedType declaringType = hUniverse.lookup(analysisField.getDeclaringClass());
+                String name = analysisField.getName();
+                HostedType type = hUniverse.lookup(analysisField.getType());
+                int modifiers = analysisField.getModifiers();
+                reflectionMetadataEncoder.addHidingFieldMetadata(analysisField, declaringType, name, type, modifiers);
             }
         }
 
         for (Object method : reflectionSupport.getHidingReflectionMethods()) {
-            AnalysisMethod hidingMethod = (AnalysisMethod) method;
-            HostedMethod hostedMethod = hUniverse.optionalLookup(hidingMethod);
-            if (hostedMethod == null || !includedMethods.contains(hostedMethod)) {
-                HostedType declaringType = hUniverse.lookup(hidingMethod.getDeclaringClass());
-                String name = hidingMethod.getName();
-                JavaType[] analysisParameterTypes = hidingMethod.getSignature().toParameterTypes(null);
+            AnalysisMethod analysisMethod = (AnalysisMethod) method;
+            if (includedMethods.add(analysisMethod)) {
+                HostedType declaringType = hUniverse.lookup(analysisMethod.getDeclaringClass());
+                String name = analysisMethod.getName();
+                JavaType[] analysisParameterTypes = analysisMethod.getSignature().toParameterTypes(null);
                 HostedType[] parameterTypes = new HostedType[analysisParameterTypes.length];
                 for (int i = 0; i < analysisParameterTypes.length; ++i) {
                     parameterTypes[i] = hUniverse.lookup(analysisParameterTypes[i]);
                 }
-                int modifiers = hidingMethod.getModifiers();
-                HostedType returnType = hUniverse.lookup(hidingMethod.getSignature().getReturnType(null));
-                reflectionMetadataEncoder.addHidingMethodMetadata(hidingMethod, declaringType, name, parameterTypes, modifiers, returnType);
-                if (hostedMethod != null) {
-                    includedMethods.add(hostedMethod);
-                }
+                int modifiers = analysisMethod.getModifiers();
+                HostedType returnType = hUniverse.lookup(analysisMethod.getSignature().getReturnType(null));
+                reflectionMetadataEncoder.addHidingMethodMetadata(analysisMethod, declaringType, name, parameterTypes, modifiers, returnType);
             }
         }
 
         if (SubstrateOptions.IncludeMethodData.getValue()) {
             for (HostedField field : hUniverse.getFields()) {
-                if (field.isAccessed() && !includedFields.contains(field)) {
+                if (field.isAccessed() && !includedFields.contains(field.getWrapped())) {
                     reflectionMetadataEncoder.addReachableFieldMetadata(field);
                 }
             }
 
             for (HostedMethod method : hUniverse.getMethods()) {
-                if (method.getWrapped().isReachable() && !method.getWrapped().isIntrinsicMethod() && !includedMethods.contains(method)) {
+                if (method.getWrapped().isReachable() && !method.getWrapped().isIntrinsicMethod() && !includedMethods.contains(method.getWrapped())) {
                     reflectionMetadataEncoder.addReachableExecutableMetadata(method);
                 }
             }
         }
 
+        if (throwMissingRegistrationErrors()) {
+            reflectionSupport.getNegativeFieldQueries().forEach((analysisType, fieldNames) -> {
+                HostedType hostedType = hUniverse.optionalLookup(analysisType);
+                if (hostedType != null) {
+                    for (String fieldName : fieldNames) {
+                        reflectionMetadataEncoder.addNegativeFieldQueryMetadata(hostedType, fieldName);
+                    }
+                }
+            });
+
+            reflectionSupport.getNegativeMethodQueries().forEach((analysisType, methodSignatures) -> {
+                HostedType hostedType = hUniverse.optionalLookup(analysisType);
+                if (hostedType != null) {
+                    for (AnalysisMethod.Signature methodSignature : methodSignatures) {
+                        HostedType[] parameterTypes = hUniverse.optionalLookup(methodSignature.parameterTypes());
+                        if (parameterTypes != null) {
+                            reflectionMetadataEncoder.addNegativeMethodQueryMetadata(hostedType, methodSignature.name(), parameterTypes);
+                        }
+                    }
+                }
+            });
+
+            reflectionSupport.getNegativeConstructorQueries().forEach((analysisType, constructorSignatures) -> {
+                HostedType hostedType = hUniverse.optionalLookup(analysisType);
+                if (hostedType != null) {
+                    for (AnalysisType[] analysisParameterTypes : constructorSignatures) {
+                        HostedType[] parameterTypes = hUniverse.optionalLookup(analysisParameterTypes);
+                        if (parameterTypes != null) {
+                            reflectionMetadataEncoder.addNegativeConstructorQueryMetadata(hostedType, parameterTypes);
+                        }
+                    }
+                }
+            });
+        }
+
         if (NativeImageOptions.PrintMethodHistogram.getValue()) {
             System.out.println("encoded deopt entry points                 ; " + frameInfoCustomization.numDeoptEntryPoints);
             System.out.println("encoded during call entry points           ; " + frameInfoCustomization.numDuringCallEntryPoints);
@@ -661,6 +686,12 @@ public interface ReflectionMetadataEncoder extends EncodedReflectionMetadataSupp
 
         void addReachableExecutableMetadata(HostedMethod method);
 
+        void addNegativeFieldQueryMetadata(HostedType declaringClass, String fieldName);
+
+        void addNegativeMethodQueryMetadata(HostedType declaringClass, String methodName, HostedType[] parameterTypes);
+
+        void addNegativeConstructorQueryMetadata(HostedType declaringClass, HostedType[] parameterTypes);
+
         void encodeAllAndInstall();
 
         Method getRoot = ReflectionUtil.lookupMethod(AccessibleObject.class, "getRoot");
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java
index 106ccabbd2e2c..4abfe91b41859 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java
@@ -348,6 +348,17 @@ public HostedType optionalLookup(JavaType type) {
         return types.get(type);
     }
 
+    public HostedType[] optionalLookup(JavaType... javaTypes) {
+        HostedType[] result = new HostedType[javaTypes.length];
+        for (int i = 0; i < javaTypes.length; ++i) {
+            result[i] = optionalLookup(javaTypes[i]);
+            if (result[i] == null) {
+                return null;
+            }
+        }
+        return result;
+    }
+
     @Override
     public HostedField lookup(JavaField field) {
         JavaField result = lookupAllowUnresolved(field);
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 8a58beb19b08c..12b3718b3aa5f 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
@@ -24,6 +24,20 @@
  */
 package com.oracle.svm.hosted.reflect;
 
+import static com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils.throwMissingRegistrationErrors;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_CLASSES_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_CONSTRUCTORS_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_DECLARED_CLASSES_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_DECLARED_CONSTRUCTORS_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_DECLARED_FIELDS_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_DECLARED_METHODS_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_FIELDS_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_METHODS_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_NEST_MEMBERS_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_PERMITTED_SUBCLASSES_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_RECORD_COMPONENTS_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_SIGNERS_FLAG;
+
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Executable;
 import java.lang.reflect.Field;
@@ -33,6 +47,7 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Proxy;
+import java.lang.reflect.RecordComponent;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
 import java.lang.reflect.WildcardType;
@@ -91,6 +106,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl
     // Reflection data
     private final Map, Object[]> registeredRecordComponents = new ConcurrentHashMap<>();
     private final Map, Set>> innerClasses = new ConcurrentHashMap<>();
+    private final Map, Integer> enabledQueriesFlags = new ConcurrentHashMap<>();
     private final Map registeredFields = new ConcurrentHashMap<>();
     private final Set hidingFields = ConcurrentHashMap.newKeySet();
     private final Map registeredMethods = new ConcurrentHashMap<>();
@@ -102,9 +118,14 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl
     private final Map heapFields = new ConcurrentHashMap<>();
     private final Map heapMethods = new ConcurrentHashMap<>();
 
+    // Negative queries
+    private final Map> negativeFieldLookups = new ConcurrentHashMap<>();
+    private final Map> negativeMethodLookups = new ConcurrentHashMap<>();
+    private final Map> negativeConstructorLookups = new ConcurrentHashMap<>();
+
     // Intermediate bookkeeping
     private final Map> processedTypes = new ConcurrentHashMap<>();
-    private final Map, Set> pendingRecordClasses = new ConcurrentHashMap<>();
+    private final Map, Set> pendingRecordClasses;
     private final Set> pendingRegistrations = ConcurrentHashMap.newKeySet();
 
     // Annotations handling
@@ -114,6 +135,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl
 
     ReflectionDataBuilder(SubstrateAnnotationExtractor annotationExtractor) {
         this.annotationExtractor = annotationExtractor;
+        pendingRecordClasses = !throwMissingRegistrationErrors() ? new ConcurrentHashMap<>() : null;
     }
 
     public void duringSetup(AnalysisMetaAccess analysisMetaAccess, AnalysisUniverse analysisUniverse) {
@@ -138,6 +160,10 @@ private void register(Consumer registrationCallback) {
         }
     }
 
+    private void setQueryFlag(Class clazz, int flag) {
+        enabledQueriesFlags.compute(clazz, (key, oldValue) -> (oldValue == null) ? flag : (oldValue | flag));
+    }
+
     @Override
     public void register(ConfigurationCondition condition, boolean unsafeInstantiated, Class clazz) {
         checkNotSealed();
@@ -145,6 +171,20 @@ public void register(ConfigurationCondition condition, boolean unsafeInstantiate
                         () -> analysisUniverse.getBigbang().postTask(debug -> registerClass(clazz, unsafeInstantiated))));
     }
 
+    @Override
+    public void registerAllClassesQuery(ConfigurationCondition condition, Class clazz) {
+        checkNotSealed();
+        registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_CLASSES_FLAG));
+        register(condition, clazz.getClasses());
+    }
+
+    @Override
+    public void registerAllDeclaredClassesQuery(ConfigurationCondition condition, Class clazz) {
+        checkNotSealed();
+        registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_DECLARED_CLASSES_FLAG));
+        register(condition, clazz.getDeclaredClasses());
+    }
+
     private void registerClass(Class clazz, boolean unsafeInstantiated) {
         if (shouldExcludeClass(clazz)) {
             return;
@@ -173,6 +213,52 @@ public void registerClassLookupException(ConfigurationCondition condition, Strin
         registerConditionalConfiguration(condition, () -> ClassForNameSupport.registerExceptionForClass(typeName, t));
     }
 
+    @Override
+    public void registerClassLookup(ConfigurationCondition condition, String typeName) {
+        checkNotSealed();
+        try {
+            register(condition, Class.forName(typeName));
+        } catch (ClassNotFoundException e) {
+            registerConditionalConfiguration(condition, () -> ClassForNameSupport.registerNegativeQuery(typeName));
+        } catch (Throwable t) {
+            registerClassLookupException(condition, typeName, t);
+        }
+    }
+
+    @Override
+    public void registerAllRecordComponentsQuery(ConfigurationCondition condition, Class clazz) {
+        checkNotSealed();
+        registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_RECORD_COMPONENTS_FLAG));
+        register(analysisUniverse -> registerConditionalConfiguration(condition,
+                        () -> analysisUniverse.getBigbang().postTask(debug -> registerRecordComponents(clazz))));
+    }
+
+    @Override
+    public void registerAllPermittedSubclassesQuery(ConfigurationCondition condition, Class clazz) {
+        checkNotSealed();
+        registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_PERMITTED_SUBCLASSES_FLAG));
+        if (clazz.isSealed()) {
+            register(condition, clazz.getPermittedSubclasses());
+        }
+    }
+
+    @Override
+    public void registerAllNestMembersQuery(ConfigurationCondition condition, Class clazz) {
+        checkNotSealed();
+        registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_NEST_MEMBERS_FLAG));
+        for (Class nestMember : clazz.getNestMembers()) {
+            if (nestMember != clazz) {
+                register(condition, nestMember);
+            }
+        }
+    }
+
+    @Override
+    public void registerAllSignersQuery(ConfigurationCondition condition, Class clazz) {
+        checkNotSealed();
+        registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_SIGNERS_FLAG));
+    }
+
     @Override
     public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... executables) {
         checkNotSealed();
@@ -183,6 +269,40 @@ public void register(ConfigurationCondition condition, boolean queriedOnly, Exec
         }));
     }
 
+    @Override
+    public void registerAllMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) {
+        checkNotSealed();
+        for (Class current = clazz; current != null; current = current.getSuperclass()) {
+            final Class currentLambda = current;
+            registerConditionalConfiguration(condition, () -> setQueryFlag(currentLambda, ALL_METHODS_FLAG));
+        }
+        register(condition, queriedOnly, clazz.getMethods());
+    }
+
+    @Override
+    public void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) {
+        checkNotSealed();
+        registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_DECLARED_METHODS_FLAG));
+        register(condition, queriedOnly, clazz.getDeclaredMethods());
+    }
+
+    @Override
+    public void registerAllConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) {
+        checkNotSealed();
+        for (Class current = clazz; current != null; current = current.getSuperclass()) {
+            final Class currentLambda = current;
+            registerConditionalConfiguration(condition, () -> setQueryFlag(currentLambda, ALL_CONSTRUCTORS_FLAG));
+        }
+        register(condition, queriedOnly, clazz.getConstructors());
+    }
+
+    @Override
+    public void registerAllDeclaredConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) {
+        checkNotSealed();
+        registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_DECLARED_CONSTRUCTORS_FLAG));
+        register(condition, queriedOnly, clazz.getDeclaredConstructors());
+    }
+
     private void registerMethod(boolean queriedOnly, Executable reflectExecutable) {
         if (SubstitutionReflectivityFilter.shouldExclude(reflectExecutable, metaAccess, universe)) {
             return;
@@ -198,11 +318,14 @@ private void registerMethod(boolean queriedOnly, Executable reflectExecutable) {
              * The image needs to know about subtypes shadowing methods registered for reflection to
              * ensure the correctness of run-time reflection queries.
              */
-            analysisAccess.registerSubtypeReachabilityHandler((access, subType) -> {
-                universe.getBigbang().postTask(debug -> checkSubtypeForOverridingMethod(analysisMethod, metaAccess.lookupJavaType(subType)));
-            }, declaringClass);
+            analysisAccess.registerSubtypeReachabilityHandler(
+                            (access, subType) -> universe.getBigbang().postTask(debug -> checkSubtypeForOverridingMethod(analysisMethod, metaAccess.lookupJavaType(subType))), declaringClass);
 
-            if (RecordSupport.singleton().isRecord(declaringClass)) {
+            if (declaringType.isAnnotation() && !analysisMethod.isConstructor()) {
+                processAnnotationMethod(queriedOnly, (Method) reflectExecutable);
+            }
+
+            if (!throwMissingRegistrationErrors() && RecordSupport.singleton().isRecord(declaringClass)) {
                 pendingRecordClasses.computeIfPresent(declaringClass, (clazz, unregisteredAccessors) -> {
                     if (unregisteredAccessors.remove(reflectExecutable) && unregisteredAccessors.isEmpty()) {
                         registerRecordComponents(declaringClass);
@@ -210,10 +333,6 @@ private void registerMethod(boolean queriedOnly, Executable reflectExecutable) {
                     return unregisteredAccessors;
                 });
             }
-
-            if (declaringType.isAnnotation() && !analysisMethod.isConstructor()) {
-                processAnnotationMethod(queriedOnly, (Method) reflectExecutable);
-            }
         }
 
         /*
@@ -229,9 +348,36 @@ private void registerMethod(boolean queriedOnly, Executable reflectExecutable) {
         }
     }
 
+    @Override
+    public void registerMethodLookup(ConfigurationCondition condition, Class declaringClass, String methodName, Class... parameterTypes) {
+        checkNotSealed();
+        try {
+            register(condition, true, declaringClass.getDeclaredMethod(methodName, parameterTypes));
+        } catch (NoSuchMethodException e) {
+            registerConditionalConfiguration(condition, () -> negativeMethodLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet())
+                            .add(new AnalysisMethod.Signature(methodName, metaAccess.lookupJavaTypes(parameterTypes))));
+        }
+    }
+
+    @Override
+    public void registerConstructorLookup(ConfigurationCondition condition, Class declaringClass, Class... parameterTypes) {
+        checkNotSealed();
+        try {
+            register(condition, true, declaringClass.getDeclaredConstructor(parameterTypes));
+        } catch (NoSuchMethodException e) {
+            registerConditionalConfiguration(condition,
+                            () -> negativeConstructorLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet())
+                                            .add(metaAccess.lookupJavaTypes(parameterTypes)));
+        }
+    }
+
     @Override
     public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) {
         checkNotSealed();
+        registerInternal(condition, fields);
+    }
+
+    private void registerInternal(ConfigurationCondition condition, Field... fields) {
         register(analysisUniverse -> registerConditionalConfiguration(condition, () -> {
             for (Field field : fields) {
                 analysisUniverse.getBigbang().postTask(debug -> registerField(field));
@@ -239,6 +385,23 @@ public void register(ConfigurationCondition condition, boolean finalIsWritable,
         }));
     }
 
+    @Override
+    public void registerAllFieldsQuery(ConfigurationCondition condition, Class clazz) {
+        checkNotSealed();
+        for (Class current = clazz; current != null; current = current.getSuperclass()) {
+            final Class currentLambda = current;
+            registerConditionalConfiguration(condition, () -> setQueryFlag(currentLambda, ALL_FIELDS_FLAG));
+        }
+        registerInternal(condition, clazz.getFields());
+    }
+
+    @Override
+    public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class clazz) {
+        checkNotSealed();
+        registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_DECLARED_FIELDS_FLAG));
+        registerInternal(condition, clazz.getDeclaredFields());
+    }
+
     private void registerField(Field reflectField) {
         if (SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) {
             return;
@@ -253,9 +416,9 @@ private void registerField(Field reflectField) {
              * The image needs to know about subtypes shadowing fields registered for reflection to
              * ensure the correctness of run-time reflection queries.
              */
-            analysisAccess.registerSubtypeReachabilityHandler((access, subType) -> {
-                universe.getBigbang().postTask(debug -> checkSubtypeForOverridingField(analysisField, metaAccess.lookupJavaType(subType)));
-            }, declaringClass.getJavaClass());
+            analysisAccess.registerSubtypeReachabilityHandler(
+                            (access, subType) -> universe.getBigbang().postTask(debug -> checkSubtypeForOverridingField(analysisField, metaAccess.lookupJavaType(subType))),
+                            declaringClass.getJavaClass());
 
             if (declaringClass.isAnnotation()) {
                 processAnnotationField(reflectField);
@@ -263,6 +426,16 @@ private void registerField(Field reflectField) {
         }
     }
 
+    @Override
+    public void registerFieldLookup(ConfigurationCondition condition, Class declaringClass, String fieldName) {
+        checkNotSealed();
+        try {
+            registerInternal(condition, declaringClass.getDeclaredField(fieldName));
+        } catch (NoSuchFieldException e) {
+            registerConditionalConfiguration(condition, () -> negativeFieldLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()).add(fieldName));
+        }
+    }
+
     private void checkNotSealed() {
         if (sealed) {
             throw UserError.abort("Too late to add classes, methods, and fields for reflective access. Registration must happen in a Feature before the analysis has finished.");
@@ -359,15 +532,20 @@ private void registerTypesForClass(AnalysisType analysisType, Class clazz) {
         registerTypesForGenericSignature(queryGenericInfo(clazz::getGenericInterfaces));
 
         registerTypesForEnclosingMethodInfo(clazz);
-        maybeRegisterRecordComponents(clazz);
+        if (!throwMissingRegistrationErrors()) {
+            maybeRegisterRecordComponents(clazz);
+        }
 
         registerTypesForAnnotations(analysisType);
         registerTypesForTypeAnnotations(analysisType);
     }
 
     private void registerRecordComponents(Class clazz) {
-        Object[] recordComponents = RecordSupport.singleton().getRecordComponents(clazz);
-        for (Object recordComponent : recordComponents) {
+        RecordComponent[] recordComponents = clazz.getRecordComponents();
+        if (recordComponents == null) {
+            return;
+        }
+        for (RecordComponent recordComponent : recordComponents) {
             registerTypesForRecordComponent(recordComponent);
         }
         registeredRecordComponents.put(clazz, recordComponents);
@@ -528,9 +706,10 @@ private void registerTypesForGenericSignature(Type type, int dimension) {
         }
     }
 
-    private void registerTypesForRecordComponent(Object recordComponent) {
-        registerTypesForAnnotations((AnnotatedElement) recordComponent);
-        registerTypesForTypeAnnotations((AnnotatedElement) recordComponent);
+    private void registerTypesForRecordComponent(RecordComponent recordComponent) {
+        register(ConfigurationCondition.alwaysTrue(), true, recordComponent.getAccessor());
+        registerTypesForAnnotations(recordComponent);
+        registerTypesForTypeAnnotations(recordComponent);
     }
 
     private void registerTypesForAnnotations(AnnotatedElement annotatedElement) {
@@ -683,7 +862,9 @@ private static void reportLinkingErrors(Class clazz, List errors)
     protected void afterAnalysis() {
         sealed = true;
         processedTypes.clear();
-        pendingRecordClasses.clear();
+        if (!throwMissingRegistrationErrors()) {
+            pendingRecordClasses.clear();
+        }
     }
 
     @Override
@@ -692,6 +873,10 @@ public Map, Set>> getReflectionInnerClasses() {
         return Collections.unmodifiableMap(innerClasses);
     }
 
+    public int getEnabledReflectionQueries(Class clazz) {
+        return enabledQueriesFlags.getOrDefault(clazz, 0);
+    }
+
     @Override
     public Map getReflectionFields() {
         assert sealed;
@@ -780,6 +965,21 @@ public Map getHeapReflectionExecutables() {
         return Collections.unmodifiableMap(heapMethods);
     }
 
+    @Override
+    public Map> getNegativeFieldQueries() {
+        return Collections.unmodifiableMap(negativeFieldLookups);
+    }
+
+    @Override
+    public Map> getNegativeMethodQueries() {
+        return Collections.unmodifiableMap(negativeMethodLookups);
+    }
+
+    @Override
+    public Map> getNegativeConstructorQueries() {
+        return Collections.unmodifiableMap(negativeConstructorLookups);
+    }
+
     private static final AnnotationValue[] NO_ANNOTATIONS = new AnnotationValue[0];
 
     public AnnotationValue[] getAnnotationData(AnnotatedElement element) {
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java
index 2ceebd93ae470..fe04ab4c66abb 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java
@@ -32,6 +32,7 @@
 import com.oracle.graal.pointsto.ObjectScanner.ScanReason;
 import com.oracle.graal.pointsto.meta.AnalysisField;
 import com.oracle.graal.pointsto.meta.AnalysisMethod;
+import com.oracle.graal.pointsto.meta.AnalysisType;
 
 public interface ReflectionHostedSupport {
     Map, Set>> getReflectionInnerClasses();
@@ -68,6 +69,12 @@ public interface ReflectionHostedSupport {
 
     Map getHeapReflectionExecutables();
 
+    Map> getNegativeFieldQueries();
+
+    Map> getNegativeMethodQueries();
+
+    Map> getNegativeConstructorQueries();
+
     int getReflectionMethodsCount();
 
     int getReflectionFieldsCount();
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadata.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadata.java
index 3e0e1a13b4f1a..40c132d6458de 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadata.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadata.java
@@ -50,30 +50,36 @@ static class ClassMetadata extends AnnotatedElementMetadata {
         final Object enclosingMethodInfo;
         final RecordComponentMetadata[] recordComponents;
         final HostedType[] permittedSubclasses;
-        final int classAccessFlags;
+        final HostedType[] nestMembers;
+        final JavaConstant[] signers;
+        final int flags;
 
-        ClassMetadata(HostedType[] classes, Object enclosingMethodInfo, RecordComponentMetadata[] recordComponents, HostedType[] permittedSubclasses, int classAccessFlags,
-                        AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations) {
+        ClassMetadata(HostedType[] classes, Object enclosingMethodInfo, RecordComponentMetadata[] recordComponents, HostedType[] permittedSubclasses, HostedType[] nestMembers, JavaConstant[] signers,
+                        int flags, AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations) {
             super(annotations, typeAnnotations);
             this.classes = classes;
             this.enclosingMethodInfo = enclosingMethodInfo;
             this.recordComponents = recordComponents;
             this.permittedSubclasses = permittedSubclasses;
-            this.classAccessFlags = classAccessFlags;
+            this.nestMembers = nestMembers;
+            this.signers = signers;
+            this.flags = flags;
         }
     }
 
     static class AccessibleObjectMetadata extends AnnotatedElementMetadata {
         final boolean complete;
+        final boolean negative;
         final JavaConstant heapObject;
         final HostedType declaringType;
         final int modifiers;
         final String signature;
 
-        AccessibleObjectMetadata(boolean complete, JavaConstant heapObject, HostedType declaringType, int modifiers, String signature, AnnotationValue[] annotations,
+        AccessibleObjectMetadata(boolean complete, boolean negative, JavaConstant heapObject, HostedType declaringType, int modifiers, String signature, AnnotationValue[] annotations,
                         TypeAnnotationValue[] typeAnnotations) {
             super(annotations, typeAnnotations);
             this.complete = complete;
+            this.negative = negative;
             this.heapObject = heapObject;
             this.declaringType = declaringType;
             this.modifiers = modifiers;
@@ -89,9 +95,9 @@ static class FieldMetadata extends AccessibleObjectMetadata {
         final int offset;
         final String deletedReason;
 
-        private FieldMetadata(boolean complete, boolean hiding, JavaConstant heapObject, HostedType declaringType, String name, HostedType type, int modifiers, boolean trustedFinal, String signature,
-                        AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations, int offset, String deletedReason) {
-            super(complete, heapObject, declaringType, modifiers, signature, annotations, typeAnnotations);
+        private FieldMetadata(boolean complete, boolean negative, boolean hiding, JavaConstant heapObject, HostedType declaringType, String name, HostedType type, int modifiers, boolean trustedFinal,
+                        String signature, AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations, int offset, String deletedReason) {
+            super(complete, negative, heapObject, declaringType, modifiers, signature, annotations, typeAnnotations);
             this.hiding = hiding;
             this.name = name;
             this.type = type;
@@ -103,22 +109,22 @@ private FieldMetadata(boolean complete, boolean hiding, JavaConstant heapObject,
         /* Field registered for reflection */
         FieldMetadata(HostedType declaringType, String name, HostedType type, int modifiers, boolean trustedFinal, String signature, AnnotationValue[] annotations,
                         TypeAnnotationValue[] typeAnnotations, int offset, String deletedReason) {
-            this(true, false, null, declaringType, name, type, modifiers, trustedFinal, signature, annotations, typeAnnotations, offset, deletedReason);
+            this(true, false, false, null, declaringType, name, type, modifiers, trustedFinal, signature, annotations, typeAnnotations, offset, deletedReason);
         }
 
         /* Field in heap */
         FieldMetadata(boolean registered, JavaConstant heapObject, AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations) {
-            this(registered, false, heapObject, null, null, null, 0, false, null, annotations, typeAnnotations, LOC_UNINITIALIZED, null);
+            this(registered, false, false, heapObject, null, null, null, 0, false, null, annotations, typeAnnotations, LOC_UNINITIALIZED, null);
         }
 
         /* Hiding field */
         FieldMetadata(HostedType declaringType, String name, HostedType type, int modifiers) {
-            this(false, true, null, declaringType, name, type, modifiers, false, null, null, null, LOC_UNINITIALIZED, null);
+            this(false, false, true, null, declaringType, name, type, modifiers, false, null, null, null, LOC_UNINITIALIZED, null);
         }
 
-        /* Reachable field */
-        FieldMetadata(HostedType declaringType, String name) {
-            this(false, false, null, declaringType, name, null, 0, false, null, null, null, LOC_UNINITIALIZED, null);
+        /* Reachable or negative query field */
+        FieldMetadata(HostedType declaringType, String name, boolean negative) {
+            this(false, negative, false, null, declaringType, name, null, 0, false, null, null, null, LOC_UNINITIALIZED, null);
         }
     }
 
@@ -129,10 +135,10 @@ static class ExecutableMetadata extends AccessibleObjectMetadata {
         final ReflectParameterMetadata[] reflectParameters;
         final JavaConstant accessor;
 
-        ExecutableMetadata(boolean complete, JavaConstant heapObject, HostedType declaringType, Object[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature,
+        ExecutableMetadata(boolean complete, boolean negative, JavaConstant heapObject, HostedType declaringType, Object[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature,
                         AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters,
                         JavaConstant accessor) {
-            super(complete, heapObject, declaringType, modifiers, signature, annotations, typeAnnotations);
+            super(complete, negative, heapObject, declaringType, modifiers, signature, annotations, typeAnnotations);
             this.parameterTypes = parameterTypes;
             this.exceptionTypes = exceptionTypes;
             this.parameterAnnotations = parameterAnnotations;
@@ -147,10 +153,11 @@ static class MethodMetadata extends ExecutableMetadata {
         final HostedType returnType;
         final AnnotationMemberValue annotationDefault;
 
-        private MethodMetadata(boolean complete, boolean hiding, JavaConstant heapObject, HostedType declaringClass, String name, Object[] parameterTypes, int modifiers, HostedType returnType,
-                        HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, AnnotationMemberValue annotationDefault,
-                        TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) {
-            super(complete, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, accessor);
+        private MethodMetadata(boolean complete, boolean negative, boolean hiding, JavaConstant heapObject, HostedType declaringClass, String name, Object[] parameterTypes, int modifiers,
+                        HostedType returnType, HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations,
+                        AnnotationMemberValue annotationDefault, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) {
+            super(complete, negative, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters,
+                            accessor);
             this.hiding = hiding;
             this.name = name;
             this.returnType = returnType;
@@ -161,50 +168,61 @@ private MethodMetadata(boolean complete, boolean hiding, JavaConstant heapObject
         MethodMetadata(HostedType declaringClass, String name, HostedType[] parameterTypes, int modifiers, HostedType returnType, HostedType[] exceptionTypes, String signature,
                         AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, AnnotationMemberValue annotationDefault, TypeAnnotationValue[] typeAnnotations,
                         ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) {
-            this(true, false, null, declaringClass, name, parameterTypes, modifiers, returnType, exceptionTypes, signature, annotations, parameterAnnotations, annotationDefault, typeAnnotations,
-                            reflectParameters, accessor);
+            this(true, false, false, null, declaringClass, name, parameterTypes, modifiers, returnType, exceptionTypes, signature, annotations, parameterAnnotations, annotationDefault,
+                            typeAnnotations, reflectParameters, accessor);
         }
 
         /* Method in heap */
         MethodMetadata(boolean registered, JavaConstant heapObject, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, AnnotationMemberValue annotationDefault,
                         TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters) {
-            this(registered, false, heapObject, null, null, null, 0, null, null, null, annotations, parameterAnnotations, annotationDefault, typeAnnotations, reflectParameters, null);
+            this(registered, false, false, heapObject, null, null, null, 0, null, null, null, annotations, parameterAnnotations, annotationDefault, typeAnnotations, reflectParameters, null);
         }
 
         /* Hiding method */
         MethodMetadata(HostedType declaringClass, String name, HostedType[] parameterTypes, int modifiers, HostedType returnType) {
-            this(false, true, null, declaringClass, name, parameterTypes, modifiers, returnType, null, null, null, null, null, null, null, null);
+            this(false, false, true, null, declaringClass, name, parameterTypes, modifiers, returnType, null, null, null, null, null, null, null, null);
         }
 
         /* Reachable method */
         MethodMetadata(HostedType declaringClass, String name, String[] parameterTypeNames) {
-            this(false, false, null, declaringClass, name, parameterTypeNames, 0, null, null, null, null, null, null, null, null, null);
+            this(false, false, false, null, declaringClass, name, parameterTypeNames, 0, null, null, null, null, null, null, null, null, null);
+        }
+
+        /* Negative query method */
+        MethodMetadata(HostedType declaringClass, String name, HostedType[] parameterTypes) {
+            this(false, true, false, null, declaringClass, name, parameterTypes, 0, null, null, null, null, null, null, null, null, null);
         }
     }
 
     static class ConstructorMetadata extends ExecutableMetadata {
 
-        private ConstructorMetadata(boolean complete, JavaConstant heapObject, HostedType declaringClass, Object[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature,
-                        AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters,
+        private ConstructorMetadata(boolean complete, boolean negative, JavaConstant heapObject, HostedType declaringClass, Object[] parameterTypes, int modifiers, HostedType[] exceptionTypes,
+                        String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters,
                         JavaConstant accessor) {
-            super(complete, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, accessor);
+            super(complete, negative, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters,
+                            accessor);
         }
 
         /* Constructor registered for reflection */
         ConstructorMetadata(HostedType declaringClass, HostedType[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations,
                         AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) {
-            this(true, null, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, accessor);
+            this(true, false, null, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, accessor);
         }
 
         /* Constructor in heap */
         ConstructorMetadata(boolean registered, JavaConstant heapObject, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations,
                         ReflectParameterMetadata[] reflectParameters) {
-            this(registered, heapObject, null, null, 0, null, null, annotations, parameterAnnotations, typeAnnotations, reflectParameters, null);
+            this(registered, false, heapObject, null, null, 0, null, null, annotations, parameterAnnotations, typeAnnotations, reflectParameters, null);
         }
 
         /* Reachable constructor */
         ConstructorMetadata(HostedType declaringClass, String[] parameterTypeNames) {
-            this(false, null, declaringClass, parameterTypeNames, 0, null, null, null, null, null, null, null);
+            this(false, false, null, declaringClass, parameterTypeNames, 0, null, null, null, null, null, null, null);
+        }
+
+        /* Negative query constructor */
+        ConstructorMetadata(HostedType declaringClass, HostedType[] parameterTypes) {
+            this(false, true, null, declaringClass, parameterTypes, 0, null, null, null, null, null, null, null);
         }
     }
 
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadataEncoderImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadataEncoderImpl.java
index 1e958c21cb01d..9d7dab52faca4 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadataEncoderImpl.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadataEncoderImpl.java
@@ -26,10 +26,13 @@
 
 import static com.oracle.svm.core.reflect.ReflectionMetadataDecoder.NO_DATA;
 import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_FLAGS_MASK;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_NEST_MEMBERS_FLAG;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_PERMITTED_SUBCLASSES_FLAG;
 import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.COMPLETE_FLAG_MASK;
 import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.FIRST_ERROR_INDEX;
 import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.HIDING_FLAG_MASK;
 import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.IN_HEAP_FLAG_MASK;
+import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.NEGATIVE_FLAG_MASK;
 import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.NULL_OBJECT;
 import static com.oracle.svm.hosted.reflect.ReflectionMetadata.AccessibleObjectMetadata;
 import static com.oracle.svm.hosted.reflect.ReflectionMetadata.ClassMetadata;
@@ -60,6 +63,7 @@
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
+import org.graalvm.collections.Pair;
 import org.graalvm.compiler.core.common.util.TypeConversion;
 import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter;
 import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
@@ -252,7 +256,12 @@ public void addClassMetadata(MetaAccessProvider metaAccess, HostedType type, Cla
         Object enclosingMethodInfo = getEnclosingMethodInfo(javaClass);
         RecordComponentMetadata[] recordComponents = getRecordComponents(metaAccess, type, javaClass);
         Class[] permittedSubclasses = getPermittedSubclasses(metaAccess, javaClass);
+        Class[] nestMembers = getNestMembers(metaAccess, javaClass);
+        Object[] signers = javaClass.getSigners();
         int classAccessFlags = Reflection.getClassAccessFlags(javaClass);
+        int enabledQueries = dataBuilder.getEnabledReflectionQueries(javaClass);
+        VMError.guarantee((classAccessFlags & enabledQueries) == 0);
+        int flags = classAccessFlags | enabledQueries;
 
         /* Register string and class values in annotations */
         encoders.sourceClasses.addObject(javaClass);
@@ -263,11 +272,20 @@ public void addClassMetadata(MetaAccessProvider metaAccess, HostedType type, Cla
         }
         HostedType[] innerTypes = registerClassValues(metaAccess, innerClasses);
         HostedType[] permittedSubtypes = (permittedSubclasses != null) ? registerClassValues(metaAccess, permittedSubclasses) : null;
+        HostedType[] nestMemberTypes = (nestMembers != null) ? registerClassValues(metaAccess, nestMembers) : null;
+        JavaConstant[] signerConstants = null;
+        if (signers != null) {
+            signerConstants = new JavaConstant[signers.length];
+            for (int i = 0; i < signers.length; ++i) {
+                signerConstants[i] = SubstrateObjectConstant.forObject(signers[i]);
+                encoders.objectConstants.addObject(signerConstants[i]);
+            }
+        }
         AnalysisType analysisType = type.getWrapped();
         AnnotationValue[] annotations = registerAnnotationValues(analysisType);
         TypeAnnotationValue[] typeAnnotations = registerTypeAnnotationValues(analysisType);
 
-        registerClass(type, new ClassMetadata(innerTypes, enclosingMethodInfo, recordComponents, permittedSubtypes, classAccessFlags, annotations, typeAnnotations));
+        registerClass(type, new ClassMetadata(innerTypes, enclosingMethodInfo, recordComponents, permittedSubtypes, nestMemberTypes, signerConstants, flags, annotations, typeAnnotations));
     }
 
     private void registerError(Throwable error) {
@@ -300,32 +318,41 @@ private void registerEnclosingMethodInfo(Object[] enclosingMethodInfo) {
 
     private static final Method getPermittedSubclasses = ReflectionUtil.lookupMethod(true, Class.class, "getPermittedSubclasses");
 
-    private static Class[] getPermittedSubclasses(MetaAccessProvider metaAccess, Class clazz) {
-        if (JavaVersionUtil.JAVA_SPEC < 17) {
+    private Class[] getPermittedSubclasses(MetaAccessProvider metaAccess, Class clazz) {
+        if (JavaVersionUtil.JAVA_SPEC < 17 || (dataBuilder.getEnabledReflectionQueries(clazz) & ALL_PERMITTED_SUBCLASSES_FLAG) == 0) {
             return null;
         }
         try {
             Class[] permittedSubclasses = (Class[]) getPermittedSubclasses.invoke(clazz);
-            if (permittedSubclasses == null) {
-                return null;
-            }
-            Set> reachablePermittedSubclasses = new HashSet<>();
-            for (Class permittedSubclass : permittedSubclasses) {
-                try {
-                    HostedType hostedType = ((HostedMetaAccess) metaAccess).optionalLookupJavaType(permittedSubclass).orElse(null);
-                    if (hostedType != null && hostedType.getWrapped().isReachable()) {
-                        reachablePermittedSubclasses.add(permittedSubclass);
-                    }
-                } catch (DeletedElementException dee) {
-                    // permitted subclass has been deleted -> ignore
-                }
-            }
-            return reachablePermittedSubclasses.toArray(new Class[0]);
+            return filterDeletedClasses(metaAccess, permittedSubclasses);
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw VMError.shouldNotReachHere(e);
         }
     }
 
+    private Class[] getNestMembers(MetaAccessProvider metaAccess, Class clazz) {
+        if (JavaVersionUtil.JAVA_SPEC < 17 || (dataBuilder.getEnabledReflectionQueries(clazz) & ALL_NEST_MEMBERS_FLAG) == 0) {
+            return null;
+        }
+        return filterDeletedClasses(metaAccess, clazz.getNestMembers());
+    }
+
+    private static Class[] filterDeletedClasses(MetaAccessProvider metaAccess, Class[] classes) {
+        if (classes == null) {
+            return null;
+        }
+        Set> reachableClasses = new HashSet<>();
+        for (Class clazz : classes) {
+            try {
+                metaAccess.lookupJavaType(clazz);
+                reachableClasses.add(clazz);
+            } catch (DeletedElementException dee) {
+                // class has been deleted -> ignore
+            }
+        }
+        return reachableClasses.toArray(new Class[0]);
+    }
+
     @Override
     public void addReflectionFieldMetadata(MetaAccessProvider metaAccess, HostedField hostedField, Field reflectField) {
         HostedType declaringType = hostedField.getDeclaringClass();
@@ -545,7 +572,7 @@ public void addReachableFieldMetadata(HostedField field) {
         /* Fill encoders with the necessary values. */
         encoders.sourceMethodNames.addObject(name);
 
-        registerField(declaringType, field, new FieldMetadata(declaringType, name));
+        registerField(declaringType, field, new FieldMetadata(declaringType, name, false));
     }
 
     @Override
@@ -570,6 +597,29 @@ public void addReachableExecutableMetadata(HostedMethod executable) {
         }
     }
 
+    @Override
+    public void addNegativeFieldQueryMetadata(HostedType declaringClass, String fieldName) {
+        encoders.sourceMethodNames.addObject(fieldName);
+        registerField(declaringClass, fieldName, new FieldMetadata(declaringClass, fieldName, true));
+    }
+
+    @Override
+    public void addNegativeMethodQueryMetadata(HostedType declaringClass, String methodName, HostedType[] parameterTypes) {
+        encoders.sourceMethodNames.addObject(methodName);
+        for (HostedType parameterType : parameterTypes) {
+            encoders.sourceClasses.addObject(parameterType.getJavaClass());
+        }
+        registerMethod(declaringClass, Pair.create(methodName, parameterTypes), new MethodMetadata(declaringClass, methodName, parameterTypes));
+    }
+
+    @Override
+    public void addNegativeConstructorQueryMetadata(HostedType declaringClass, HostedType[] parameterTypes) {
+        for (HostedType parameterType : parameterTypes) {
+            encoders.sourceClasses.addObject(parameterType.getJavaClass());
+        }
+        registerConstructor(declaringClass, parameterTypes, new ConstructorMetadata(declaringClass, parameterTypes));
+    }
+
     private static HostedType[] getParameterTypes(HostedMethod method) {
         HostedType[] parameterTypes = new HostedType[method.getSignature().getParameterCount(false)];
         for (int i = 0; i < parameterTypes.length; ++i) {
@@ -689,17 +739,19 @@ public void encodeAllAndInstall() {
             int typeAnnotationsIndex = addEncodedElement(buf, encodeTypeAnnotations(classMetadata.typeAnnotations));
             int classesEncodingIndex = encodeAndAddCollection(buf, classMetadata.classes, this::encodeType, false);
             int permittedSubclassesIndex = JavaVersionUtil.JAVA_SPEC >= 17 ? encodeAndAddCollection(buf, classMetadata.permittedSubclasses, this::encodeType, true) : NO_DATA;
-            if (anySet(enclosingMethodInfoIndex, annotationsIndex, typeAnnotationsIndex, classesEncodingIndex, permittedSubclassesIndex)) {
-                hub.setHubMetadata(enclosingMethodInfoIndex, annotationsIndex, typeAnnotationsIndex, classesEncodingIndex, permittedSubclassesIndex);
+            int nestMembersEncodingIndex = encodeAndAddCollection(buf, classMetadata.nestMembers, this::encodeType, true);
+            int signersEncodingIndex = encodeAndAddCollection(buf, classMetadata.signers, this::encodeObject, true);
+            if (anySet(enclosingMethodInfoIndex, annotationsIndex, typeAnnotationsIndex, classesEncodingIndex, permittedSubclassesIndex, nestMembersEncodingIndex, signersEncodingIndex)) {
+                hub.setHubMetadata(enclosingMethodInfoIndex, annotationsIndex, typeAnnotationsIndex, classesEncodingIndex, permittedSubclassesIndex, nestMembersEncodingIndex, signersEncodingIndex);
             }
 
             int fieldsIndex = encodeAndAddCollection(buf, getFields(declaringType), this::encodeField, false);
             int methodsIndex = encodeAndAddCollection(buf, getMethods(declaringType), this::encodeExecutable, false);
             int constructorsIndex = encodeAndAddCollection(buf, getConstructors(declaringType), this::encodeExecutable, false);
             int recordComponentsIndex = JavaVersionUtil.JAVA_SPEC >= 17 ? encodeAndAddCollection(buf, classMetadata.recordComponents, this::encodeRecordComponent, true) : NO_DATA;
-            int classAccessFlags = classMetadata.classAccessFlags;
-            if (anySet(fieldsIndex, methodsIndex, constructorsIndex, recordComponentsIndex) || classAccessFlags != hub.getModifiers()) {
-                hub.setReflectionMetadata(fieldsIndex, methodsIndex, constructorsIndex, recordComponentsIndex, classAccessFlags);
+            int classFlags = classMetadata.flags;
+            if (anySet(fieldsIndex, methodsIndex, constructorsIndex, recordComponentsIndex) || classFlags != hub.getModifiers()) {
+                hub.setReflectionMetadata(fieldsIndex, methodsIndex, constructorsIndex, recordComponentsIndex, classFlags);
             }
         }
         for (AccessibleObjectMetadata metadata : heapData) {
@@ -781,6 +833,7 @@ private void encodeField(UnsafeArrayTypeWriter buf, FieldMetadata field) {
         modifiers |= field.complete ? COMPLETE_FLAG_MASK : 0;
         modifiers |= field.heapObject != null ? IN_HEAP_FLAG_MASK : 0;
         modifiers |= field.hiding ? HIDING_FLAG_MASK : 0;
+        modifiers |= field.negative ? NEGATIVE_FLAG_MASK : 0;
         buf.putUV(modifiers);
         if (field.heapObject != null) {
             encodeObject(buf, field.heapObject);
@@ -811,6 +864,7 @@ private void encodeExecutable(UnsafeArrayTypeWriter buf, ExecutableMetadata exec
         modifiers |= executable.complete ? COMPLETE_FLAG_MASK : 0;
         modifiers |= executable.heapObject != null ? IN_HEAP_FLAG_MASK : 0;
         modifiers |= isHiding ? HIDING_FLAG_MASK : 0;
+        modifiers |= executable.negative ? NEGATIVE_FLAG_MASK : 0;
         buf.putUV(modifiers);
         if (executable.heapObject != null) {
             encodeObject(buf, executable.heapObject);
@@ -818,7 +872,7 @@ private void encodeExecutable(UnsafeArrayTypeWriter buf, ExecutableMetadata exec
             if (isMethod) {
                 encodeName(buf, ((MethodMetadata) executable).name);
             }
-            if (executable.complete || isHiding) {
+            if (executable.complete || isHiding || executable.negative) {
                 encodeArray(buf, (HostedType[]) executable.parameterTypes, parameterType -> encodeType(buf, parameterType));
             } else {
                 encodeArray(buf, (String[]) executable.parameterTypes, parameterTypeName -> encodeName(buf, parameterTypeName));