Skip to content

Commit

Permalink
[GR-13355] Fix incomplete classpath issues.
Browse files Browse the repository at this point in the history
PullRequest: graal/2754
  • Loading branch information
cstancu committed Jan 17, 2019
2 parents 7d978ce + 61faf00 commit dd0bae3
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,26 @@
* parameter and one of the referenced classes is not present on the classpath parsing the
* annotations will result in an ArrayStoreException instead of caching of a
* TypeNotPresentExceptionProxy. This is a problem in JDK8 but was fixed in JDK11+.
*
* This wrapper class also defends against incomplete class path issues. If the element for which
* annotations are queried is a JMVCI value, i.e., a HotSpotResolvedJavaField, or
* HotSpotResolvedJavaMethod, the annotations are read via HotSpotJDKReflection using the
* getFieldAnnotation()/getMethodAnnotation() methods which first construct the field/method object
* via CompilerToVM.asReflectionField()/CompilerToVM.asReflectionExecutable() which eagerly try to
* resolve the types referenced in the element signature. If a field declared type or a method
* return type is missing then JVMCI throws a NoClassDefFoundError.
*/
public final class AnnotationAccess {

public static <T extends Annotation> T getAnnotation(AnnotatedElement element, Class<T> annotationType) {
try {
return element.getAnnotation(annotationType);
} catch (ArrayStoreException e) {
} catch (ArrayStoreException | NoClassDefFoundError e) {
/*
* Returning null essentially means that the element doesn't declare the annotationType,
* but we cannot know that since the annotation parsing failed. However, this allows us
* to defend against crashing the image builder if the above JDK bug is encountered in
* user code.
* user code or if the user code references types missing from the classpath.
*/
return null;
}
Expand All @@ -53,13 +61,13 @@ public static <T extends Annotation> T getAnnotation(AnnotatedElement element, C
public static Annotation[] getAnnotations(AnnotatedElement element) {
try {
return element.getAnnotations();
} catch (ArrayStoreException e) {
} catch (ArrayStoreException | NoClassDefFoundError e) {
/*
* Returning an empty array essentially means that the element doesn't declare any
* annotations, but we know that it is not true since the reason the annotation parsing
* failed is because some annotation referenced a missing class. However, this allows us
* to defend against crashing the image builder if the above JDK bug is encountered in
* user code.
* user code or if the user code references types missing from the classpath.
*/
return new Annotation[0];
}
Expand All @@ -68,12 +76,12 @@ public static Annotation[] getAnnotations(AnnotatedElement element) {
public static <T extends Annotation> T getDeclaredAnnotation(AnnotatedElement element, Class<T> annotationType) {
try {
return element.getDeclaredAnnotation(annotationType);
} catch (ArrayStoreException e) {
} catch (ArrayStoreException | NoClassDefFoundError e) {
/*
* Returning null essentially means that the element doesn't declare the annotationType,
* but we cannot know that since the annotation parsing failed. However, this allows us
* to defend against crashing the image builder if the above JDK bug is encountered in
* user code.
* user code or if the user code references types missing from the classpath.
*/
return null;
}
Expand All @@ -82,13 +90,14 @@ public static <T extends Annotation> T getDeclaredAnnotation(AnnotatedElement el
public static Annotation[] getDeclaredAnnotations(AnnotatedElement element) {
try {
return element.getDeclaredAnnotations();
} catch (ArrayStoreException e) {
} catch (ArrayStoreException | NoClassDefFoundError e) {
/*
* Returning an empty array essentially means that the element doesn't declare any
* annotations, but we know that it is not true since the reason the annotation parsing
* failed is because it at least one annotation referenced a missing class. However,
* this allows us to defend against crashing the image builder if the above JDK bug is
* encountered in user code.
* encountered in user code or if the user code references types missing from the
* classpath.
*/
return new Annotation[0];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
*/
package com.oracle.svm.hosted;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -50,11 +49,14 @@
import com.oracle.svm.core.option.OptionUtils;
import com.oracle.svm.core.option.SubstrateOptionsParser;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.hosted.FeatureImpl.AfterRegistrationAccessImpl;
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl;
import com.oracle.svm.hosted.meta.HostedType;
import com.oracle.svm.hosted.meta.MethodPointer;

import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;

/**
Expand Down Expand Up @@ -106,6 +108,7 @@ InitKind max(InitKind other) {
* errors without immediately aborting image building.
*/
private UnsupportedFeatures unsupportedFeatures;
private MetaAccessProvider metaAccess;

public static ClassInitializationFeature singleton() {
return (ClassInitializationFeature) ImageSingletons.lookup(RuntimeClassInitializationSupport.class);
Expand Down Expand Up @@ -151,10 +154,22 @@ private static AnalysisType toAnalysisType(ResolvedJavaType type) {
return type instanceof HostedType ? ((HostedType) type).getWrapped() : (AnalysisType) type;
}

private static ResolvedJavaType toWrappedType(ResolvedJavaType type) {
if (type instanceof AnalysisType) {
return ((AnalysisType) type).getWrappedWithoutResolve();
} else if (type instanceof HostedType) {
return ((HostedType) type).getWrapped().getWrappedWithoutResolve();
} else {
return type;
}
}

@Override
public void afterRegistration(AfterRegistrationAccess access) {
ImageSingletons.add(RuntimeClassInitializationSupport.class, this);

metaAccess = ((AfterRegistrationAccessImpl) access).getMetaAccess();

processOption(access, Options.DelayClassInitialization, this::delayClassInitialization);
processOption(access, Options.RerunClassInitialization, this::rerunClassInitialization);
}
Expand Down Expand Up @@ -274,15 +289,15 @@ private static boolean hasDefaultMethods(ResolvedJavaType type) {
}

private static boolean declaresDefaultMethods(ResolvedJavaType type) {
return declaresDefaultMethods(toAnalysisType(type).getJavaClass());
}

private static boolean declaresDefaultMethods(Class<?> clazz) {
if (!clazz.isInterface()) {
if (!type.isInterface()) {
/* Only interfaces can declare default methods. */
return false;
}
for (Method method : clazz.getDeclaredMethods()) {
/*
* We call getDeclaredMethods() directly on the wrapped type. We avoid calling it on the
* AnalysisType because it resolves all the methods in the AnalysisUniverse.
*/
for (ResolvedJavaMethod method : toWrappedType(type).getDeclaredMethods()) {
if (method.isDefault()) {
assert !Modifier.isStatic(method.getModifiers()) : "Default method that is static?";
return true;
Expand Down Expand Up @@ -328,7 +343,7 @@ private InitKind computeInitKindAndMaybeInitializeClass(Class<?> clazz, boolean
private InitKind processInterfaces(Class<?> clazz, boolean memoizeEager) {
InitKind result = InitKind.EAGER;
for (Class<?> iface : clazz.getInterfaces()) {
if (declaresDefaultMethods(iface)) {
if (declaresDefaultMethods(metaAccess.lookupJavaType(iface))) {
/*
* An interface that declares default methods is initialized when a class
* implementing it is initialized. So we need to inherit the InitKind from such an
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.graalvm.nativeimage.c.constant.CEnumLookup;
import org.graalvm.nativeimage.c.constant.CEnumValue;

import com.oracle.graal.pointsto.api.AnnotationAccess;
import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor;
import com.oracle.svm.hosted.c.NativeLibraries;

Expand All @@ -50,7 +51,7 @@ public CEnumCallWrapperSubstitutionProcessor() {

@Override
public ResolvedJavaMethod lookup(ResolvedJavaMethod method) {
if (method.getAnnotation(CEnumLookup.class) != null || method.getAnnotation(CEnumValue.class) != null) {
if (AnnotationAccess.getAnnotation(method, CEnumLookup.class) != null || AnnotationAccess.getAnnotation(method, CEnumValue.class) != null) {
return callWrappers.computeIfAbsent(method, v -> new CEnumCallWrapperMethod(nativeLibraries, v));
} else {
return method;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ protected void parseAndRegister(Reader reader, String featureName, Object locati
JSONParser parser = new JSONParser(reader);
Object json = parser.parse();
parseClassArray(asList(json, "first level of document must be an array of class descriptors"));
} catch (NoClassDefFoundError e) {
throw e;
} catch (IOException | JSONParserException e) {
String errorMessage = e.getMessage();
if (errorMessage == null || errorMessage.isEmpty()) {
Expand Down Expand Up @@ -102,40 +104,44 @@ private void parseClass(Map<String, Object> data) {
for (Map.Entry<String, Object> entry : data.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
if (name.equals("name")) {
/* Already handled. */
} else if (name.equals("allDeclaredConstructors")) {
if (asBoolean(value, "allDeclaredConstructors")) {
registry.register(clazz.getDeclaredConstructors());
}
} else if (name.equals("allPublicConstructors")) {
if (asBoolean(value, "allPublicConstructors")) {
registry.register(clazz.getConstructors());
}
} else if (name.equals("allDeclaredMethods")) {
if (asBoolean(value, "allDeclaredMethods")) {
registry.register(clazz.getDeclaredMethods());
}
} else if (name.equals("allPublicMethods")) {
if (asBoolean(value, "allPublicMethods")) {
registry.register(clazz.getMethods());
}
} else if (name.equals("allDeclaredFields")) {
if (asBoolean(value, "allDeclaredFields")) {
registry.register(false, clazz.getDeclaredFields());
}
} else if (name.equals("allPublicFields")) {
if (asBoolean(value, "allPublicFields")) {
registry.register(false, clazz.getFields());
try {
if (name.equals("name")) {
/* Already handled. */
} else if (name.equals("allDeclaredConstructors")) {
if (asBoolean(value, "allDeclaredConstructors")) {
registry.register(clazz.getDeclaredConstructors());
}
} else if (name.equals("allPublicConstructors")) {
if (asBoolean(value, "allPublicConstructors")) {
registry.register(clazz.getConstructors());
}
} else if (name.equals("allDeclaredMethods")) {
if (asBoolean(value, "allDeclaredMethods")) {
registry.register(clazz.getDeclaredMethods());
}
} else if (name.equals("allPublicMethods")) {
if (asBoolean(value, "allPublicMethods")) {
registry.register(clazz.getMethods());
}
} else if (name.equals("allDeclaredFields")) {
if (asBoolean(value, "allDeclaredFields")) {
registry.register(false, clazz.getDeclaredFields());
}
} else if (name.equals("allPublicFields")) {
if (asBoolean(value, "allPublicFields")) {
registry.register(false, clazz.getFields());
}
} else if (name.equals("methods")) {
parseMethods(asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz);
} else if (name.equals("fields")) {
parseFields(asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz);
} else {
throw new JSONParserException("Unknown attribute '" + name +
"' (supported attributes: allDeclaredConstructors, allPublicConstructors, allDeclaredMethods, allPublicMethods, allDeclaredFields, allPublicFields, methods, fields) in defintion of class " +
clazz.getTypeName());
}
} else if (name.equals("methods")) {
parseMethods(asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz);
} else if (name.equals("fields")) {
parseFields(asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz);
} else {
throw new JSONParserException("Unknown attribute '" + name +
"' (supported attributes: allDeclaredConstructors, allPublicConstructors, allDeclaredMethods, allPublicMethods, allDeclaredFields, allPublicFields, methods, fields) in defintion of class " +
clazz.getTypeName());
} catch (NoClassDefFoundError e) {
showWarning("Could not register " + clazz.getTypeName() + ": " + name + " for reflection. Reason: " + formatError(e) + ".");
}
}
}
Expand Down Expand Up @@ -168,6 +174,8 @@ private void parseField(Map<String, Object> data, Class<?> clazz) {
registry.register(allowWrite, clazz.getDeclaredField(fieldName));
} catch (NoSuchFieldException e) {
throw new JSONParserException("Field " + clazz.getTypeName() + "." + fieldName + " not found");
} catch (NoClassDefFoundError e) {
showWarning("Could not register field " + clazz.getTypeName() + "." + fieldName + " for reflection. Reason: " + formatError(e) + ".");
}
}

Expand Down Expand Up @@ -196,31 +204,31 @@ private void parseMethod(Map<String, Object> data, Class<?> clazz) {
throw new JSONParserException("Missing attribute 'name' in definition of method for class '" + clazz.getTypeName() + "'");
}

boolean isConstructor = CONSTRUCTOR_NAME.equals(methodName);
if (methodParameterTypes != null) {
try {
Executable method;
if (CONSTRUCTOR_NAME.equals(methodName)) {
method = clazz.getDeclaredConstructor(methodParameterTypes);
} else {
method = clazz.getDeclaredMethod(methodName, methodParameterTypes);
}
Executable method = isConstructor ? clazz.getDeclaredConstructor(methodParameterTypes) : clazz.getDeclaredMethod(methodName, methodParameterTypes);
registry.register(method);
} catch (NoSuchMethodException e) {
String parameterTypeNames = Stream.of(methodParameterTypes).map(Class::getSimpleName).collect(Collectors.joining(", "));
throw new JSONParserException("Method " + clazz.getTypeName() + "." + methodName + "(" + parameterTypeNames + ") not found");
throw new JSONParserException("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found");
} catch (NoClassDefFoundError e) {
showWarning("Could not register method " + formatMethod(clazz, methodName, methodParameterTypes) + " for reflection. Reason: " + formatError(e) + ".");
}
} else {
boolean found = false;
boolean isConstructor = CONSTRUCTOR_NAME.equals(methodName);
Executable[] methods = isConstructor ? clazz.getDeclaredConstructors() : clazz.getDeclaredMethods();
for (Executable method : methods) {
if (isConstructor || method.getName().equals(methodName)) {
registry.register(method);
found = true;
try {
boolean found = false;
Executable[] methods = isConstructor ? clazz.getDeclaredConstructors() : clazz.getDeclaredMethods();
for (Executable method : methods) {
if (isConstructor || method.getName().equals(methodName)) {
registry.register(method);
found = true;
}
}
}
if (!found) {
throw new JSONParserException("Method " + clazz.getTypeName() + "." + methodName + " not found");
if (!found) {
throw new JSONParserException("Method " + clazz.getTypeName() + "." + methodName + " not found");
}
} catch (NoClassDefFoundError e) {
showWarning("Could not register method " + clazz.getTypeName() + "." + methodName + " for reflection. Reason: " + formatError(e) + ".");
}
}
}
Expand All @@ -242,4 +250,17 @@ private Class<?>[] parseTypes(List<Object> types) {
return result.toArray(new Class<?>[result.size()]);
}

private static String formatError(Error e) {
return e.getClass().getTypeName() + ": " + e.getMessage();
}

private static String formatMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes) {
String parameterTypeNames = Stream.of(paramTypes).map(Class::getSimpleName).collect(Collectors.joining(", "));
return clazz.getTypeName() + "." + methodName + "(" + parameterTypeNames + ")";
}

private static void showWarning(String message) {
System.out.println("WARNING: " + message);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,25 @@ protected void duringAnalysis(DuringAnalysisAccess a) {
* Ensure all internal fields of the original Class.ReflectionData object are
* initialized. Calling the public methods triggers lazy initialization of the fields.
*/
clazz.getDeclaredFields();
clazz.getFields();
clazz.getDeclaredMethods();
clazz.getMethods();
clazz.getDeclaredConstructors();
clazz.getConstructors();
try {
clazz.getDeclaredFields();
clazz.getFields();
clazz.getDeclaredMethods();
clazz.getMethods();
clazz.getDeclaredConstructors();
clazz.getConstructors();
} catch (NoClassDefFoundError e) {
/*
* If any of the methods or fields reference missing types in their signatures a
* NoClassDefFoundError is thrown. Skip registering reflection metadata for this
* class.
*/
// Checkstyle: stop
System.out.println("WARNING: Could not register reflection metadata for " + clazz.getTypeName() +
". Reason: " + e.getClass().getTypeName() + ": " + e.getMessage() + ".");
// Checkstyle: resume
continue;
}

try {
Object originalReflectionData = reflectionDataMethod.invoke(clazz);
Expand Down

0 comments on commit dd0bae3

Please sign in to comment.