Skip to content

Commit 285e734

Browse files
committed
Give a concrete action in dynamic-access errors
1 parent 83f240c commit 285e734

File tree

23 files changed

+285
-175
lines changed

23 files changed

+285
-175
lines changed

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/json/JsonWriter.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -357,19 +357,27 @@ private static String quoteString(String s) {
357357
* @see #unindent()
358358
*/
359359
public JsonWriter newline() throws IOException {
360-
StringBuilder builder = new StringBuilder(1 + 2 * indentation);
361-
builder.append("\n");
362-
for (int i = 0; i < indentation; ++i) {
363-
builder.append(" ");
364-
}
365-
writer.write(builder.toString());
360+
writer.write('\n');
361+
appendIndentation();
362+
return this;
363+
}
364+
365+
/**
366+
* Appends <code>2 * indentation</code> whitespaces. This call is used to print objects that
367+
* start indented.
368+
*
369+
* @see #indent()
370+
* @see #unindent()
371+
*/
372+
public JsonWriter appendIndentation() throws IOException {
373+
writer.write(" ".repeat(indentation));
366374
return this;
367375
}
368376

369377
/**
370378
* Increases the current indentation level by one. This does not print any character to the
371379
* writer directly, but modifies how many indents will be printed at the next call to
372-
* {@link #newline()}.
380+
* {@link #newline()} or {@link #appendIndentation()}.
373381
*/
374382
public JsonWriter indent() {
375383
indentation++;
@@ -379,7 +387,7 @@ public JsonWriter indent() {
379387
/**
380388
* Decreases the current indentation level by one. This does not print any character to the
381389
* writer directly, but modifies how many indents will be printed at the next call to
382-
* {@link #newline()}.
390+
* {@link #newline()} or {@link #appendIndentation()}.
383391
*/
384392
public JsonWriter unindent() {
385393
assert indentation > 0 : "Json indentation underflowed";

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTypeDescriptor.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@
2424
*/
2525
package com.oracle.svm.configure;
2626

27+
import java.lang.reflect.Proxy;
28+
import java.util.Arrays;
2729
import java.util.Collection;
30+
import java.util.stream.Stream;
2831

32+
import jdk.graal.compiler.java.LambdaUtils;
2933
import jdk.graal.compiler.util.json.JsonPrintable;
3034

3135
/**
@@ -44,6 +48,19 @@ enum Kind {
4448
LAMBDA
4549
}
4650

51+
static ConfigurationTypeDescriptor fromClass(Class<?> clazz) {
52+
Stream<String> interfacesStream = Arrays.stream(clazz.getInterfaces())
53+
.map(Class::getTypeName);
54+
if (Proxy.isProxyClass(clazz)) {
55+
return ProxyConfigurationTypeDescriptor.fromInterfaceReflectionNames(interfacesStream.toList());
56+
} else if (LambdaUtils.isLambdaClass(clazz)) {
57+
String declaringClass = LambdaUtils.capturingClass(clazz.getTypeName());
58+
return LambdaConfigurationTypeDescriptor.fromReflectionNames(declaringClass, interfacesStream.toList());
59+
} else {
60+
return NamedConfigurationTypeDescriptor.fromReflectionName(clazz.getTypeName());
61+
}
62+
}
63+
4764
Kind getDescriptorType();
4865

4966
@Override

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public static final class BundleConfiguration {
120120
public final Set<String> locales = ConcurrentHashMap.newKeySet();
121121
public final Set<String> classNames = ConcurrentHashMap.newKeySet();
122122

123-
private BundleConfiguration(UnresolvedConfigurationCondition condition, String baseName) {
123+
public BundleConfiguration(UnresolvedConfigurationCondition condition, String baseName) {
124124
this.condition = condition;
125125
this.baseName = baseName;
126126
}
@@ -386,7 +386,7 @@ public ConfigurationParser createParser(boolean combinedFileSchema, EnumSet<Conf
386386
return ResourceConfigurationParser.create(combinedFileSchema, ConfigurationConditionResolver.identityResolver(), new ParserAdapter(this), parserOptions);
387387
}
388388

389-
private static void printResourceBundle(BundleConfiguration config, JsonWriter writer, boolean combinedFile) throws IOException {
389+
public static void printResourceBundle(BundleConfiguration config, JsonWriter writer, boolean combinedFile) throws IOException {
390390
writer.appendObjectStart();
391391
ConfigurationConditionPrintable.printConditionAttribute(config.condition, writer, combinedFile);
392392
writer.quote(combinedFile ? BUNDLE_KEY : NAME_KEY).appendFieldSeparator().quote(config.baseName);
@@ -419,7 +419,7 @@ public boolean supportsCombinedFile() {
419419
return true;
420420
}
421421

422-
private static void conditionalGlobElementJson(ConditionalElement<ResourceEntry> p, JsonWriter w, boolean combinedFile) throws IOException {
422+
public static void conditionalGlobElementJson(ConditionalElement<ResourceEntry> p, JsonWriter w, boolean combinedFile) throws IOException {
423423
String pattern = p.element().pattern();
424424
String module = p.element().module();
425425
w.appendObjectStart();

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationUtils.java

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,39 @@
2424
*/
2525
package com.oracle.svm.core;
2626

27+
import static com.oracle.svm.core.SubstrateOptions.ThrowMissingRegistrationErrors;
28+
29+
import java.io.IOException;
2730
import java.io.Serial;
31+
import java.io.StringWriter;
32+
import java.lang.reflect.Proxy;
33+
import java.util.ArrayList;
34+
import java.util.Arrays;
35+
import java.util.List;
2836
import java.util.Set;
2937
import java.util.concurrent.ConcurrentHashMap;
3038
import java.util.concurrent.atomic.AtomicReference;
3139
import java.util.function.Supplier;
40+
import java.util.stream.Collectors;
3241

42+
import com.oracle.svm.configure.ConfigurationTypeDescriptor;
43+
import com.oracle.svm.configure.NamedConfigurationTypeDescriptor;
44+
import com.oracle.svm.configure.UnresolvedConfigurationCondition;
45+
import com.oracle.svm.configure.config.ConfigurationMemberInfo;
46+
import com.oracle.svm.configure.config.ConfigurationMethod;
47+
import com.oracle.svm.configure.config.ConfigurationType;
3348
import com.oracle.svm.core.util.ExitStatus;
3449
import com.oracle.svm.core.util.VMError;
3550

36-
public final class MissingRegistrationUtils {
51+
import jdk.graal.compiler.java.LambdaUtils;
52+
import jdk.graal.compiler.util.json.JsonPrettyWriter;
53+
import jdk.graal.compiler.util.json.JsonPrintable;
54+
import jdk.graal.compiler.util.json.JsonWriter;
3755

38-
public static final String ERROR_EMPHASIS_INDENT = " ";
56+
public class MissingRegistrationUtils {
3957

4058
public static boolean throwMissingRegistrationErrors() {
41-
return SubstrateOptions.ThrowMissingRegistrationErrors.hasBeenSet();
59+
return ThrowMissingRegistrationErrors.hasBeenSet();
4260
}
4361

4462
public static SubstrateOptions.ReportingMode missingRegistrationReportingMode() {
@@ -94,8 +112,9 @@ public static void report(Error exception, StackTraceElement responsibleClass) {
94112
}
95113
if (seenOutputs.get() == null && seenOutputs.compareAndSet(null, ConcurrentHashMap.newKeySet())) {
96114
/* First output, we print an explanation message */
97-
System.out.println("Note: this run will print partial stack traces of the locations where a " + exception.getClass().toString() + " would be thrown " +
98-
"when the -H:+ThrowMissingRegistrationErrors option is set. The trace stops at the first entry of JDK code and provides " +
115+
System.out.println("Note: this run will print partial stack traces of the locations where a " + exception.getClass() + " would be thrown " +
116+
"when the '-XX:MissingRegistrationReportingMode=Warn'" +
117+
" option is set. The trace stops at the first entry of JDK code and provides " +
99118
SubstrateOptions.MissingRegistrationWarnContextLines.getValue() + " lines of context.");
100119
}
101120
String output = sb.toString();
@@ -129,6 +148,76 @@ private static void printLine(StringBuilder sb, Object object) {
129148
sb.append(" ").append(object).append(System.lineSeparator());
130149
}
131150

151+
protected static JsonWriter getJSONWriter(StringWriter json) throws IOException {
152+
return new JsonPrettyWriter(json).indent().appendIndentation();
153+
}
154+
155+
protected static String elementToJSON(JsonPrintable element) {
156+
var json = new StringWriter();
157+
try {
158+
element.printJson(getJSONWriter(json));
159+
} catch (IOException e) {
160+
VMError.shouldNotReachHere("Writing to JSON to memory");
161+
}
162+
return json.toString();
163+
}
164+
165+
protected static String quote(String element) {
166+
return "'" + element + "'";
167+
}
168+
169+
protected static String registrationMessage(String failedAction, String elementDescriptor, String json, String accessManner, String section, String helpLink) {
170+
/* Can't use multi-line strings as they pull in format and bloat "Hello, World!" */
171+
String optionalSpace = accessManner.isEmpty() ? "" : " ";
172+
return "Cannot" + optionalSpace + accessManner + " " + failedAction + " " + elementDescriptor + ". To allow this operation, add the following to the '" + section +
173+
"' section of 'reachability-metadata.json' and rebuild the native image:" + System.lineSeparator() +
174+
System.lineSeparator() +
175+
json + System.lineSeparator() +
176+
System.lineSeparator() +
177+
"The 'reachability-metadata.json' file should be located in 'META-INF/native-image/<group-id>/<artifact-id>/' of your project. For further help, see https://www.graalvm.org/latest/reference-manual/native-image/metadata/#" +
178+
helpLink;
179+
}
180+
181+
protected static ConfigurationType namedConfigurationType(String typeName) {
182+
return new ConfigurationType(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(typeName), true);
183+
}
184+
185+
protected static void addField(ConfigurationType type, String fieldName) {
186+
type.addField(fieldName, ConfigurationMemberInfo.ConfigurationMemberDeclaration.PRESENT, false);
187+
}
188+
189+
protected static void addMethod(ConfigurationType type, String methodName, Class<?>[] paramTypes) {
190+
List<ConfigurationType> params = new ArrayList<>();
191+
if (paramTypes != null) {
192+
for (Class<?> paramType : paramTypes) {
193+
params.add(namedConfigurationType(paramType.getTypeName()));
194+
}
195+
}
196+
type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(params), ConfigurationMemberInfo.ConfigurationMemberDeclaration.PRESENT);
197+
}
198+
199+
protected static ConfigurationType getConfigurationType(Class<?> declaringClass) {
200+
return new ConfigurationType(UnresolvedConfigurationCondition.alwaysTrue(), ConfigurationTypeDescriptor.fromClass(declaringClass), true);
201+
}
202+
203+
protected static String typeDescriptor(Class<?> clazz) {
204+
if (Proxy.isProxyClass(clazz)) {
205+
return "proxy class inheriting " + interfacesString(clazz.getInterfaces());
206+
} else if (LambdaUtils.isLambdaClass(clazz)) {
207+
String declaringClass = LambdaUtils.capturingClass(clazz.getTypeName());
208+
return "lambda-proxy class declared in " + quote(declaringClass) + " inheriting " + interfacesString(clazz.getInterfaces());
209+
} else {
210+
return quote(clazz.getTypeName());
211+
}
212+
}
213+
214+
protected static String interfacesString(Class<?>[] classes) {
215+
return Arrays.stream(classes)
216+
.map(Class::getTypeName)
217+
.map(MissingRegistrationUtils::quote)
218+
.collect(Collectors.joining(",", "[", "]"));
219+
}
220+
132221
public static final class ExitException extends Error {
133222
@Serial//
134223
private static final long serialVersionUID = -3638940737396726143L;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ private static DynamicHub slowPathHubOrUnsafeInstantiationError(DynamicHub hub)
382382
return hub;
383383
} else {
384384
if (MissingRegistrationUtils.throwMissingRegistrationErrors()) {
385-
MissingReflectionRegistrationUtils.forUnsafeAllocation(hub.getTypeName());
385+
MissingReflectionRegistrationUtils.reportUnsafeAllocation(DynamicHub.toClass(hub));
386386
}
387387
throw new IllegalArgumentException("Type " + DynamicHub.toClass(hub).getTypeName() + " is instantiated reflectively but was never registered." +
388388
" Register the type by adding \"unsafeAllocated\" for the type in " + ConfigurationFile.REFLECTION.getFileName() + ".");
@@ -417,7 +417,7 @@ private static void arrayHubErrorStub(DynamicHub elementType) {
417417
} else if (elementType == DynamicHub.fromClass(void.class)) {
418418
throw new IllegalArgumentException("Cannot allocate void array.");
419419
} else if (elementType.getArrayHub() == null || !elementType.getArrayHub().isInstantiated()) {
420-
throw MissingReflectionRegistrationUtils.errorForArray(DynamicHub.toClass(elementType), 1);
420+
throw MissingReflectionRegistrationUtils.reportArrayInstantiation(DynamicHub.toClass(elementType), 1);
421421
} else {
422422
VMError.shouldNotReachHereUnexpectedInput(elementType); // ExcludeFromJacocoGeneratedReport
423423
}

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
@@ -330,7 +330,7 @@ private static Class<?> forName(String className, ClassLoader classLoader, boole
330330
}
331331
} else if (result == null) {
332332
if (throwMissingRegistrationErrors()) {
333-
MissingReflectionRegistrationUtils.forClass(className);
333+
MissingReflectionRegistrationUtils.reportClassAccess(className);
334334
}
335335

336336
if (returnNullOnException) {

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ private void checkClassFlag(int mask, String methodName) {
713713
traceClassFlagQuery(mask);
714714
}
715715
if (throwMissingRegistrationErrors() && !(isClassFlagSet(mask) && getConditions().satisfied())) {
716-
MissingReflectionRegistrationUtils.forBulkQuery(DynamicHub.toClass(this), methodName);
716+
MissingReflectionRegistrationUtils.reportClassQuery(DynamicHub.toClass(this), methodName);
717717
}
718718
}
719719

@@ -1321,7 +1321,7 @@ private void checkField(String fieldName, Field field, boolean publicOnly) throw
13211321

13221322
if (field == null) {
13231323
if (throwMissingErrors && !allElementsRegistered(publicOnly, ALL_DECLARED_FIELDS_FLAG, ALL_FIELDS_FLAG)) {
1324-
MissingReflectionRegistrationUtils.forField(clazz, fieldName);
1324+
MissingReflectionRegistrationUtils.reportFieldQuery(clazz, fieldName);
13251325
}
13261326
/*
13271327
* If getDeclaredFields (or getFields for a public field) is registered, we know for
@@ -1334,7 +1334,7 @@ private void checkField(String fieldName, Field field, boolean publicOnly) throw
13341334
boolean negative = decoder.isNegative(fieldModifiers);
13351335
boolean hiding = decoder.isHiding(fieldModifiers);
13361336
if (throwMissingErrors && hiding) {
1337-
MissingReflectionRegistrationUtils.forField(clazz, fieldName);
1337+
MissingReflectionRegistrationUtils.reportFieldQuery(clazz, fieldName);
13381338
}
13391339
if (negative || hiding) {
13401340
throw new NoSuchFieldException(fieldName);
@@ -1407,7 +1407,7 @@ private boolean checkExecutableExists(String methodName, Class<?>[] parameterTyp
14071407
int allPublicFlag = isConstructor ? ALL_CONSTRUCTORS_FLAG : ALL_METHODS_FLAG;
14081408
if (throwMissingErrors && !allElementsRegistered(publicOnly, allDeclaredFlag, allPublicFlag) &&
14091409
!(isConstructor && isInterface())) {
1410-
MissingReflectionRegistrationUtils.forMethod(clazz, methodName, parameterTypes);
1410+
MissingReflectionRegistrationUtils.reportMethodQuery(clazz, methodName, parameterTypes);
14111411
}
14121412
/*
14131413
* If getDeclaredMethods (or getMethods for a public method) is registered, we know for
@@ -1421,7 +1421,7 @@ private boolean checkExecutableExists(String methodName, Class<?>[] parameterTyp
14211421
boolean negative = decoder.isNegative(methodModifiers);
14221422
boolean hiding = decoder.isHiding(methodModifiers);
14231423
if (throwMissingErrors && hiding) {
1424-
MissingReflectionRegistrationUtils.forMethod(clazz, methodName, parameterTypes);
1424+
MissingReflectionRegistrationUtils.reportMethodQuery(clazz, methodName, parameterTypes);
14251425
}
14261426
return !(negative || hiding);
14271427
}
@@ -1974,7 +1974,7 @@ public DynamicHub arrayType() {
19741974
throw new UnsupportedOperationException(new IllegalArgumentException());
19751975
}
19761976
if (companion.arrayHub == null) {
1977-
MissingReflectionRegistrationUtils.forClass(getTypeName() + "[]");
1977+
MissingReflectionRegistrationUtils.reportClassAccess(getTypeName() + "[]");
19781978
} else if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) {
19791979
MetadataTracer.singleton().traceReflectionType(companion.arrayHub.getTypeName());
19801980
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassRegistries.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ public static Class<?> findBootstrapClass(String name) {
163163

164164
public static Class<?> findLoadedClass(String name, ClassLoader loader) {
165165
if (throwMissingRegistrationErrors() && RuntimeClassLoading.followReflectionConfiguration() && !ClassForNameSupport.isRegisteredClass(name)) {
166-
MissingReflectionRegistrationUtils.forClass(name);
166+
MissingReflectionRegistrationUtils.reportClassAccess(name);
167167
return null;
168168
}
169169
ByteSequence typeBytes = ByteSequence.createTypeFromName(name);
@@ -203,7 +203,7 @@ private Class<?> resolveOrThrowException(String name, ClassLoader loader) throws
203203
private Class<?> resolve(String name, ClassLoader loader) throws ClassNotFoundException {
204204
if (RuntimeClassLoading.followReflectionConfiguration()) {
205205
if (throwMissingRegistrationErrors() && !ClassForNameSupport.isRegisteredClass(name)) {
206-
MissingReflectionRegistrationUtils.forClass(name);
206+
MissingReflectionRegistrationUtils.reportClassAccess(name);
207207
if (loader == null) {
208208
return null;
209209
}
@@ -301,7 +301,7 @@ private static Class<?> getArrayClass(String name, Class<?> elementalResult, int
301301
RuntimeClassLoading.getOrCreateArrayHub(hub);
302302
} else {
303303
if (throwMissingRegistrationErrors()) {
304-
MissingReflectionRegistrationUtils.forClass(name);
304+
MissingReflectionRegistrationUtils.reportClassAccess(name);
305305
}
306306
return null;
307307
}
@@ -316,7 +316,7 @@ public static Class<?> defineClass(ClassLoader loader, String name, byte[] b, in
316316
// name is a "binary name": `foo.Bar$1`
317317
assert RuntimeClassLoading.isSupported();
318318
if (RuntimeClassLoading.followReflectionConfiguration() && throwMissingRegistrationErrors() && !ClassForNameSupport.isRegisteredClass(name)) {
319-
MissingReflectionRegistrationUtils.forClass(name);
319+
MissingReflectionRegistrationUtils.reportClassAccess(name);
320320
// The defineClass path usually can't throw ClassNotFoundException
321321
throw sneakyThrow(new ClassNotFoundException(name));
322322
}

0 commit comments

Comments
 (0)