Skip to content

Commit b6f8562

Browse files
committed
[GR-65589] Provide a concrete action in dynamic access error messages.
PullRequest: graal/21123
2 parents 6cbebef + c5d892d commit b6f8562

25 files changed

+325
-188
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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@
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 com.oracle.svm.util.StringUtil;
33+
34+
import jdk.graal.compiler.java.LambdaUtils;
2935
import jdk.graal.compiler.util.json.JsonPrintable;
3036

3137
/**
@@ -44,6 +50,19 @@ enum Kind {
4450
LAMBDA
4551
}
4652

53+
static ConfigurationTypeDescriptor fromClass(Class<?> clazz) {
54+
Stream<String> interfacesStream = Arrays.stream(clazz.getInterfaces())
55+
.map(Class::getTypeName);
56+
if (Proxy.isProxyClass(clazz)) {
57+
return ProxyConfigurationTypeDescriptor.fromInterfaceReflectionNames(interfacesStream.toList());
58+
} else if (LambdaUtils.isLambdaClass(clazz)) {
59+
String declaringClass = StringUtil.split(clazz.getTypeName(), LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)[0];
60+
return LambdaConfigurationTypeDescriptor.fromReflectionNames(declaringClass, interfacesStream.toList());
61+
} else {
62+
return NamedConfigurationTypeDescriptor.fromReflectionName(clazz.getTypeName());
63+
}
64+
}
65+
4766
Kind getDescriptorType();
4867

4968
@Override

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

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,32 @@
3333
import java.util.ArrayList;
3434
import java.util.Arrays;
3535
import java.util.Collection;
36-
import java.util.Comparator;
3736
import java.util.List;
3837

3938
import jdk.graal.compiler.util.json.JsonPrinter;
4039
import jdk.graal.compiler.util.json.JsonWriter;
4140

4241
public record LambdaConfigurationTypeDescriptor(ConfigurationTypeDescriptor declaringClass, ConfigurationParser.ConfigurationMethodDescriptor declaringMethod,
4342
List<NamedConfigurationTypeDescriptor> interfaces) implements ConfigurationTypeDescriptor {
43+
44+
public static final ConfigurationTypeDescriptor[] EMPTY_TYPE_DESCRIPTOR_ARRAY = new ConfigurationTypeDescriptor[0];
45+
4446
public static LambdaConfigurationTypeDescriptor fromReflectionNames(String declaringClass, List<String> interfaces) {
45-
return new LambdaConfigurationTypeDescriptor(NamedConfigurationTypeDescriptor.fromReflectionName(declaringClass), null,
46-
interfaces.stream().map(NamedConfigurationTypeDescriptor::fromReflectionName).toList());
47+
return new LambdaConfigurationTypeDescriptor(NamedConfigurationTypeDescriptor.fromReflectionName(declaringClass),
48+
null, getNamedConfigurationTypeDescriptors(interfaces));
4749
}
4850

4951
public static LambdaConfigurationTypeDescriptor fromTypeNames(String declaringClass, List<String> interfaces) {
5052
return new LambdaConfigurationTypeDescriptor(NamedConfigurationTypeDescriptor.fromTypeName(declaringClass), null,
51-
interfaces.stream().map(NamedConfigurationTypeDescriptor::fromTypeName).toList());
53+
getNamedConfigurationTypeDescriptors(interfaces));
54+
}
55+
56+
private static List<NamedConfigurationTypeDescriptor> getNamedConfigurationTypeDescriptors(List<String> interfaces) {
57+
List<NamedConfigurationTypeDescriptor> reflectionInterfaces = new ArrayList<>(interfaces.size());
58+
for (var interfaceName : interfaces) {
59+
reflectionInterfaces.add(NamedConfigurationTypeDescriptor.fromReflectionName(interfaceName));
60+
}
61+
return reflectionInterfaces;
5262
}
5363

5464
@Override
@@ -67,11 +77,26 @@ public Collection<String> getAllQualifiedJavaNames() {
6777

6878
@Override
6979
public int compareTo(ConfigurationTypeDescriptor other) {
70-
if (other instanceof LambdaConfigurationTypeDescriptor lambdaOther) {
71-
return Comparator.comparing(LambdaConfigurationTypeDescriptor::declaringClass)
72-
.thenComparing(LambdaConfigurationTypeDescriptor::declaringMethod, Comparator.nullsFirst(ConfigurationParser.ConfigurationMethodDescriptor::compareTo))
73-
.thenComparing((a, b) -> Arrays.compare(a.interfaces.toArray(ConfigurationTypeDescriptor[]::new), b.interfaces.toArray(ConfigurationTypeDescriptor[]::new)))
74-
.compare(this, lambdaOther);
80+
if (other instanceof LambdaConfigurationTypeDescriptor otherLambda) {
81+
int result = declaringClass.compareTo(otherLambda.declaringClass());
82+
if (result != 0) {
83+
return result;
84+
}
85+
// Compare declaringMethod with nullsFirst
86+
if (this.declaringMethod() == null && otherLambda.declaringMethod() != null) {
87+
return -1;
88+
} else if (this.declaringMethod() != null && otherLambda.declaringMethod() == null) {
89+
return 1;
90+
} else if (this.declaringMethod() != null) {
91+
result = this.declaringMethod().compareTo(otherLambda.declaringMethod());
92+
if (result != 0) {
93+
return result;
94+
}
95+
}
96+
97+
return Arrays.compare(
98+
interfaces.toArray(EMPTY_TYPE_DESCRIPTOR_ARRAY),
99+
otherLambda.interfaces().toArray(EMPTY_TYPE_DESCRIPTOR_ARRAY));
75100
} else {
76101
return getDescriptorType().compareTo(other.getDescriptorType());
77102
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -498,10 +498,9 @@ public synchronized void printJson(JsonWriter writer) throws IOException {
498498
Set<ConfigurationMethod> accessedMethods = getMethodsByAccessibility(ConfigurationMemberAccessibility.ACCESSED);
499499
if (!accessedMethods.isEmpty()) {
500500
writer.appendSeparator().quote("methods").appendFieldSeparator();
501-
JsonPrinter.printCollection(writer,
502-
accessedMethods,
503-
Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))),
504-
JsonPrintable::printJson);
501+
Comparator<ConfigurationMethod> methodComparator = Comparator.comparing(ConfigurationMethod::getName)
502+
.thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature)));
503+
JsonPrinter.printCollection(writer, accessedMethods, methodComparator, JsonPrintable::printJson);
505504
}
506505
}
507506

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: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,40 @@
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;
50+
import com.oracle.svm.util.StringUtil;
3551

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

38-
public static final String ERROR_EMPHASIS_INDENT = " ";
57+
public class MissingRegistrationUtils {
3958

4059
public static boolean throwMissingRegistrationErrors() {
41-
return SubstrateOptions.ThrowMissingRegistrationErrors.hasBeenSet();
60+
return ThrowMissingRegistrationErrors.hasBeenSet();
4261
}
4362

4463
public static SubstrateOptions.ReportingMode missingRegistrationReportingMode() {
@@ -94,8 +113,9 @@ public static void report(Error exception, StackTraceElement responsibleClass) {
94113
}
95114
if (seenOutputs.get() == null && seenOutputs.compareAndSet(null, ConcurrentHashMap.newKeySet())) {
96115
/* 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 " +
116+
System.out.println("Note: this run will print partial stack traces of the locations where a " + exception.getClass() + " would be thrown " +
117+
"when the '-XX:MissingRegistrationReportingMode=Warn'" +
118+
" option is set. The trace stops at the first entry of JDK code and provides " +
99119
SubstrateOptions.MissingRegistrationWarnContextLines.getValue() + " lines of context.");
100120
}
101121
String output = sb.toString();
@@ -129,6 +149,76 @@ private static void printLine(StringBuilder sb, Object object) {
129149
sb.append(" ").append(object).append(System.lineSeparator());
130150
}
131151

152+
protected static JsonWriter getJSONWriter(StringWriter json) throws IOException {
153+
return new JsonPrettyWriter(json).indent().appendIndentation();
154+
}
155+
156+
protected static String elementToJSON(JsonPrintable element) {
157+
var json = new StringWriter();
158+
try {
159+
element.printJson(getJSONWriter(json));
160+
} catch (IOException e) {
161+
VMError.shouldNotReachHere("Writing to JSON to memory");
162+
}
163+
return json.toString();
164+
}
165+
166+
protected static String quote(String element) {
167+
return "'" + element + "'";
168+
}
169+
170+
protected static String registrationMessage(String failedAction, String elementDescriptor, String json, String accessManner, String section, String helpLink) {
171+
/* Can't use multi-line strings as they pull in format and bloat "Hello, World!" */
172+
String optionalSpace = accessManner.isEmpty() ? "" : " ";
173+
return "Cannot" + optionalSpace + accessManner + " " + failedAction + " " + elementDescriptor + ". To allow this operation, add the following to the '" + section +
174+
"' section of 'reachability-metadata.json' and rebuild the native image:" + System.lineSeparator() +
175+
System.lineSeparator() +
176+
json + System.lineSeparator() +
177+
System.lineSeparator() +
178+
"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/#" +
179+
helpLink;
180+
}
181+
182+
protected static ConfigurationType namedConfigurationType(String typeName) {
183+
return new ConfigurationType(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(typeName), true);
184+
}
185+
186+
protected static void addField(ConfigurationType type, String fieldName) {
187+
type.addField(fieldName, ConfigurationMemberInfo.ConfigurationMemberDeclaration.PRESENT, false);
188+
}
189+
190+
protected static void addMethod(ConfigurationType type, String methodName, Class<?>[] paramTypes) {
191+
List<ConfigurationType> params = new ArrayList<>();
192+
if (paramTypes != null) {
193+
for (Class<?> paramType : paramTypes) {
194+
params.add(namedConfigurationType(paramType.getTypeName()));
195+
}
196+
}
197+
type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(params), ConfigurationMemberInfo.ConfigurationMemberDeclaration.PRESENT);
198+
}
199+
200+
protected static ConfigurationType getConfigurationType(Class<?> declaringClass) {
201+
return new ConfigurationType(UnresolvedConfigurationCondition.alwaysTrue(), ConfigurationTypeDescriptor.fromClass(declaringClass), true);
202+
}
203+
204+
protected static String typeDescriptor(Class<?> clazz) {
205+
if (Proxy.isProxyClass(clazz)) {
206+
return "proxy class inheriting " + interfacesString(clazz.getInterfaces());
207+
} else if (LambdaUtils.isLambdaClass(clazz)) {
208+
String declaringClass = StringUtil.split(clazz.getTypeName(), LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)[0];
209+
return "lambda-proxy class declared in " + quote(declaringClass) + " inheriting " + interfacesString(clazz.getInterfaces());
210+
} else {
211+
return quote(clazz.getTypeName());
212+
}
213+
}
214+
215+
protected static String interfacesString(Class<?>[] classes) {
216+
return Arrays.stream(classes)
217+
.map(Class::getTypeName)
218+
.map(MissingRegistrationUtils::quote)
219+
.collect(Collectors.joining(",", "[", "]"));
220+
}
221+
132222
public static final class ExitException extends Error {
133223
@Serial//
134224
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
@@ -385,7 +385,7 @@ private static Class<?> forName(String className, ClassLoader classLoader, boole
385385
}
386386
} else if (result == null) {
387387
if (throwMissingRegistrationErrors()) {
388-
MissingReflectionRegistrationUtils.forClass(className);
388+
MissingReflectionRegistrationUtils.reportClassAccess(className);
389389
}
390390

391391
if (returnNullOnException) {

0 commit comments

Comments
 (0)