Skip to content

[GR-46407] Rethrow reflection linkage errors at run-time #8021

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This changelog summarizes major changes to GraalVM Native Image.
* (GR-49807) Before this change the function `System#setSecurityManager` was always halting program execution with a VM error. This was inconvenient as the VM error prints an uncomprehensible error message and prevents further continuation of the program. For cases where the program is expected to throw an exception when `System#setSecurityManager` is called, execution on Native Image was not possible. Now, `System#setSecurityManager` throws an `java.lang.UnsupportedOperationException` by default. If the property `java.security.manager` is set to anything but `disallow` at program startup this function will throw a `java.lang.SecurityException` according to the Java spec.
* (GR-30433) Disallow the deprecated environment variable USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM=false.
* (GR-49655) Experimental support for parts of the [Foreign Function & Memory API](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ForeignInterface.md) (part of "Project Panama", [JEP 454](https://openjdk.org/jeps/454)) on AMD64. Must be enabled with `-H:+ForeignAPISupport` (requiring `-H:+UnlockExperimentalVMOptions`).
* (GR-46407) Correctly rethrow build-time linkage errors at run-time for registered reflection queries.

## GraalVM for JDK 21 (Internal Version 23.1.0)
* (GR-35746) Lower the default aligned chunk size from 1 MB to 512 KB for the serial and epsilon GCs, reducing memory usage and image size in many cases.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,10 @@ private static Object decodeObject(UnsafeArrayTypeReader buf) {
*/
@SuppressWarnings("unchecked")
private static <T> T[] decodeArray(UnsafeArrayTypeReader buf, Class<T> elementType, Function<Integer, T> elementDecoder) {
int length = buf.getUVInt();
int length = buf.getSVInt();
if (isErrorIndex(length)) {
decodeAndThrowError(length);
}
T[] result = (T[]) Array.newInstance(elementType, length);
int valueCount = 0;
for (int i = 0; i < length; ++i) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,26 @@ protected void buildRuntimeMetadata(DebugContext debug, SnippetReflectionProvide
}
}

reflectionSupport.getClassLookupErrors().forEach((clazz, error) -> {
HostedType type = hMetaAccess.lookupJavaType(clazz);
reflectionMetadataEncoder.addClassLookupError(type, error);
});

reflectionSupport.getFieldLookupErrors().forEach((clazz, error) -> {
HostedType type = hMetaAccess.lookupJavaType(clazz);
reflectionMetadataEncoder.addFieldLookupError(type, error);
});

reflectionSupport.getMethodLookupErrors().forEach((clazz, error) -> {
HostedType type = hMetaAccess.lookupJavaType(clazz);
reflectionMetadataEncoder.addMethodLookupError(type, error);
});

reflectionSupport.getConstructorLookupErrors().forEach((clazz, error) -> {
HostedType type = hMetaAccess.lookupJavaType(clazz);
reflectionMetadataEncoder.addConstructorLookupError(type, error);
});

Set<AnalysisField> includedFields = new HashSet<>();
Set<AnalysisMethod> includedMethods = new HashSet<>();
Map<AnalysisField, Field> configurationFields = reflectionSupport.getReflectionFields();
Expand Down Expand Up @@ -768,6 +788,14 @@ public interface ReflectionMetadataEncoder extends EncodedReflectionMetadataSupp

void addNegativeConstructorQueryMetadata(HostedType declaringClass, HostedType[] parameterTypes);

void addClassLookupError(HostedType declaringClass, Throwable exception);

void addFieldLookupError(HostedType declaringClass, Throwable exception);

void addMethodLookupError(HostedType declaringClass, Throwable exception);

void addConstructorLookupError(HostedType declaringClass, Throwable exception);

void encodeAllAndInstall();

Method getRoot = ReflectionUtil.lookupMethod(AccessibleObject.class, "getRoot");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.ConditionalConfigurationRegistry;
import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl;
import com.oracle.svm.hosted.LinkAtBuildTimeSupport;
import com.oracle.svm.hosted.annotation.AnnotationMemberValue;
import com.oracle.svm.hosted.annotation.AnnotationValue;
import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor;
Expand Down Expand Up @@ -130,6 +131,12 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl
private final Map<AnalysisType, Set<AnalysisMethod.Signature>> negativeMethodLookups = new ConcurrentHashMap<>();
private final Map<AnalysisType, Set<AnalysisType[]>> negativeConstructorLookups = new ConcurrentHashMap<>();

// Linkage error handling
private final Map<Class<?>, Throwable> classLookupExceptions = new ConcurrentHashMap<>();
private final Map<Class<?>, Throwable> fieldLookupExceptions = new ConcurrentHashMap<>();
private final Map<Class<?>, Throwable> methodLookupExceptions = new ConcurrentHashMap<>();
private final Map<Class<?>, Throwable> constructorLookupExceptions = new ConcurrentHashMap<>();

// Intermediate bookkeeping
private final Map<Type, Set<Integer>> processedTypes = new ConcurrentHashMap<>();
private final Map<Class<?>, Set<Method>> pendingRecordClasses;
Expand Down Expand Up @@ -189,7 +196,7 @@ public void registerAllClassesQuery(ConfigurationCondition condition, Class<?> c
registerClass(innerClass, false, !MissingRegistrationUtils.throwMissingRegistrationErrors());
}
} catch (LinkageError e) {
/* Ignore the error */
registerLinkageError(clazz, e, classLookupExceptions);
}
}

Expand All @@ -203,7 +210,7 @@ public void registerAllDeclaredClassesQuery(ConfigurationCondition condition, Cl
registerClass(innerClass, false, !MissingRegistrationUtils.throwMissingRegistrationErrors());
}
} catch (LinkageError e) {
/* Ignore the error */
registerLinkageError(clazz, e, classLookupExceptions);
}
}

