diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index b78b746918e3..49862dcad3f1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -26,6 +26,7 @@ import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors; +import java.lang.reflect.Modifier; import java.util.EnumSet; import java.util.Objects; @@ -154,7 +155,7 @@ public void registerNegativeQuery(ConfigurationCondition condition, String class @Platforms(Platform.HOSTED_ONLY.class) public void registerUnsafeAllocated(ConfigurationCondition condition, Class clazz) { - if (!clazz.isArray()) { + if (!clazz.isArray() && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) { var conditionSet = unsafeInstantiatedClasses.putIfAbsent(clazz, RuntimeConditionSet.createHosted(condition)); if (conditionSet != null) { conditionSet.addCondition(condition); 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 e87c47637c58..ba3a352dda0b 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 @@ -31,6 +31,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; +import java.lang.reflect.RecordComponent; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; @@ -646,7 +647,7 @@ public void checkType(ResolvedJavaType type, AnalysisUniverse universe) { throw new UnsupportedFeatureException(message); } if (originalClass.isRecord()) { - for (var recordComponent : originalClass.getRecordComponents()) { + for (var recordComponent : ReflectionUtil.linkageSafeQuery(originalClass, new RecordComponent[0], Class::getRecordComponents)) { if (WordBase.class.isAssignableFrom(recordComponent.getType())) { throw UserError.abort("Records cannot use Word types. " + "The equals/hashCode/toString implementation of records uses method handles, and Word types are not supported as parameters of method handle invocations. " + diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/RecordUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/RecordUtils.java index 1cfc6093df44..92052e9e8538 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/RecordUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/RecordUtils.java @@ -31,18 +31,19 @@ import java.util.Objects; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; public final class RecordUtils { public static Method[] getRecordComponentAccessorMethods(Class clazz) { - return Arrays.stream(clazz.getRecordComponents()) + return Arrays.stream(ReflectionUtil.linkageSafeQuery(clazz, new RecordComponent[0], Class::getRecordComponents)) .map(RecordComponent::getAccessor) .filter(Objects::nonNull) .toArray(Method[]::new); } public static Constructor getCanonicalRecordConstructor(Class clazz) { - Class[] paramTypes = Arrays.stream(clazz.getRecordComponents()) + Class[] paramTypes = Arrays.stream(ReflectionUtil.linkageSafeQuery(clazz, new RecordComponent[0], Class::getRecordComponents)) .map(RecordComponent::getType) .toArray(Class[]::new); try { 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 5b6941dc6bf0..d0d9ca7aeef2 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 @@ -64,9 +64,11 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.stream.Collectors; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeProxyCreation; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -501,6 +503,8 @@ public void registerMethodLookup(ConfigurationCondition condition, Class decl } catch (NoSuchMethodException e) { negativeMethodLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()) .add(new AnalysisMethod.Signature(methodName, metaAccess.lookupJavaTypes(parameterTypes))); + } catch (LinkageError le) { + registerLinkageError(declaringClass, le, methodLookupExceptions); } }); } @@ -514,6 +518,8 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class } catch (NoSuchMethodException e) { negativeConstructorLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()) .add(metaAccess.lookupJavaTypes(parameterTypes)); + } catch (LinkageError le) { + registerLinkageError(declaringClass, le, constructorLookupExceptions); } }); } @@ -525,7 +531,7 @@ public void register(ConfigurationCondition condition, boolean finalIsWritable, } @Override - public void registerAllFieldsQuery(ConfigurationCondition condition, Class clazz) { + public void registerAllFields(ConfigurationCondition condition, Class clazz) { registerAllFieldsQuery(condition, false, clazz); } @@ -544,7 +550,7 @@ public void registerAllFieldsQuery(ConfigurationCondition condition, boolean que } @Override - public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class clazz) { + public void registerAllDeclaredFields(ConfigurationCondition condition, Class clazz) { registerAllDeclaredFieldsQuery(condition, false, clazz); } @@ -575,16 +581,13 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel var classFields = registeredFields.computeIfAbsent(declaringClass, t -> new ConcurrentHashMap<>()); boolean exists = classFields.containsKey(analysisField); + // TODO open a ticket to disallow boolean shouldRegisterReachabilityHandler = classFields.isEmpty(); var cndValue = classFields.computeIfAbsent(analysisField, f -> new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectField)); - if (!queriedOnly) { - /* queryOnly methods are conditioned by the type itself */ - cndValue.getConditions().addCondition(cnd); - } - if (!exists) { - registerTypesForField(analysisField, reflectField, true); + registerTypesForField(analysisField, reflectField, queriedOnly); + // TODO bulk it up /* * The image needs to know about subtypes shadowing fields registered for reflection to * ensure the correctness of run-time reflection queries. @@ -611,10 +614,12 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel } /* - * We need to run this even if the method has already been registered, in case it was only + * We need to run this even if the field has already been registered, in case it was only * registered as queried. */ if (!queriedOnly) { + /* queryOnly methods are conditioned on the type itself */ + cndValue.getConditions().addCondition(cnd); registerTypesForField(analysisField, reflectField, false); } } @@ -631,6 +636,8 @@ public void registerFieldLookup(ConfigurationCondition condition, Class decla * not necessary. */ negativeFieldLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()).add(fieldName); + } catch (LinkageError le) { + registerLinkageError(declaringClass, le, fieldLookupExceptions); } }); } @@ -752,7 +759,7 @@ private void registerTypesForClass(AnalysisType analysisType, Class clazz) { } private void registerRecordComponents(Class clazz) { - RecordComponent[] recordComponents = clazz.getRecordComponents(); + RecordComponent[] recordComponents = ReflectionUtil.linkageSafeQuery(clazz, null, Class::getRecordComponents); if (recordComponents == null) { return; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java index 271f8268ac16..7de9a439a1d2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java @@ -50,6 +50,10 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import com.oracle.svm.core.MissingRegistrationSupport; +import com.oracle.svm.core.MissingRegistrationUtils; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -504,7 +508,8 @@ private void registerForSerialization(ConfigurationCondition cnd, Class seria * serialization class consistency, so need to register all constructors, methods and * fields. */ - registerSerializationUIDElements(serializationTargetClass, true); // if MRE + boolean legacyFullyRegister = !MissingRegistrationSupport.singleton().reportMissingRegistrationErrors(serializationTargetClass); + registerSerializationUIDElements(serializationTargetClass, legacyFullyRegister); /* * Required by jdk.internal.reflect.ReflectionFactory.newConstructorForSerialization @@ -527,11 +532,17 @@ private void registerForSerialization(ConfigurationCondition cnd, Class seria Class iter = serializationTargetClass; while (iter != null) { - Arrays.stream(iter.getDeclaredFields()).map(Field::getType).forEach(type -> { - RuntimeReflection.registerAllDeclaredMethods(type); - RuntimeReflection.registerAllDeclaredFields(type); - RuntimeReflection.registerAllDeclaredConstructors(type); - }); + + Arrays.stream(ReflectionUtil.linkageSafeQuery(iter, new Field[0], Class::getDeclaredFields)) + .map(Field::getType).forEach(type -> { + if (legacyFullyRegister) { + RuntimeReflection.registerAllDeclaredMethods(type); + RuntimeReflection.registerAllDeclaredFields(type); + RuntimeReflection.registerAllDeclaredConstructors(type); + } else { + RuntimeReflection.register(type); + } + }); iter = iter.getSuperclass(); } } @@ -550,11 +561,12 @@ static void registerSerializationUIDElements(Class serializationTargetClass, RuntimeReflection.registerAllDeclaredFields(serializationTargetClass); if (fullyRegister) { /* This is here a legacy that we can't remove as it is a breaking change */ - RuntimeReflection.register(serializationTargetClass.getDeclaredConstructors()); - RuntimeReflection.register(serializationTargetClass.getDeclaredMethods()); - RuntimeReflection.register(serializationTargetClass.getDeclaredFields()); + RuntimeReflection.register(ReflectionUtil.linkageSafeQuery(serializationTargetClass, new Constructor[0], Class::getDeclaredConstructors)); + RuntimeReflection.register(ReflectionUtil.linkageSafeQuery(serializationTargetClass, new Method[0], Class::getDeclaredMethods)); + RuntimeReflection.register(ReflectionUtil.linkageSafeQuery(serializationTargetClass, new Field[0], Class::getDeclaredFields)); + + RuntimeReflection.registerFieldLookup(serializationTargetClass, "serialPersistentFields"); } - RuntimeReflection.registerFieldLookup(serializationTargetClass, "serialPersistentFields"); } public void afterAnalysis() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java index 8ae0b0b2225f..5e8c0e7e1b41 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java @@ -36,6 +36,7 @@ import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.TargetClass; +import jdk.graal.compiler.api.replacements.Fold; import jdk.vm.ci.meta.ResolvedJavaType; /** @@ -74,6 +75,8 @@ public static boolean shouldExclude(Executable method, AnalysisMetaAccess metaAc return true; } else if (aMethod.isAnnotationPresent(Delete.class)) { return true; // accesses would fail at runtime + } else if (aMethod.isAnnotationPresent(Fold.class)) { + return true; // accesses can contain hosted elements } else if (aMethod.isSynthetic() && aMethod.getDeclaringClass().isAnnotationPresent(TargetClass.class)) { /* * Synthetic methods are usually methods injected by javac to provide access to diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java index f8a8c3c237f5..7b39d977c7aa 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java @@ -30,6 +30,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.function.Function; /** * This class contains utility methods for commonly used reflection functionality. Note that lookups @@ -86,7 +87,7 @@ public static Method lookupMethod(boolean optional, Class declaringClass, Str openModule(declaringClass); result.setAccessible(true); return result; - } catch (ReflectiveOperationException ex) { + } catch (ReflectiveOperationException | LinkageError ex) { if (optional) { return null; } @@ -238,4 +239,17 @@ public static void writeField(Class declaringClass, String fieldName, Object public static void writeStaticField(Class declaringClass, String fieldName, Object value) { writeField(declaringClass, fieldName, null, value); } + + /** + * Query a class value without worrying if the class can be properly linked. + */ + public static T linkageSafeQuery(Class clazz, T defaultValue, Function, T> f) { + var result = defaultValue; + try { + result = f.apply(clazz); + } catch (LinkageError e) { + /* nothing we can do */ + } + return result; + } }