diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index e612ff3293ec..ac531e7431e5 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 000000000000..47567d0daac5 --- /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. + *+ * 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
* 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 dc45967966e2..dc0d9b700f97 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 934c71888855..bd90ba19007b 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 d4b002372b12..e24975be8677 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 @@ -185,6 +188,7 @@ public SVMHost(OptionValues options, ClassLoader classLoader, ClassInitializatio multiMethodAnalysisPolicy = DEFAULT_MULTIMETHOD_ANALYSIS_POLICY; } parsingSupport = ImageSingletons.contains(SVMParsingSupport.class) ? ImageSingletons.lookup(SVMParsingSupport.class) : null; + this.reflectionSupport = ImageSingletons.lookup(RuntimeReflectionSupport.class); } private static Map> setupForbiddenTypes(OptionValues options) { @@ -312,6 +316,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 750f30e9ddd0..5dbccfa154b4 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 e1c3e9d606af..06b12f696877 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 7ceeb4899080..700961f041e4 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 000000000000..1725a1cc7752 --- /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 1ef40573259a..2ec6df5486d7 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 2bc43f9f91fb..3a8bbebe3c44 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 8a58beb19b08..12b3718b3aa5 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 2ceebd93ae47..fe04ab4c66ab 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 3e0e1a13b4f1..40c132d6458d 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 1e958c21cb01..9d7dab52faca 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));