Expand Down Expand Up @@ -326,7 +333,7 @@ public void registerAllMethodsQuery(ConfigurationCondition condition, boolean qu
try {
register(condition, queriedOnly, clazz.getMethods());
} catch (LinkageError e) {
/* Ignore the error */
registerLinkageError(clazz, e, methodLookupExceptions);
}
}

Expand All @@ -337,7 +344,7 @@ public void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, bo
try {
register(condition, queriedOnly, clazz.getDeclaredMethods());
} catch (LinkageError e) {
/* Ignore the error */
registerLinkageError(clazz, e, methodLookupExceptions);
}
}

Expand All @@ -351,7 +358,7 @@ public void registerAllConstructorsQuery(ConfigurationCondition condition, boole
try {
register(condition, queriedOnly, clazz.getConstructors());
} catch (LinkageError e) {
/* Ignore the error */
registerLinkageError(clazz, e, constructorLookupExceptions);
}
}

Expand All @@ -362,7 +369,7 @@ public void registerAllDeclaredConstructorsQuery(ConfigurationCondition conditio
try {
register(condition, queriedOnly, clazz.getDeclaredConstructors());
} catch (LinkageError e) {
/* Ignore the error */
registerLinkageError(clazz, e, constructorLookupExceptions);
}
}

Expand Down Expand Up @@ -459,7 +466,7 @@ public void registerAllFieldsQuery(ConfigurationCondition condition, Class<?> cl
try {
registerInternal(condition, clazz.getFields());
} catch (LinkageError e) {
/* Ignore the error */
registerLinkageError(clazz, e, fieldLookupExceptions);
}
}

Expand All @@ -470,7 +477,7 @@ public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Cla
try {
registerInternal(condition, clazz.getDeclaredFields());
} catch (LinkageError e) {
/* Ignore the error */
registerLinkageError(clazz, e, fieldLookupExceptions);
}
}

Expand Down Expand Up @@ -933,6 +940,14 @@ private void maybeRegisterRecordComponents(Class<?> clazz) {
}
}

private void registerLinkageError(Class<?> clazz, LinkageError error, Map<Class<?>, Throwable> errorMap) {
if (LinkAtBuildTimeSupport.singleton().linkAtBuildTime(clazz)) {
throw error;
} else {
errorMap.put(clazz, error);
}
}

