2828import java .lang .reflect .Executable ;
2929import java .lang .reflect .Field ;
3030import java .lang .reflect .Method ;
31+ import java .util .Set ;
3132import java .util .StringJoiner ;
33+ import java .util .concurrent .ConcurrentHashMap ;
3234
3335import org .graalvm .compiler .options .Option ;
3436import org .graalvm .nativeimage .MissingReflectionRegistrationError ;
3941
4042public 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
0 commit comments