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 cd01947b822a..bdb8ad4079be 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 @@ -647,12 +647,20 @@ public void checkType(ResolvedJavaType type, AnalysisUniverse universe) { throw new UnsupportedFeatureException(message); } if (originalClass.isRecord()) { - for (var recordComponent : originalClass.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. " + - "Record type: `" + originalClass.getTypeName() + "`, component: `" + recordComponent.getName() + "` of type `" + recordComponent.getType().getTypeName() + "`"); + try { + for (var recordComponent : originalClass.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. " + + "Record type: `" + originalClass.getTypeName() + "`, component: `" + recordComponent.getName() + "` of type `" + recordComponent.getType().getTypeName() + "`"); + } } + } catch (LinkageError e) { + /* + * If a record refers to a missing/incomplete type then Class.getRecordComponents() + * will throw a LinkageError. It's safe to ignore this here since the Word type + * restriction applies to VM classes which should be fully defined. + */ } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java index 9fc9077ab053..79c3e7749295 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java @@ -158,6 +158,7 @@ public RuntimeMetadataEncoder create(SnippetReflectionProvider snippetReflection private final Map fieldLookupErrors = new HashMap<>(); private final Map methodLookupErrors = new HashMap<>(); private final Map constructorLookupErrors = new HashMap<>(); + private final Map recordComponentLookupErrors = new HashMap<>(); private final Set heapData = new HashSet<>(); @@ -684,6 +685,13 @@ public void addConstructorLookupError(HostedType declaringClass, Throwable excep constructorLookupErrors.put(declaringClass, exception); } + @Override + public void addRecordComponentsLookupError(HostedType declaringClass, Throwable exception) { + addType(declaringClass); + registerError(exception); + recordComponentLookupErrors.put(declaringClass, exception); + } + private static HostedType[] getParameterTypes(HostedMethod method) { HostedType[] parameterTypes = new HostedType[method.getSignature().getParameterCount(false)]; for (int i = 0; i < parameterTypes.length; ++i) { @@ -784,7 +792,7 @@ public void encodeAllAndInstall() { int fieldsIndex = encodeAndAddCollection(buf, getFields(declaringType), fieldLookupErrors.get(declaringType), this::encodeField, false); int methodsIndex = encodeAndAddCollection(buf, getMethods(declaringType), methodLookupErrors.get(declaringType), this::encodeExecutable, false); int constructorsIndex = encodeAndAddCollection(buf, getConstructors(declaringType), constructorLookupErrors.get(declaringType), this::encodeExecutable, false); - int recordComponentsIndex = encodeAndAddCollection(buf, classMetadata.recordComponents, this::encodeRecordComponent, true); + int recordComponentsIndex = encodeAndAddCollection(buf, classMetadata.recordComponents, recordComponentLookupErrors.get(declaringType), this::encodeRecordComponent, true); int classFlags = classMetadata.flags; if (anySet(fieldsIndex, methodsIndex, constructorsIndex, recordComponentsIndex) || classFlags != hub.getModifiers()) { hub.setReflectionMetadata(fieldsIndex, methodsIndex, constructorsIndex, recordComponentsIndex, classFlags); 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 f348152cc099..80a6dbd63fea 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 @@ -374,6 +374,11 @@ protected void buildRuntimeMetadata(DebugContext debug, SnippetReflectionProvide runtimeMetadataEncoder.addConstructorLookupError(type, error); }); + reflectionSupport.getRecordComponentLookupErrors().forEach((clazz, error) -> { + HostedType type = hMetaAccess.lookupJavaType(clazz); + runtimeMetadataEncoder.addRecordComponentsLookupError(type, error); + }); + Set includedFields = new HashSet<>(); Set includedMethods = new HashSet<>(); Map>> configurationFields = reflectionSupport.getReflectionFields(); @@ -831,6 +836,8 @@ public interface RuntimeMetadataEncoder extends EncodedRuntimeMetadataSupplier { void addConstructorLookupError(HostedType declaringClass, Throwable exception); + void addRecordComponentsLookupError(HostedType declaringClass, Throwable exception); + void encodeAllAndInstall(); Method getRoot = ReflectionUtil.lookupMethod(AccessibleObject.class, "getRoot"); 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..a96a47e681a7 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 @@ -141,6 +141,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl private final Map, Throwable> fieldLookupExceptions = new ConcurrentHashMap<>(); private final Map, Throwable> methodLookupExceptions = new ConcurrentHashMap<>(); private final Map, Throwable> constructorLookupExceptions = new ConcurrentHashMap<>(); + private final Map, Throwable> recordComponentsLookupExceptions = new ConcurrentHashMap<>(); // Intermediate bookkeeping private final Map> processedTypes = new ConcurrentHashMap<>(); @@ -501,6 +502,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 +517,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); } }); } @@ -611,7 +616,7 @@ 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) { @@ -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,14 +759,18 @@ private void registerTypesForClass(AnalysisType analysisType, Class clazz) { } private void registerRecordComponents(Class clazz) { - RecordComponent[] recordComponents = clazz.getRecordComponents(); - if (recordComponents == null) { - return; - } - for (RecordComponent recordComponent : recordComponents) { - registerTypesForRecordComponent(recordComponent); + try { + RecordComponent[] recordComponents = clazz.getRecordComponents(); + if (recordComponents == null) { + return; + } + for (RecordComponent recordComponent : recordComponents) { + registerTypesForRecordComponent(recordComponent); + } + registeredRecordComponents.put(clazz, recordComponents); + } catch (LinkageError le) { + registerLinkageError(clazz, le, recordComponentsLookupExceptions); } - registeredRecordComponents.put(clazz, recordComponents); } private void registerTypesForEnclosingMethodInfo(Class clazz) { @@ -1080,20 +1091,24 @@ private void maybeRegisterRecordComponents(Class clazz) { * components in that case will throw an exception at image run time, see * DynamicHub.getRecordComponents0(). */ - Method[] accessors = RecordUtils.getRecordComponentAccessorMethods(clazz); - Set unregisteredAccessors = ConcurrentHashMap.newKeySet(); - for (Method accessor : accessors) { - if (SubstitutionReflectivityFilter.shouldExclude(accessor, metaAccess, universe)) { - return; + try { + Method[] accessors = RecordUtils.getRecordComponentAccessorMethods(clazz); + Set unregisteredAccessors = ConcurrentHashMap.newKeySet(); + for (Method accessor : accessors) { + if (SubstitutionReflectivityFilter.shouldExclude(accessor, metaAccess, universe)) { + return; + } + unregisteredAccessors.add(accessor); } - unregisteredAccessors.add(accessor); - } - pendingRecordClasses.put(clazz, unregisteredAccessors); + pendingRecordClasses.put(clazz, unregisteredAccessors); - AnalysisType analysisType = metaAccess.lookupJavaType(clazz); - unregisteredAccessors.removeIf(accessor -> registeredMethods.getOrDefault(analysisType, Collections.emptyMap()).containsKey(metaAccess.lookupJavaMethod(accessor))); - if (unregisteredAccessors.isEmpty()) { - registerRecordComponents(clazz); + AnalysisType analysisType = metaAccess.lookupJavaType(clazz); + unregisteredAccessors.removeIf(accessor -> registeredMethods.getOrDefault(analysisType, Collections.emptyMap()).containsKey(metaAccess.lookupJavaMethod(accessor))); + if (unregisteredAccessors.isEmpty()) { + registerRecordComponents(clazz); + } + } catch (LinkageError le) { + registerLinkageError(clazz, le, recordComponentsLookupExceptions); } } @@ -1280,6 +1295,11 @@ public Map, Throwable> getConstructorLookupErrors() { return Collections.unmodifiableMap(constructorLookupExceptions); } + @Override + public Map, Throwable> getRecordComponentLookupErrors() { + return Collections.unmodifiableMap(recordComponentsLookupExceptions); + } + 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 c2c673f3dfee..2f44f1b10b40 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 @@ -85,6 +85,8 @@ public interface ReflectionHostedSupport { Map, Throwable> getConstructorLookupErrors(); + Map, Throwable> getRecordComponentLookupErrors(); + int getReflectionMethodsCount(); int getReflectionFieldsCount(); 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..c2e1c09821e7 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 @@ -527,11 +527,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); - }); + RuntimeReflection.registerAllDeclaredFields(iter); + try { + Arrays.stream(iter.getDeclaredFields()) + .map(Field::getType).forEach(type -> { + RuntimeReflection.registerAllDeclaredMethods(type); + RuntimeReflection.registerAllDeclaredFields(type); + RuntimeReflection.registerAllDeclaredConstructors(type); + }); + } catch (LinkageError l) { + /* Handled with registration above */ + } iter = iter.getSuperclass(); } } @@ -549,10 +555,14 @@ static void registerSerializationUIDElements(Class serializationTargetClass, RuntimeReflection.registerAllDeclaredMethods(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()); + try { + /* 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()); + } catch (LinkageError e) { + /* Handled by registrations above */ + } } RuntimeReflection.registerFieldLookup(serializationTargetClass, "serialPersistentFields"); } @@ -565,17 +575,23 @@ private static void registerForDeserialization(ConfigurationCondition cnd, Class ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, serializationTargetClass); if (serializationTargetClass.isRecord()) { - /* Serialization for records uses the canonical record constructor directly. */ - Executable[] methods = new Executable[]{RecordUtils.getCanonicalRecordConstructor(serializationTargetClass)}; - ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods); /* * Serialization for records invokes Class.getRecordComponents(). Registering all record * component accessor methods for reflection ensures that the record components are * available at run time. */ ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllRecordComponentsQuery(cnd, serializationTargetClass); - Executable[] methods1 = RecordUtils.getRecordComponentAccessorMethods(serializationTargetClass); - ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods1); + try { + /* Serialization for records uses the canonical record constructor directly. */ + Executable[] methods = new Executable[]{RecordUtils.getCanonicalRecordConstructor(serializationTargetClass)}; + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods); + Executable[] methods1 = RecordUtils.getRecordComponentAccessorMethods(serializationTargetClass); + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods1); + } catch (LinkageError le) { + /* + * Handled by the record component registration above. + */ + } } else if (Externalizable.class.isAssignableFrom(serializationTargetClass)) { RuntimeReflection.registerConstructorLookup(serializationTargetClass); } 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..43f661063491 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 @@ -86,7 +86,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; }