private static void reportLinkingErrors(Class<?> clazz, List<Throwable> errors) {
if (errors.isEmpty()) {
return;
Expand Down Expand Up @@ -1072,6 +1087,26 @@ public Map<AnalysisType, Set<AnalysisType[]>> getNegativeConstructorQueries() {
return Collections.unmodifiableMap(negativeConstructorLookups);
}

@Override
public Map<Class<?>, Throwable> getClassLookupErrors() {
return Collections.unmodifiableMap(classLookupExceptions);
}

@Override
public Map<Class<?>, Throwable> getFieldLookupErrors() {
return Collections.unmodifiableMap(fieldLookupExceptions);
}

@Override
public Map<Class<?>, Throwable> getMethodLookupErrors() {
return Collections.unmodifiableMap(methodLookupExceptions);
}

@Override
public Map<Class<?>, Throwable> getConstructorLookupErrors() {
return Collections.unmodifiableMap(constructorLookupExceptions);
}

private static final AnnotationValue[] NO_ANNOTATIONS = new AnnotationValue[0];

public AnnotationValue[] getAnnotationData(AnnotatedElement element) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ public interface ReflectionHostedSupport {

Map<AnalysisType, Set<AnalysisType[]>> getNegativeConstructorQueries();

Map<Class<?>, Throwable> getClassLookupErrors();

Map<Class<?>, Throwable> getFieldLookupErrors();

Map<Class<?>, Throwable> getMethodLookupErrors();

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

int getReflectionMethodsCount();

int getReflectionFieldsCount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ public ReflectionMetadataEncoder create(SnippetReflectionProvider snippetReflect
private final Map<HostedType, Map<Object, MethodMetadata>> methodData = new HashMap<>();
private final Map<HostedType, Map<Object, ConstructorMetadata>> constructorData = new HashMap<>();

private final Map<HostedType, Throwable> classLookupErrors = new HashMap<>();
private final Map<HostedType, Throwable> fieldLookupErrors = new HashMap<>();
private final Map<HostedType, Throwable> methodLookupErrors = new HashMap<>();
private final Map<HostedType, Throwable> constructorLookupErrors = new HashMap<>();

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

private final Map<AccessibleObject, byte[]> annotationsEncodings = new HashMap<>();
Expand Down Expand Up @@ -633,6 +638,34 @@ public void addNegativeConstructorQueryMetadata(HostedType declaringClass, Hoste
registerConstructor(declaringClass, parameterTypes, new ConstructorMetadata(declaringClass, parameterTypes));
}

@Override
public void addClassLookupError(HostedType declaringClass, Throwable exception) {
addType(declaringClass);
registerError(exception);
classLookupErrors.put(declaringClass, exception);
}

@Override
public void addFieldLookupError(HostedType declaringClass, Throwable exception) {
addType(declaringClass);
registerError(exception);
fieldLookupErrors.put(declaringClass, exception);
}

@Override
public void addMethodLookupError(HostedType declaringClass, Throwable exception) {
addType(declaringClass);
registerError(exception);
methodLookupErrors.put(declaringClass, exception);
}

@Override
public void addConstructorLookupError(HostedType declaringClass, Throwable exception) {
addType(declaringClass);
registerError(exception);
constructorLookupErrors.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) {
Expand Down Expand Up @@ -712,17 +745,17 @@ public void encodeAllAndInstall() {
: addElement(buf, encodeEnclosingMethodInfo((Object[]) classMetadata.enclosingMethodInfo));
int annotationsIndex = addEncodedElement(buf, encodeAnnotations(classMetadata.annotations));
int typeAnnotationsIndex = addEncodedElement(buf, encodeTypeAnnotations(classMetadata.typeAnnotations));
int classesEncodingIndex = encodeAndAddCollection(buf, classMetadata.classes, this::encodeType, false);
int classesEncodingIndex = encodeAndAddCollection(buf, classMetadata.classes, classLookupErrors.get(declaringType), this::encodeType, false);
int permittedSubclassesIndex = encodeAndAddCollection(buf, classMetadata.permittedSubclasses, this::encodeType, true);
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 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 classFlags = classMetadata.flags;
if (anySet(fieldsIndex, methodsIndex, constructorsIndex, recordComponentsIndex) || classFlags != hub.getModifiers()) {
Expand Down Expand Up @@ -755,16 +788,23 @@ private int encodeErrorIndex(Throwable error) {
return encodedIndex;
}

private static <T> int encodeAndAddCollection(UnsafeArrayTypeWriter buf, T[] data, BiConsumer<UnsafeArrayTypeWriter, T> encodeCallback, boolean canBeNull) {
if (data == null || (!canBeNull && data.length == 0)) {
private <T> int encodeAndAddCollection(UnsafeArrayTypeWriter buf, T[] data, BiConsumer<UnsafeArrayTypeWriter, T> encodeCallback, boolean canBeNull) {
return encodeAndAddCollection(buf, data, null, encodeCallback, canBeNull);
}

private <T> int encodeAndAddCollection(UnsafeArrayTypeWriter buf, T[] data, Throwable lookupError, BiConsumer<UnsafeArrayTypeWriter, T> encodeCallback, boolean canBeNull) {
int offset = TypeConversion.asS4(buf.getBytesWritten());
if (lookupError != null) {
buf.putSV(encodeErrorIndex(lookupError));
} else if (data == null || (!canBeNull && data.length == 0)) {
/*
* We must encode a zero-length array if it does not have the same meaning as a null
* array (e.g. for permitted classes)
*/
return NO_DATA;
} else {
encodeArray(buf, data, element -> encodeCallback.accept(buf, element));
}
int offset = TypeConversion.asS4(buf.getBytesWritten());
encodeArray(buf, data, element -> encodeCallback.accept(buf, element));
return offset;
}

Expand Down Expand Up @@ -889,7 +929,7 @@ private void encodeObject(UnsafeArrayTypeWriter buf, JavaConstant object) {
}

private static <T> void encodeArray(UnsafeArrayTypeWriter buf, T[] array, Consumer<T> elementEncoder) {
buf.putUV(array.length);
buf.putSV(array.length);
for (T elem : array) {
elementEncoder.accept(elem);
}
Expand Down