Skip to content

Commit a5f0e66

Browse files
committed
Fixes for various linkage errors
These happen when all elements are included for reflection.
1 parent d7d4ddb commit a5f0e66

File tree

7 files changed

+102
-41
lines changed

7 files changed

+102
-41
lines changed

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -646,12 +646,20 @@ public void checkType(ResolvedJavaType type, AnalysisUniverse universe) {
646646
throw new UnsupportedFeatureException(message);
647647
}
648648
if (originalClass.isRecord()) {
649-
for (var recordComponent : originalClass.getRecordComponents()) {
650-
if (WordBase.class.isAssignableFrom(recordComponent.getType())) {
651-
throw UserError.abort("Records cannot use Word types. " +
652-
"The equals/hashCode/toString implementation of records uses method handles, and Word types are not supported as parameters of method handle invocations. " +
653-
"Record type: `" + originalClass.getTypeName() + "`, component: `" + recordComponent.getName() + "` of type `" + recordComponent.getType().getTypeName() + "`");
649+
try {
650+
for (var recordComponent : originalClass.getRecordComponents()) {
651+
if (WordBase.class.isAssignableFrom(recordComponent.getType())) {
652+
throw UserError.abort("Records cannot use Word types. " +
653+
"The equals/hashCode/toString implementation of records uses method handles, and Word types are not supported as parameters of method handle invocations. " +
654+
"Record type: `" + originalClass.getTypeName() + "`, component: `" + recordComponent.getName() + "` of type `" + recordComponent.getType().getTypeName() + "`");
655+
}
654656
}
657+
} catch (LinkageError e) {
658+
/*
659+
* If a record refers to a missing/incomplete type then Class.getRecordComponents()
660+
* will throw a LinkageError. It's safe to ignore this here since the Word type
661+
* restriction applies to VM classes which should be fully defined.
662+
*/
655663
}
656664
}
657665
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public RuntimeMetadataEncoder create(SnippetReflectionProvider snippetReflection
158158
private final Map<HostedType, Throwable> fieldLookupErrors = new HashMap<>();
159159
private final Map<HostedType, Throwable> methodLookupErrors = new HashMap<>();
160160
private final Map<HostedType, Throwable> constructorLookupErrors = new HashMap<>();
161+
private final Map<HostedType, Throwable> recordComponentLookupErrors = new HashMap<>();
161162

162163
private final Set<AccessibleObjectMetadata> heapData = new HashSet<>();
163164

@@ -684,6 +685,13 @@ public void addConstructorLookupError(HostedType declaringClass, Throwable excep
684685
constructorLookupErrors.put(declaringClass, exception);
685686
}
686687

688+
@Override
689+
public void addRecordComponentsLookupError(HostedType declaringClass, Throwable exception) {
690+
addType(declaringClass);
691+
registerError(exception);
692+
recordComponentLookupErrors.put(declaringClass, exception);
693+
}
694+
687695
private static HostedType[] getParameterTypes(HostedMethod method) {
688696
HostedType[] parameterTypes = new HostedType[method.getSignature().getParameterCount(false)];
689697
for (int i = 0; i < parameterTypes.length; ++i) {
@@ -784,7 +792,7 @@ public void encodeAllAndInstall() {
784792
int fieldsIndex = encodeAndAddCollection(buf, getFields(declaringType), fieldLookupErrors.get(declaringType), this::encodeField, false);
785793
int methodsIndex = encodeAndAddCollection(buf, getMethods(declaringType), methodLookupErrors.get(declaringType), this::encodeExecutable, false);
786794
int constructorsIndex = encodeAndAddCollection(buf, getConstructors(declaringType), constructorLookupErrors.get(declaringType), this::encodeExecutable, false);
787-
int recordComponentsIndex = encodeAndAddCollection(buf, classMetadata.recordComponents, this::encodeRecordComponent, true);
795+
int recordComponentsIndex = encodeAndAddCollection(buf, classMetadata.recordComponents, recordComponentLookupErrors.get(declaringType), this::encodeRecordComponent, true);
788796
int classFlags = classMetadata.flags;
789797
if (anySet(fieldsIndex, methodsIndex, constructorsIndex, recordComponentsIndex) || classFlags != hub.getModifiers()) {
790798
hub.setReflectionMetadata(fieldsIndex, methodsIndex, constructorsIndex, recordComponentsIndex, classFlags);

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,11 @@ protected void buildRuntimeMetadata(DebugContext debug, SnippetReflectionProvide
374374
runtimeMetadataEncoder.addConstructorLookupError(type, error);
375375
});
376376

377+
reflectionSupport.getRecordComponentLookupErrors().forEach((clazz, error) -> {
378+
HostedType type = hMetaAccess.lookupJavaType(clazz);
379+
runtimeMetadataEncoder.addRecordComponentsLookupError(type, error);
380+
});
381+
377382
Set<AnalysisField> includedFields = new HashSet<>();
378383
Set<AnalysisMethod> includedMethods = new HashSet<>();
379384
Map<AnalysisType, Map<AnalysisField, ConditionalRuntimeValue<Field>>> configurationFields = reflectionSupport.getReflectionFields();
@@ -831,6 +836,8 @@ public interface RuntimeMetadataEncoder extends EncodedRuntimeMetadataSupplier {
831836

832837
void addConstructorLookupError(HostedType declaringClass, Throwable exception);
833838

839+
void addRecordComponentsLookupError(HostedType declaringClass, Throwable exception);
840+
834841
void encodeAllAndInstall();
835842

836843
Method getRoot = ReflectionUtil.lookupMethod(AccessibleObject.class, "getRoot");

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl
141141
private final Map<Class<?>, Throwable> fieldLookupExceptions = new ConcurrentHashMap<>();
142142
private final Map<Class<?>, Throwable> methodLookupExceptions = new ConcurrentHashMap<>();
143143
private final Map<Class<?>, Throwable> constructorLookupExceptions = new ConcurrentHashMap<>();
144+
private final Map<Class<?>, Throwable> recordComponentsLookupExceptions = new ConcurrentHashMap<>();
144145

145146
// Intermediate bookkeeping
146147
private final Map<Type, Set<Integer>> processedTypes = new ConcurrentHashMap<>();
@@ -501,6 +502,8 @@ public void registerMethodLookup(ConfigurationCondition condition, Class<?> decl
501502
} catch (NoSuchMethodException e) {
502503
negativeMethodLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet())
503504
.add(new AnalysisMethod.Signature(methodName, metaAccess.lookupJavaTypes(parameterTypes)));
505+
} catch (LinkageError le) {
506+
registerLinkageError(declaringClass, le, methodLookupExceptions);
504507
}
505508
});
506509
}
@@ -514,6 +517,8 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class<?>
514517
} catch (NoSuchMethodException e) {
515518
negativeConstructorLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet())
516519
.add(metaAccess.lookupJavaTypes(parameterTypes));
520+
} catch (LinkageError le) {
521+
registerLinkageError(declaringClass, le, constructorLookupExceptions);
517522
}
518523
});
519524
}
@@ -611,7 +616,7 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel
611616
}
612617

