Skip to content

Commit c8eada5

Browse files
committed
Simplify missing reflection registration options
1 parent 128b2fe commit c8eada5

File tree

6 files changed

+117
-58
lines changed

6 files changed

+117
-58
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ private static Class<?> forName(String className, ClassLoader classLoader, boole
114114
}
115115
} else if (result == null) {
116116
if (throwMissingRegistrationErrors()) {
117-
throw MissingReflectionRegistrationUtils.forClass(className);
117+
MissingReflectionRegistrationUtils.forClass(className);
118118
}
119119

120120
if (returnNullOnException) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ public void setReflectionMetadata(int fieldsEncodingIndex, int methodsEncodingIn
527527

528528
private void checkClassFlag(int mask, String methodName) {
529529
if (throwMissingRegistrationErrors() && !isClassFlagSet(mask)) {
530-
throw MissingReflectionRegistrationUtils.forBulkQuery(DynamicHub.toClass(this), methodName);
530+
MissingReflectionRegistrationUtils.forBulkQuery(DynamicHub.toClass(this), methodName);
531531
}
532532
}
533533

@@ -995,22 +995,22 @@ private void checkField(String fieldName, Field field, boolean publicOnly) throw
995995
Class<?> clazz = DynamicHub.toClass(this);
996996
if (field == null) {
997997
if (throwMissingErrors && !allElementsRegistered(publicOnly, ALL_DECLARED_FIELDS_FLAG, ALL_FIELDS_FLAG)) {
998-
throw MissingReflectionRegistrationUtils.forField(clazz, fieldName);
999-
} else {
1000-
/*
1001-
* If getDeclaredFields (or getFields for a public field) is registered, we know for
1002-
* sure that the field does indeed not exist if we don't find it.
1003-
*/
1004-
throw new NoSuchFieldException(fieldName);
998+
MissingReflectionRegistrationUtils.forField(clazz, fieldName);
1005999
}
1000+
/*
1001+
* If getDeclaredFields (or getFields for a public field) is registered, we know for
1002+
* sure that the field does indeed not exist if we don't find it.
1003+
*/
1004+
throw new NoSuchFieldException(fieldName);
10061005
} else {
10071006
ReflectionMetadataDecoder decoder = ImageSingletons.lookup(ReflectionMetadataDecoder.class);
10081007
int fieldModifiers = field.getModifiers();
10091008
boolean negative = decoder.isNegative(fieldModifiers);
10101009
boolean hiding = decoder.isHiding(fieldModifiers);
10111010
if (throwMissingErrors && hiding) {
1012-
throw MissingReflectionRegistrationUtils.forField(clazz, fieldName);
1013-
} else if (negative || hiding) {
1011+
MissingReflectionRegistrationUtils.forField(clazz, fieldName);
1012+
}
1013+
if (negative || hiding) {
10141014
throw new NoSuchFieldException(fieldName);
10151015
}
10161016
}
@@ -1029,22 +1029,22 @@ private void checkMethod(String methodName, Class<?>[] parameterTypes, Executabl
10291029
Class<?> clazz = DynamicHub.toClass(this);
10301030
if (method == null) {
10311031
if (throwMissingErrors && !allElementsRegistered(publicOnly, ALL_DECLARED_METHODS_FLAG, ALL_METHODS_FLAG)) {
1032-
throw MissingReflectionRegistrationUtils.forMethod(clazz, methodName, parameterTypes);
1033-
} else {
1034-
/*
1035-
* If getDeclaredMethods (or getMethods for a public method) is registered, we know
1036-
* for sure that the method does indeed not exist if we don't find it.
1037-
*/
1038-
throw new NoSuchMethodException(methodToString(methodName, parameterTypes));
1032+
MissingReflectionRegistrationUtils.forMethod(clazz, methodName, parameterTypes);
10391033
}
1034+
/*
1035+
* If getDeclaredMethods (or getMethods for a public method) is registered, we know for
1036+
* sure that the method does indeed not exist if we don't find it.
1037+
*/
1038+
throw new NoSuchMethodException(methodToString(methodName, parameterTypes));
10401039
} else {
10411040
ReflectionMetadataDecoder decoder = ImageSingletons.lookup(ReflectionMetadataDecoder.class);
10421041
int methodModifiers = method.getModifiers();
10431042
boolean negative = decoder.isNegative(methodModifiers);
10441043
boolean hiding = decoder.isHiding(methodModifiers);
10451044
if (throwMissingErrors && hiding) {
1046-
throw MissingReflectionRegistrationUtils.forMethod(clazz, methodName, parameterTypes);
1047-
} else if (negative || hiding) {
1045+
MissingReflectionRegistrationUtils.forMethod(clazz, methodName, parameterTypes);
1046+
}
1047+
if (negative || hiding) {
10481048
throw new NoSuchMethodException(methodToString(methodName, parameterTypes));
10491049
}
10501050
}
@@ -1487,7 +1487,7 @@ public DynamicHub arrayType() {
14871487
throw new UnsupportedOperationException(new IllegalArgumentException());
14881488
}
14891489
if (arrayHub == null) {
1490-
throw MissingReflectionRegistrationUtils.forClass(getTypeName() + "[]");
1490+
MissingReflectionRegistrationUtils.forClass(getTypeName() + "[]");
14911491
}
14921492
return arrayHub;
14931493
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java

Lines changed: 93 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import java.lang.reflect.Executable;
2929
import java.lang.reflect.Field;
3030
import java.lang.reflect.Method;
31+
import java.util.Set;
3132
import java.util.StringJoiner;
33+
import java.util.concurrent.ConcurrentHashMap;
3234

3335
import org.graalvm.compiler.options.Option;
3436
import org.graalvm.nativeimage.MissingReflectionRegistrationError;
@@ -39,67 +41,69 @@
3941

4042
public final class MissingReflectionRegistrationUtils {
4143
public static class Options {
42-
@Option(help = "Enable termination caused by missing metadata.")//
43-
public static final HostedOptionKey<Boolean> ExitOnMissingReflectionRegistration = new HostedOptionKey<>(false);
44+
@Option(help = {"Select the mode in which the missing reflection registrations will be reported.",
45+
"Possible values are:",
46+
"\"Throw\" (default): Throw a MissingReflectionRegistrationError;",
47+
"\"Exit\": Call System.exit() to avoid accidentally catching the error;",
48+
"\"Warn\": Print a message to stdout, including a stack trace to see what caused the issue."})//
49+
public static final HostedOptionKey<ReportingMode> MissingRegistrationReportingMode = new HostedOptionKey<>(ReportingMode.Throw);
50+
}
4451

45-
@Option(help = "Simulate exiting the program with an exception instead of calling System.exit() (for testing)")//
46-
public static final HostedOptionKey<Boolean> ExitWithExceptionOnMissingReflectionRegistration = new HostedOptionKey<>(false);
52+
public enum ReportingMode {
53+
Throw,
54+
Exit,
55+
ExitTest,
56+
Warn
4757
}
4858

4959
public static boolean throwMissingRegistrationErrors() {
5060
return SubstrateOptions.ThrowMissingRegistrationErrors.getValue();
5161
}
5262

53-
public static MissingReflectionRegistrationError forClass(String className) {
63+
public static ReportingMode missingRegistrationReportingMode() {
64+
return Options.MissingRegistrationReportingMode.getValue();
65+
}
66+
67+
public static void forClass(String className) {
5468
MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access class", className),
5569
Class.class, null, className, null);
56-
if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) {
57-
exitOnMissingMetadata(exception);
58-
}
59-
return exception;
70+
report(exception);
6071
}
6172

62-
public static MissingReflectionRegistrationError forField(Class<?> declaringClass, String fieldName) {
73+
public static void forField(Class<?> declaringClass, String fieldName) {
6374
MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access field",
6475
declaringClass.getTypeName() + "#" + fieldName),
6576
Field.class, declaringClass, fieldName, null);
66-
if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) {
67-
exitOnMissingMetadata(exception);
68-
}
69-
return exception;
77+
report(exception);
7078
}
7179

72-
public static MissingReflectionRegistrationError forMethod(Class<?> declaringClass, String methodName, Class<?>[] paramTypes) {
80+
public static void forMethod(Class<?> declaringClass, String methodName, Class<?>[] paramTypes) {
7381
StringJoiner paramTypeNames = new StringJoiner(", ", "(", ")");
7482
for (Class<?> paramType : paramTypes) {
7583
paramTypeNames.add(paramType.getTypeName());
7684
}
7785
MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access method",
7886
declaringClass.getTypeName() + "#" + methodName + paramTypeNames),
7987
Method.class, declaringClass, methodName, paramTypes);
80-
if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) {
81-
exitOnMissingMetadata(exception);
82-
}
83-
return exception;
88+
report(exception);
8489
}
8590

