Skip to content

Commit

Permalink
Fixes for various linkage errors
Browse files Browse the repository at this point in the history
These happen when all elements are included for reflection.
  • Loading branch information
vjovanov committed Jan 14, 2025
1 parent aae7098 commit 2d50264
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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. " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
});
}
Expand All @@ -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);
}
});
}
Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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.
Expand All @@ -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);
}
}
Expand All @@ -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);
}
});
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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();
}
}
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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> T linkageSafeQuery(Class<?> clazz, T defaultValue, Function<Class<?>, T> f) {
var result = defaultValue;
try {
result = f.apply(clazz);
} catch (LinkageError e) {
/* nothing we can do */
}
return result;
}
}

0 comments on commit 2d50264

Please sign in to comment.