Skip to content

Commit dd0bae3

Browse files
committed
[GR-13355] Fix incomplete classpath issues.
PullRequest: graal/2754
2 parents 7d978ce + 61faf00 commit dd0bae3

File tree

5 files changed

+133
-74
lines changed

5 files changed

+133
-74
lines changed

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/AnnotationAccess.java

+17-8
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,26 @@
3333
* parameter and one of the referenced classes is not present on the classpath parsing the
3434
* annotations will result in an ArrayStoreException instead of caching of a
3535
* TypeNotPresentExceptionProxy. This is a problem in JDK8 but was fixed in JDK11+.
36+
*
37+
* This wrapper class also defends against incomplete class path issues. If the element for which
38+
* annotations are queried is a JMVCI value, i.e., a HotSpotResolvedJavaField, or
39+
* HotSpotResolvedJavaMethod, the annotations are read via HotSpotJDKReflection using the
40+
* getFieldAnnotation()/getMethodAnnotation() methods which first construct the field/method object
41+
* via CompilerToVM.asReflectionField()/CompilerToVM.asReflectionExecutable() which eagerly try to
42+
* resolve the types referenced in the element signature. If a field declared type or a method
43+
* return type is missing then JVMCI throws a NoClassDefFoundError.
3644
*/
3745
public final class AnnotationAccess {
3846

3947
public static <T extends Annotation> T getAnnotation(AnnotatedElement element, Class<T> annotationType) {
4048
try {
4149
return element.getAnnotation(annotationType);
42-
} catch (ArrayStoreException e) {
50+
} catch (ArrayStoreException | NoClassDefFoundError e) {
4351
/*
4452
* Returning null essentially means that the element doesn't declare the annotationType,
4553
* but we cannot know that since the annotation parsing failed. However, this allows us
4654
* to defend against crashing the image builder if the above JDK bug is encountered in
47-
* user code.
55+
* user code or if the user code references types missing from the classpath.
4856
*/
4957
return null;
5058
}
@@ -53,13 +61,13 @@ public static <T extends Annotation> T getAnnotation(AnnotatedElement element, C
5361
public static Annotation[] getAnnotations(AnnotatedElement element) {
5462
try {
5563
return element.getAnnotations();
56-
} catch (ArrayStoreException e) {
64+
} catch (ArrayStoreException | NoClassDefFoundError e) {
5765
/*
5866
* Returning an empty array essentially means that the element doesn't declare any
5967
* annotations, but we know that it is not true since the reason the annotation parsing
6068
* failed is because some annotation referenced a missing class. However, this allows us
6169
* to defend against crashing the image builder if the above JDK bug is encountered in
62-
* user code.
70+
* user code or if the user code references types missing from the classpath.
6371
*/
6472
return new Annotation[0];
6573
}
@@ -68,12 +76,12 @@ public static Annotation[] getAnnotations(AnnotatedElement element) {
6876
public static <T extends Annotation> T getDeclaredAnnotation(AnnotatedElement element, Class<T> annotationType) {
6977
try {
7078
return element.getDeclaredAnnotation(annotationType);
71-
} catch (ArrayStoreException e) {
79+
} catch (ArrayStoreException | NoClassDefFoundError e) {
7280
/*
7381
* Returning null essentially means that the element doesn't declare the annotationType,
7482
* but we cannot know that since the annotation parsing failed. However, this allows us
7583
* to defend against crashing the image builder if the above JDK bug is encountered in
76-
* user code.
84+
* user code or if the user code references types missing from the classpath.
7785
*/
7886
return null;
7987
}
@@ -82,13 +90,14 @@ public static <T extends Annotation> T getDeclaredAnnotation(AnnotatedElement el
8290
public static Annotation[] getDeclaredAnnotations(AnnotatedElement element) {
8391
try {
8492
return element.getDeclaredAnnotations();
85-
} catch (ArrayStoreException e) {
93+
} catch (ArrayStoreException | NoClassDefFoundError e) {
8694
/*
8795
* Returning an empty array essentially means that the element doesn't declare any
8896
* annotations, but we know that it is not true since the reason the annotation parsing
8997
* failed is because it at least one annotation referenced a missing class. However,
9098
* this allows us to defend against crashing the image builder if the above JDK bug is
91-
* encountered in user code.
99+
* encountered in user code or if the user code references types missing from the
100+
* classpath.
92101
*/
93102
return new Annotation[0];
94103
}

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

+23-8
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525
package com.oracle.svm.hosted;
2626

27-
import java.lang.reflect.Method;
2827
import java.lang.reflect.Modifier;
2928
import java.util.Map;
3029
import java.util.concurrent.ConcurrentHashMap;
@@ -50,11 +49,14 @@
5049
import com.oracle.svm.core.option.OptionUtils;
5150
import com.oracle.svm.core.option.SubstrateOptionsParser;
5251
import com.oracle.svm.core.util.UserError;
52+
import com.oracle.svm.hosted.FeatureImpl.AfterRegistrationAccessImpl;
5353
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
5454
import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl;
5555
import com.oracle.svm.hosted.meta.HostedType;
5656
import com.oracle.svm.hosted.meta.MethodPointer;
5757

58+
import jdk.vm.ci.meta.MetaAccessProvider;
59+
import jdk.vm.ci.meta.ResolvedJavaMethod;
5860
import jdk.vm.ci.meta.ResolvedJavaType;
5961

6062
/**
@@ -106,6 +108,7 @@ InitKind max(InitKind other) {
106108
* errors without immediately aborting image building.
107109
*/
108110
private UnsupportedFeatures unsupportedFeatures;
111+
private MetaAccessProvider metaAccess;
109112

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

157+
private static ResolvedJavaType toWrappedType(ResolvedJavaType type) {
158+
if (type instanceof AnalysisType) {
159+
return ((AnalysisType) type).getWrappedWithoutResolve();
160+
} else if (type instanceof HostedType) {
161+
return ((HostedType) type).getWrapped().getWrappedWithoutResolve();
162+
} else {
163+
return type;
164+
}
165+
}
166+
154167
@Override
155168
public void afterRegistration(AfterRegistrationAccess access) {
156169
ImageSingletons.add(RuntimeClassInitializationSupport.class, this);
157170

171+
metaAccess = ((AfterRegistrationAccessImpl) access).getMetaAccess();
172+
158173
processOption(access, Options.DelayClassInitialization, this::delayClassInitialization);
159174
processOption(access, Options.RerunClassInitialization, this::rerunClassInitialization);
160175
}
@@ -274,15 +289,15 @@ private static boolean hasDefaultMethods(ResolvedJavaType type) {
274289
}
275290

276291
private static boolean declaresDefaultMethods(ResolvedJavaType type) {
277-
return declaresDefaultMethods(toAnalysisType(type).getJavaClass());
278-
}
279-
280-
private static boolean declaresDefaultMethods(Class<?> clazz) {
281-
if (!clazz.isInterface()) {
292+
if (!type.isInterface()) {
282293
/* Only interfaces can declare default methods. */
283294
return false;
284295
}
285-
for (Method method : clazz.getDeclaredMethods()) {
296+
/*
297+
* We call getDeclaredMethods() directly on the wrapped type. We avoid calling it on the
298+
* AnalysisType because it resolves all the methods in the AnalysisUniverse.
299+
*/
300+
for (ResolvedJavaMethod method : toWrappedType(type).getDeclaredMethods()) {
286301
if (method.isDefault()) {
287302
assert !Modifier.isStatic(method.getModifiers()) : "Default method that is static?";
288303
return true;
@@ -328,7 +343,7 @@ private InitKind computeInitKindAndMaybeInitializeClass(Class<?> clazz, boolean
328343
private InitKind processInterfaces(Class<?> clazz, boolean memoizeEager) {
329344
InitKind result = InitKind.EAGER;
330345
for (Class<?> iface : clazz.getInterfaces()) {
331-
if (declaresDefaultMethods(iface)) {
346+
if (declaresDefaultMethods(metaAccess.lookupJavaType(iface))) {
332347
/*
333348
* An interface that declares default methods is initialized when a class
334349
* implementing it is initialized. So we need to inherit the InitKind from such an

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperSubstitutionProcessor.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.graalvm.nativeimage.c.constant.CEnumLookup;
3131
import org.graalvm.nativeimage.c.constant.CEnumValue;
3232

33+
import com.oracle.graal.pointsto.api.AnnotationAccess;
3334
import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor;
3435
import com.oracle.svm.hosted.c.NativeLibraries;
3536

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

5152
@Override
5253
public ResolvedJavaMethod lookup(ResolvedJavaMethod method) {
53-
if (method.getAnnotation(CEnumLookup.class) != null || method.getAnnotation(CEnumValue.class) != null) {
54+
if (AnnotationAccess.getAnnotation(method, CEnumLookup.class) != null || AnnotationAccess.getAnnotation(method, CEnumValue.class) != null) {
5455
return callWrappers.computeIfAbsent(method, v -> new CEnumCallWrapperMethod(nativeLibraries, v));
5556
} else {
5657
return method;

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionConfigurationParser.java

+72-51
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ protected void parseAndRegister(Reader reader, String featureName, Object locati
6868
JSONParser parser = new JSONParser(reader);
6969
Object json = parser.parse();
7070
parseClassArray(asList(json, "first level of document must be an array of class descriptors"));
71+
} catch (NoClassDefFoundError e) {
72+
throw e;
7173
} catch (IOException | JSONParserException e) {
7274
String errorMessage = e.getMessage();
7375
if (errorMessage == null || errorMessage.isEmpty()) {
@@ -102,40 +104,44 @@ private void parseClass(Map<String, Object> data) {
102104
for (Map.Entry<String, Object> entry : data.entrySet()) {
103105
String name = entry.getKey();
104106
Object value = entry.getValue();
105-
if (name.equals("name")) {
106-
/* Already handled. */
107-
} else if (name.equals("allDeclaredConstructors")) {
108-
if (asBoolean(value, "allDeclaredConstructors")) {
109-
registry.register(clazz.getDeclaredConstructors());
110-
}
111-
} else if (name.equals("allPublicConstructors")) {
112-
if (asBoolean(value, "allPublicConstructors")) {
113-
registry.register(clazz.getConstructors());
114-
}
115-
} else if (name.equals("allDeclaredMethods")) {
116-
if (asBoolean(value, "allDeclaredMethods")) {
117-
registry.register(clazz.getDeclaredMethods());
118-
}
119-
} else if (name.equals("allPublicMethods")) {
120-
if (asBoolean(value, "allPublicMethods")) {
121-
registry.register(clazz.getMethods());
122-
}
123-
} else if (name.equals("allDeclaredFields")) {
124-
if (asBoolean(value, "allDeclaredFields")) {
125-
registry.register(false, clazz.getDeclaredFields());
126-
}
127-
} else if (name.equals("allPublicFields")) {
128-
if (asBoolean(value, "allPublicFields")) {
129-
registry.register(false, clazz.getFields());
107+
try {
108+
if (name.equals("name")) {
109+
/* Already handled. */
110+
} else if (name.equals("allDeclaredConstructors")) {
111+
if (asBoolean(value, "allDeclaredConstructors")) {
112+
registry.register(clazz.getDeclaredConstructors());
113+
}
114+
} else if (name.equals("allPublicConstructors")) {
115+
if (asBoolean(value, "allPublicConstructors")) {
116+
registry.register(clazz.getConstructors());
117+
}
118+
} else if (name.equals("allDeclaredMethods")) {
119+
if (asBoolean(value, "allDeclaredMethods")) {
120+
registry.register(clazz.getDeclaredMethods());
121+
}
122+
} else if (name.equals("allPublicMethods")) {
123+
if (asBoolean(value, "allPublicMethods")) {
124+
registry.register(clazz.getMethods());
125+
}
126+
} else if (name.equals("allDeclaredFields")) {
127+
if (asBoolean(value, "allDeclaredFields")) {
128+
registry.register(false, clazz.getDeclaredFields());
129+
}
130+
} else if (name.equals("allPublicFields")) {
131+
if (asBoolean(value, "allPublicFields")) {
132+
registry.register(false, clazz.getFields());
133+
}
134+
} else if (name.equals("methods")) {
135+
parseMethods(asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz);
136+
} else if (name.equals("fields")) {
137+
parseFields(asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz);
138+
} else {
139+
throw new JSONParserException("Unknown attribute '" + name +
140+
"' (supported attributes: allDeclaredConstructors, allPublicConstructors, allDeclaredMethods, allPublicMethods, allDeclaredFields, allPublicFields, methods, fields) in defintion of class " +
141+
clazz.getTypeName());
130142
}
131-
} else if (name.equals("methods")) {
132-
parseMethods(asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz);
133-
} else if (name.equals("fields")) {
134-
parseFields(asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz);
135-
} else {
136-
throw new JSONParserException("Unknown attribute '" + name +
137-
"' (supported attributes: allDeclaredConstructors, allPublicConstructors, allDeclaredMethods, allPublicMethods, allDeclaredFields, allPublicFields, methods, fields) in defintion of class " +
138-
clazz.getTypeName());
143+
} catch (NoClassDefFoundError e) {
144+
showWarning("Could not register " + clazz.getTypeName() + ": " + name + " for reflection. Reason: " + formatError(e) + ".");
139145
}
140146
}
141147
}
@@ -168,6 +174,8 @@ private void parseField(Map<String, Object> data, Class<?> clazz) {
168174
registry.register(allowWrite, clazz.getDeclaredField(fieldName));
169175
} catch (NoSuchFieldException e) {
170176
throw new JSONParserException("Field " + clazz.getTypeName() + "." + fieldName + " not found");
177+
} catch (NoClassDefFoundError e) {
178+
showWarning("Could not register field " + clazz.getTypeName() + "." + fieldName + " for reflection. Reason: " + formatError(e) + ".");
171179
}
172180
}
173181

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

207+
boolean isConstructor = CONSTRUCTOR_NAME.equals(methodName);
199208
if (methodParameterTypes != null) {
200209
try {
201-
Executable method;
202-
if (CONSTRUCTOR_NAME.equals(methodName)) {
203-
method = clazz.getDeclaredConstructor(methodParameterTypes);
204-
} else {
205-
method = clazz.getDeclaredMethod(methodName, methodParameterTypes);
206-
}
210+
Executable method = isConstructor ? clazz.getDeclaredConstructor(methodParameterTypes) : clazz.getDeclaredMethod(methodName, methodParameterTypes);
207211
registry.register(method);
208212
} catch (NoSuchMethodException e) {
209-
String parameterTypeNames = Stream.of(methodParameterTypes).map(Class::getSimpleName).collect(Collectors.joining(", "));
210-
throw new JSONParserException("Method " + clazz.getTypeName() + "." + methodName + "(" + parameterTypeNames + ") not found");
213+
throw new JSONParserException("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found");
214+
} catch (NoClassDefFoundError e) {
215+
showWarning("Could not register method " + formatMethod(clazz, methodName, methodParameterTypes) + " for reflection. Reason: " + formatError(e) + ".");
211216
}
212217
} else {
213-
boolean found = false;
214-
boolean isConstructor = CONSTRUCTOR_NAME.equals(methodName);
215-
Executable[] methods = isConstructor ? clazz.getDeclaredConstructors() : clazz.getDeclaredMethods();
216-
for (Executable method : methods) {
217-
if (isConstructor || method.getName().equals(methodName)) {
218-
registry.register(method);
219-
found = true;
218+
try {
219+
boolean found = false;
220+
Executable[] methods = isConstructor ? clazz.getDeclaredConstructors() : clazz.getDeclaredMethods();
221+
for (Executable method : methods) {
222+
if (isConstructor || method.getName().equals(methodName)) {
223+
registry.register(method);
224+
found = true;
225+
}
220226
}
221-
}
222-
if (!found) {
223-
throw new JSONParserException("Method " + clazz.getTypeName() + "." + methodName + " not found");
227+
if (!found) {
228+
throw new JSONParserException("Method " + clazz.getTypeName() + "." + methodName + " not found");
229+
}
230+
} catch (NoClassDefFoundError e) {
231+
showWarning("Could not register method " + clazz.getTypeName() + "." + methodName + " for reflection. Reason: " + formatError(e) + ".");
224232
}
225233
}
226234
}
@@ -242,4 +250,17 @@ private Class<?>[] parseTypes(List<Object> types) {
242250
return result.toArray(new Class<?>[result.size()]);
243251
}
244252

253+
private static String formatError(Error e) {
254+
return e.getClass().getTypeName() + ": " + e.getMessage();
255+
}
256+
257+
private static String formatMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes) {
258+
String parameterTypeNames = Stream.of(paramTypes).map(Class::getSimpleName).collect(Collectors.joining(", "));
259+
return clazz.getTypeName() + "." + methodName + "(" + parameterTypeNames + ")";
260+
}
261+
262+
private static void showWarning(String message) {
263+
System.out.println("WARNING: " + message);
264+
}
265+
245266
}

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

+19-6
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,25 @@ protected void duringAnalysis(DuringAnalysisAccess a) {
195195
* Ensure all internal fields of the original Class.ReflectionData object are
196196
* initialized. Calling the public methods triggers lazy initialization of the fields.
197197
*/
198-
clazz.getDeclaredFields();
199-
clazz.getFields();
200-
clazz.getDeclaredMethods();
201-
clazz.getMethods();
202-
clazz.getDeclaredConstructors();
203-
clazz.getConstructors();
198+
try {
199+
clazz.getDeclaredFields();
200+
clazz.getFields();
201+
clazz.getDeclaredMethods();
202+
clazz.getMethods();
203+
clazz.getDeclaredConstructors();
204+
clazz.getConstructors();
205+
} catch (NoClassDefFoundError e) {
206+
/*
207+
* If any of the methods or fields reference missing types in their signatures a
208+
* NoClassDefFoundError is thrown. Skip registering reflection metadata for this
209+
* class.
210+
*/
211+
// Checkstyle: stop
212+
System.out.println("WARNING: Could not register reflection metadata for " + clazz.getTypeName() +
213+
". Reason: " + e.getClass().getTypeName() + ": " + e.getMessage() + ".");
214+
// Checkstyle: resume
215+
continue;
216+
}
204217

205218
try {
206219
Object originalReflectionData = reflectionDataMethod.invoke(clazz);

0 commit comments

Comments
 (0)