86-
public static MissingReflectionRegistrationError forQueriedOnlyExecutable(Executable executable) {
91+
public static void forQueriedOnlyExecutable(Executable executable) {
8792
MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("invoke method", executable.toString()),
8893
executable.getClass(), executable.getDeclaringClass(), executable.getName(), executable.getParameterTypes());
89-
if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) {
90-
exitOnMissingMetadata(exception);
91-
}
92-
return exception;
94+
report(exception);
95+
/*
96+
* If report doesn't throw, we throw the exception anyway since this is a Native
97+
* Image-specific error that is unrecoverable in any case.
98+
*/
99+
throw exception;
93100
}
94101

95-
public static MissingReflectionRegistrationError forBulkQuery(Class<?> declaringClass, String methodName) {
102+
public static void forBulkQuery(Class<?> declaringClass, String methodName) {
96103
MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access",
97104
declaringClass.getTypeName() + "." + methodName + "()"),
98105
null, declaringClass, methodName, null);
99-
if (MissingReflectionRegistrationUtils.Options.ExitOnMissingReflectionRegistration.getValue()) {
100-
exitOnMissingMetadata(exception);
101-
}
102-
return exception;
106+
report(exception);
103107
}
104108

105109
private static String errorMessage(String failedAction, String elementDescriptor) {
@@ -108,12 +112,66 @@ private static String errorMessage(String failedAction, String elementDescriptor
108112
"See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection for help.";
109113
}
110114

111-
private static void exitOnMissingMetadata(MissingReflectionRegistrationError exception) {
112-
if (Options.ExitWithExceptionOnMissingReflectionRegistration.getValue()) {
113-
throw new ExitException(exception);
114-
} else {
115-
exception.printStackTrace(System.out);
116-
System.exit(ExitStatus.MISSING_METADATA.getValue());
115+
private static final int CONTEXT_LINES = 4;
116+
117+
private static final Set<String> seenOutputs = Options.MissingRegistrationReportingMode.getValue() == ReportingMode.Warn ? ConcurrentHashMap.newKeySet() : null;
118+
119+
private static void report(MissingReflectionRegistrationError exception) {
120+
switch (missingRegistrationReportingMode()) {
121+
case Throw -> {
122+
throw exception;
123+
}
124+
case Exit -> {
125+
exception.printStackTrace(System.out);
126+
System.exit(ExitStatus.MISSING_METADATA.getValue());
127+
}
128+
case ExitTest -> {
129+
throw new ExitException(exception);
130+
}
131+
case Warn -> {
132+
StackTraceElement[] stackTrace = exception.getStackTrace();
133+
int printed = 0;
134+
StackTraceElement entryPoint = null;
135+
StringBuilder sb = new StringBuilder(exception.toString());
136+
sb.append("\n");
137+
for (StackTraceElement stackTraceElement : stackTrace) {
138+
if (printed == 0) {
139+
String moduleName = stackTraceElement.getModuleName();
140+
/*
141+
* Skip internal stack trace entries to include only the relevant part of
142+
* the trace in the output. The heuristic used is that any JDK and Graal
143+
* code is excluded except the first element, so that the rest of the trace
144+
* consists of meaningful application code entries.
145+
*/
146+
if (moduleName != null && (moduleName.equals("java.base") || moduleName.startsWith("org.graalvm"))) {
147+
entryPoint = stackTraceElement;
148+
} else {
149+
sb.append(" ");
150+
sb.append(entryPoint);
151+
sb.append("\n");
152+
printed++;
153+
}
154+
}
155+
if (printed > 0) {
156+
sb.append(" ");
157+
sb.append(stackTraceElement);
158+
sb.append("\n");
159+
printed++;
160+
}
161+
if (printed >= CONTEXT_LINES) {
162+
break;
163+
}
164+
}
165+
if (seenOutputs.isEmpty()) {
166+
/* First output, we print an explanation message */
167+
System.out.println("Note: this run will print partial stack traces of the locations where a MissingReflectionRegistrationError would be thrown " +
168+
"when the -H:+ThrowMissingRegistrationErrors option is set. The trace stops at the first entry of JDK code and provides 4 lines of context.");
169+
}
170+
String output = sb.toString();
171+
if (seenOutputs.add(output)) {
172+
System.out.print(output);
173+
}
174+
}
117175
}
118176
}
119177

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public native void constructor(Class<?> declaringClass, Class<?>[] parameterType
7171
@Substitute
7272
Target_jdk_internal_reflect_ConstructorAccessor acquireConstructorAccessor() {
7373
if (constructorAccessor == null) {
74-
throw MissingReflectionRegistrationUtils.forQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
74+
MissingReflectionRegistrationUtils.forQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
7575
}
7676
return constructorAccessor;
7777
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public native void constructor(Class<?> declaringClass, String name, Class<?>[]
7474
@Substitute
7575
public Target_jdk_internal_reflect_MethodAccessor acquireMethodAccessor() {
7676
if (methodAccessor == null) {
77-
throw MissingReflectionRegistrationUtils.forQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
77+
MissingReflectionRegistrationUtils.forQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
7878
}
7979
return methodAccessor;
8080
}

substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ public boolean getAsBoolean() {
139139
compilerPackages = new HashSet<>();
140140
compilerPackages.add("org.graalvm.");
141141
compilerPackages.add("com.oracle.graalvm.");
142+
compilerPackages.add("com.oracle.svm.core.");
142143
compilerPackages.add("com.oracle.truffle.api.");
143144
compilerPackages.add("com.oracle.truffle.polyglot.");
144145
compilerPackages.add("com.oracle.truffle.host.");

0 commit comments

Comments
 (0)