613618
/*
614-
* We need to run this even if the method has already been registered, in case it was only
619+
* We need to run this even if the field has already been registered, in case it was only
615620
* registered as queried.
616621
*/
617622
if (!queriedOnly) {
@@ -631,6 +636,8 @@ public void registerFieldLookup(ConfigurationCondition condition, Class<?> decla
631636
* not necessary.
632637
*/
633638
negativeFieldLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()).add(fieldName);
639+
} catch (LinkageError le) {
640+
registerLinkageError(declaringClass, le, fieldLookupExceptions);
634641
}
635642
});
636643
}
@@ -752,14 +759,18 @@ private void registerTypesForClass(AnalysisType analysisType, Class<?> clazz) {
752759
}
753760

754761
private void registerRecordComponents(Class<?> clazz) {
755-
RecordComponent[] recordComponents = clazz.getRecordComponents();
756-
if (recordComponents == null) {
757-
return;
758-
}
759-
for (RecordComponent recordComponent : recordComponents) {
760-
registerTypesForRecordComponent(recordComponent);
762+
try {
763+
RecordComponent[] recordComponents = clazz.getRecordComponents();
764+
if (recordComponents == null) {
765+
return;
766+
}
767+
for (RecordComponent recordComponent : recordComponents) {
768+
registerTypesForRecordComponent(recordComponent);
769+
}
770+
registeredRecordComponents.put(clazz, recordComponents);
771+
} catch (LinkageError le) {
772+
registerLinkageError(clazz, le, recordComponentsLookupExceptions);
761773
}
762-
registeredRecordComponents.put(clazz, recordComponents);
763774
}
764775

765776
private void registerTypesForEnclosingMethodInfo(Class<?> clazz) {
@@ -1080,20 +1091,24 @@ private void maybeRegisterRecordComponents(Class<?> clazz) {
10801091
* components in that case will throw an exception at image run time, see
10811092
* DynamicHub.getRecordComponents0().
10821093
*/
1083-
Method[] accessors = RecordUtils.getRecordComponentAccessorMethods(clazz);
1084-
Set<Method> unregisteredAccessors = ConcurrentHashMap.newKeySet();
1085-
for (Method accessor : accessors) {
1086-
if (SubstitutionReflectivityFilter.shouldExclude(accessor, metaAccess, universe)) {
1087-
return;
1094+
try {
1095+
Method[] accessors = RecordUtils.getRecordComponentAccessorMethods(clazz);
1096+
Set<Method> unregisteredAccessors = ConcurrentHashMap.newKeySet();
1097+
for (Method accessor : accessors) {
1098+
if (SubstitutionReflectivityFilter.shouldExclude(accessor, metaAccess, universe)) {
1099+
return;
1100+
}
1101+
unregisteredAccessors.add(accessor);
10881102
}
1089-
unregisteredAccessors.add(accessor);
1090-
}
1091-
pendingRecordClasses.put(clazz, unregisteredAccessors);
1103+
pendingRecordClasses.put(clazz, unregisteredAccessors);
10921104

1093-
AnalysisType analysisType = metaAccess.lookupJavaType(clazz);
1094-
unregisteredAccessors.removeIf(accessor -> registeredMethods.getOrDefault(analysisType, Collections.emptyMap()).containsKey(metaAccess.lookupJavaMethod(accessor)));
1095-
if (unregisteredAccessors.isEmpty()) {
1096-
registerRecordComponents(clazz);
1105+
AnalysisType analysisType = metaAccess.lookupJavaType(clazz);
1106+
unregisteredAccessors.removeIf(accessor -> registeredMethods.getOrDefault(analysisType, Collections.emptyMap()).containsKey(metaAccess.lookupJavaMethod(accessor)));
1107+
if (unregisteredAccessors.isEmpty()) {
1108+
registerRecordComponents(clazz);
1109+
}
1110+
} catch (LinkageError le) {
1111+
registerLinkageError(clazz, le, recordComponentsLookupExceptions);
10971112
}
10981113
}
10991114

@@ -1280,6 +1295,11 @@ public Map<Class<?>, Throwable> getConstructorLookupErrors() {
12801295
return Collections.unmodifiableMap(constructorLookupExceptions);
12811296
}
12821297

1298+
@Override
1299+
public Map<Class<?>, Throwable> getRecordComponentLookupErrors() {
1300+
return Collections.unmodifiableMap(recordComponentsLookupExceptions);
1301+
}
1302+
12831303
private static final AnnotationValue[] NO_ANNOTATIONS = new AnnotationValue[0];
12841304

12851305
public AnnotationValue[] getAnnotationData(AnnotatedElement element) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public interface ReflectionHostedSupport {
8585

8686
Map<Class<?>, Throwable> getConstructorLookupErrors();
8787

88+
Map<Class<?>, Throwable> getRecordComponentLookupErrors();
89+
8890
int getReflectionMethodsCount();
8991

9092
int getReflectionFieldsCount();

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -527,11 +527,17 @@ private void registerForSerialization(ConfigurationCondition cnd, Class<?> seria
527527

528528
Class<?> iter = serializationTargetClass;
529529
while (iter != null) {
530-
Arrays.stream(iter.getDeclaredFields()).map(Field::getType).forEach(type -> {
531-
RuntimeReflection.registerAllDeclaredMethods(type);
532-
RuntimeReflection.registerAllDeclaredFields(type);
533-
RuntimeReflection.registerAllDeclaredConstructors(type);
534-
});
530+
RuntimeReflection.registerAllDeclaredFields(iter);
531+
try {
532+
Arrays.stream(iter.getDeclaredFields())
533+
.map(Field::getType).forEach(type -> {
534+
RuntimeReflection.registerAllDeclaredMethods(type);
535+
RuntimeReflection.registerAllDeclaredFields(type);
536+
RuntimeReflection.registerAllDeclaredConstructors(type);
537+
});
538+
} catch (LinkageError l) {
539+
/* Handled with registration above */
540+
}
535541
iter = iter.getSuperclass();
536542
}
537543
}
@@ -549,10 +555,14 @@ static void registerSerializationUIDElements(Class<?> serializationTargetClass,
549555
RuntimeReflection.registerAllDeclaredMethods(serializationTargetClass);
550556
RuntimeReflection.registerAllDeclaredFields(serializationTargetClass);
551557
if (fullyRegister) {
552-
/* This is here a legacy that we can't remove as it is a breaking change */
553-
RuntimeReflection.register(serializationTargetClass.getDeclaredConstructors());
554-
RuntimeReflection.register(serializationTargetClass.getDeclaredMethods());
555-
RuntimeReflection.register(serializationTargetClass.getDeclaredFields());
558+
try {
559+
/* This is here a legacy that we can't remove as it is a breaking change */
560+
RuntimeReflection.register(serializationTargetClass.getDeclaredConstructors());
561+
RuntimeReflection.register(serializationTargetClass.getDeclaredMethods());
562+
RuntimeReflection.register(serializationTargetClass.getDeclaredFields());
563+
} catch (LinkageError e) {
564+
/* Handled by registrations above */
565+
}
556566
}
557567
RuntimeReflection.registerFieldLookup(serializationTargetClass, "serialPersistentFields");
558568
}
@@ -565,17 +575,23 @@ private static void registerForDeserialization(ConfigurationCondition cnd, Class
565575
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, serializationTargetClass);
566576

567577
if (serializationTargetClass.isRecord()) {
568-
/* Serialization for records uses the canonical record constructor directly. */
569-
Executable[] methods = new Executable[]{RecordUtils.getCanonicalRecordConstructor(serializationTargetClass)};
570-
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods);
571578
/*
572579
* Serialization for records invokes Class.getRecordComponents(). Registering all record
573580
* component accessor methods for reflection ensures that the record components are
574581
* available at run time.
575582
*/
576583
ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllRecordComponentsQuery(cnd, serializationTargetClass);
577-
Executable[] methods1 = RecordUtils.getRecordComponentAccessorMethods(serializationTargetClass);
578-
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods1);
584+
try {
585+
/* Serialization for records uses the canonical record constructor directly. */
586+
Executable[] methods = new Executable[]{RecordUtils.getCanonicalRecordConstructor(serializationTargetClass)};
587+
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods);
588+
Executable[] methods1 = RecordUtils.getRecordComponentAccessorMethods(serializationTargetClass);
589+
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods1);
590+
} catch (LinkageError le) {
591+
/*
592+
* Handled by the record component registration above.
593+
*/
594+
}
579595
} else if (Externalizable.class.isAssignableFrom(serializationTargetClass)) {
580596
RuntimeReflection.registerConstructorLookup(serializationTargetClass);
581597
}

substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public static Method lookupMethod(boolean optional, Class<?> declaringClass, Str
8686
openModule(declaringClass);
8787
result.setAccessible(true);
8888
return result;
89-
} catch (ReflectiveOperationException ex) {
89+
} catch (ReflectiveOperationException | LinkageError ex) {
9090
if (optional) {
9191
return null;
9292
}

0 commit comments

Comments
 (0)