diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/AnnotationAccess.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/AnnotationAccess.java index 9e871766632d..326e259b6ffe 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/AnnotationAccess.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/AnnotationAccess.java @@ -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 getAnnotation(AnnotatedElement element, Class 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; } @@ -53,13 +61,13 @@ public static 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]; } @@ -68,12 +76,12 @@ public static Annotation[] getAnnotations(AnnotatedElement element) { public static T getDeclaredAnnotation(AnnotatedElement element, Class 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; } @@ -82,13 +90,14 @@ public static 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]; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassInitializationFeature.java index 227b7fbcf0f1..a4f4d6a0501c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassInitializationFeature.java @@ -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; @@ -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; /** @@ -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); @@ -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); } @@ -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; @@ -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 diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperSubstitutionProcessor.java index e20786f32797..c11459197a83 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperSubstitutionProcessor.java @@ -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; @@ -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; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionConfigurationParser.java index d198b43adcd8..a643cf05b8c9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionConfigurationParser.java @@ -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()) { @@ -102,40 +104,44 @@ private void parseClass(Map data) { for (Map.Entry 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) + "."); } } } @@ -168,6 +174,8 @@ private void parseField(Map 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) + "."); } } @@ -196,31 +204,31 @@ private void parseMethod(Map 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) + "."); } } } @@ -242,4 +250,17 @@ private Class[] parseTypes(List 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); + } + } diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java index 032ef5898f7e..52d49979552b 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java @@ -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);