Skip to content

Commit

Permalink
[GR-47056] Do not print reflection warnings in JSON files.
Browse files Browse the repository at this point in the history
PullRequest: graal/15083
  • Loading branch information
vjovanov committed Aug 11, 2023
2 parents 7416cea + bfa8f86 commit ca917af
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public void printJson(JsonWriter writer) throws IOException {

@Override
public ConfigurationParser createParser() {
return new ReflectionConfigurationParser<>(new ParserConfigurationAdapter(this), true);
return new ReflectionConfigurationParser<>(new ParserConfigurationAdapter(this), true, false);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@ public static final class Options {
public static final HostedOptionKey<LocatableMultiOptionValue.Strings> SerializationDenyConfigurationResources = new HostedOptionKey<>(
LocatableMultiOptionValue.Strings.buildWithCommaDelimiter());

@Option(help = "Files describing Java resources to be included in the image.", type = OptionType.User)//
@Option(help = "Files describing Java resources to be included in the image according to the schema at " +
"https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", type = OptionType.User)//
@BundleMember(role = BundleMember.Role.Input)//
public static final HostedOptionKey<LocatableMultiOptionValue.Paths> ResourceConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter());
@Option(help = "Resources describing Java resources to be included in the image.", type = OptionType.User)//
@Option(help = "Resources describing Java resources to be included in the image according to the schema at " +
"https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", type = OptionType.User)//
public static final HostedOptionKey<LocatableMultiOptionValue.Strings> ResourceConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter());

@Option(help = "Files describing program elements to be made accessible via JNI (for syntax, see ReflectionConfigurationFiles)", type = OptionType.User)//
Expand All @@ -99,15 +101,20 @@ public static final class Options {
@Option(help = "Resources describing stubs allowing foreign calls.", type = OptionType.User)//
public static final HostedOptionKey<LocatableMultiOptionValue.Strings> ForeignResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter());

@Option(help = "Files describing predefined classes that can be loaded at runtime.", type = OptionType.User)//
@Option(help = "Files describing predefined classes that can be loaded at runtime according to the schema at " +
"https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", type = OptionType.User)//
@BundleMember(role = BundleMember.Role.Input)//
public static final HostedOptionKey<LocatableMultiOptionValue.Paths> PredefinedClassesConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter());
@Option(help = "Resources describing predefined classes that can be loaded at runtime.", type = OptionType.User)//
@Option(help = "Resources describing predefined classes that can be loaded at runtime according to the schema at " +
"https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", type = OptionType.User)//
public static final HostedOptionKey<LocatableMultiOptionValue.Strings> PredefinedClassesConfigurationResources = new HostedOptionKey<>(
LocatableMultiOptionValue.Strings.buildWithCommaDelimiter());

@Option(help = "Causes unknown attributes in configuration objects to abort the image build instead of emitting a warning.")//
@Option(help = "When configuration files do not match their schema, abort the image build instead of emitting a warning.")//
public static final HostedOptionKey<Boolean> StrictConfiguration = new HostedOptionKey<>(false);

@Option(help = "Warn when reflection and JNI configuration files have elements that could not be found on the classpath or modulepath.", type = OptionType.Expert)//
public static final HostedOptionKey<Boolean> WarnAboutMissingReflectionOrJNIMetadataElements = new HostedOptionKey<>(false);
}

public static List<Path> findConfigurationFiles(String fileName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ public static InputStream openStream(URI uri) throws IOException {
public static final String CONDITIONAL_KEY = "condition";
public static final String TYPE_REACHABLE_KEY = "typeReachable";
private final Map<String, Set<String>> seenUnknownAttributesByType = new HashMap<>();
private final boolean strictConfiguration;
private final boolean strictSchema;

protected ConfigurationParser(boolean strictConfiguration) {
this.strictConfiguration = strictConfiguration;
this.strictSchema = strictConfiguration;
}

public void parseAndRegister(URI uri) throws IOException {
Expand Down Expand Up @@ -121,14 +121,20 @@ protected void checkAttributes(EconomicMap<String, Object> map, String type, Col

if (unknownAttributes.size() > 0) {
String message = "Unknown attribute(s) [" + String.join(", ", unknownAttributes) + "] in " + type;
warnOrFail(message);
warnOrFailOnSchemaError(message);
Set<String> unknownAttributesForType = seenUnknownAttributesByType.computeIfAbsent(type, key -> new HashSet<>());
unknownAttributesForType.addAll(unknownAttributes);
}
}

protected void warnOrFail(String message) {
if (strictConfiguration) {
/**
* Used to warn about schema errors in configuration files. Should never be used if the type is
* missing.
*
* @param message message to be displayed.
*/
protected void warnOrFailOnSchemaError(String message) {
if (strictSchema) {
throw new JSONParserException(message);
} else {
LogUtils.warning(message);
Expand Down Expand Up @@ -182,7 +188,7 @@ protected ConfigurationCondition parseCondition(EconomicMap<String, Object> data
if (conditionType instanceof String) {
return ConfigurationCondition.create((String) conditionType);
} else {
warnOrFail("'" + TYPE_REACHABLE_KEY + "' should be of type string");
warnOrFailOnSchemaError("'" + TYPE_REACHABLE_KEY + "' should be of type string");
}
}
return ConfigurationCondition.alwaysTrue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ public final class ReflectionConfigurationParser<T> extends ConfigurationParser
"allDeclaredClasses", "allRecordComponents", "allPermittedSubclasses", "allNestMembers", "allSigners",
"allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY,
"queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated");
private final boolean printMissingElements;

public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate<T> delegate) {
this(delegate, true);
this(delegate, true, false);
}

public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate<T> delegate, boolean strictConfiguration) {
public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate<T> delegate, boolean strictConfiguration, boolean printMissingElements) {
super(strictConfiguration);
this.printMissingElements = printMissingElements;
this.delegate = delegate;
}

Expand Down Expand Up @@ -91,7 +93,7 @@ private void parseClass(EconomicMap<String, Object> data) {
*/
TypeResult<T> result = delegate.resolveType(condition, className, true);
if (!result.isPresent()) {
handleError("Could not resolve class " + className + " for reflection configuration.", result.getException());
handleMissingElement("Could not resolve class " + className + " for reflection configuration.", result.getException());
return;
}
T clazz = result.get();
Expand Down Expand Up @@ -199,7 +201,7 @@ private void parseClass(EconomicMap<String, Object> data) {
break;
}
} catch (LinkageError e) {
handleError("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e);
handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e);
}
}
}
Expand All @@ -218,9 +220,9 @@ private void parseField(EconomicMap<String, Object> data, T clazz) {
try {
delegate.registerField(clazz, fieldName, allowWrite);
} catch (NoSuchFieldException e) {
handleError("Field " + formatField(clazz, fieldName) + " not found.");
handleMissingElement("Field " + formatField(clazz, fieldName) + " not found.");
} catch (LinkageError e) {
handleError("Could not register field " + formatField(clazz, fieldName) + " for reflection.", e);
handleMissingElement("Could not register field " + formatField(clazz, fieldName) + " for reflection.", e);
}
}

Expand Down Expand Up @@ -251,9 +253,9 @@ private void parseMethod(boolean queriedOnly, EconomicMap<String, Object> data,
delegate.registerMethod(queriedOnly, clazz, methodName, methodParameterTypes);
}
} catch (NoSuchMethodException e) {
handleError("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found.");
handleMissingElement("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found.");
} catch (LinkageError e) {
handleError("Could not register method " + formatMethod(clazz, methodName, methodParameterTypes) + " for reflection.", e);
handleMissingElement("Could not register method " + formatMethod(clazz, methodName, methodParameterTypes) + " for reflection.", e);
}
} else {
try {
Expand All @@ -267,7 +269,7 @@ private void parseMethod(boolean queriedOnly, EconomicMap<String, Object> data,
throw new JSONParserException("Method " + formatMethod(clazz, methodName) + " not found");
}
} catch (LinkageError e) {
handleError("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", e);
handleMissingElement("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", e);
}
}
}
Expand All @@ -278,7 +280,7 @@ private List<T> parseMethodParameters(T clazz, String methodName, List<Object> t
String typeName = asString(type, "types");
TypeResult<T> typeResult = delegate.resolveType(ConfigurationCondition.alwaysTrue(), typeName, true);
if (!typeResult.isPresent()) {
handleError("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", typeResult.getException());
handleMissingElement("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", typeResult.getException());
return null;
}
result.add(typeResult.get());
Expand All @@ -303,15 +305,17 @@ private String formatMethod(T clazz, String methodName, List<T> paramTypes) {
return delegate.getTypeName(clazz) + '.' + methodName + '(' + parameterTypeNames + ')';
}

private static void handleError(String message) {
handleError(message, null);
private void handleMissingElement(String message) {
handleMissingElement(message, null);
}

private static void handleError(String msg, Throwable cause) {
String message = msg;
if (cause != null) {
message += " Reason: " + formatError(cause) + '.';
private void handleMissingElement(String msg, Throwable cause) {
if (printMissingElements) {
String message = msg;
if (cause != null) {
message += " Reason: " + formatError(cause) + '.';
}
LogUtils.warning(message);
}
LogUtils.warning(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
One or several (comma-separated) paths to JSON files that specify lists of interfaces that define Java proxy classes.
The structure is an array of arrays of fully qualified interface names.
The JSON structure is described in the following schema:

Example:
https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/proxy-config-schema-v1.0.0.json

An example file contents follows:

[
["java.lang.AutoCloseable", "java.util.Comparator"],
["java.util.Comparator"],
["java.util.List"]
{
"condition" : {
"typeReachable" : "org.example.CallingClass"
},
"interfaces" : [
"java.lang.AutoCloseable",
"java.util.Comparator"
]
},
{
"condition" : {
"typeReachable" : "org.example.CallingClass"
},
"interfaces" : [
"java.util.Comparator"
]
}
]
Original file line number Diff line number Diff line change
@@ -1,34 +1,25 @@
One or several (comma-separated) paths to JSON files that specify which program elements should be made available via reflection.
The JSON object schema is:
The JSON object schema is described at:

{
String name; // fully qualified class name
boolean allDeclaredConstructors; // include all declared constructors, see Class.getDeclaredConstructors()
boolean allPublicConstructors; // include all public constructors, see Class.getConstructors()
boolean allDeclaredMethods; // include all declared methods, see Class.getDeclaredMethods()
boolean allPublicMethods; // include all public methods, see Class.getMethods()
boolean allDeclaredFields; // include all declared fields, see Class.getDeclaredFields()
boolean allPublicFields; // include all public fields, see Class.getFields()
{
String name; // method name
String[] parameterTypes; // parameter types (optional, use if ambiguous)
}[] methods;
{
String name; // field name
}[] fields;
}[];
https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.0.0.json

Example:

[
{
"condition" : {
"typeReachable" : "org.example.CallingClass"
},
"name" : "java.lang.Class",
"allDeclaredConstructors" : "true",
"allPublicConstructors" : "true",
"allDeclaredMethods" : "true",
"allPublicMethods" : "true"
},
{
"condition" : {
"typeReachable" : "org.example.CallingClass"
},
"name" : "java.lang.String",
"fields" : [
{ "name" : "value" },
Expand All @@ -42,6 +33,9 @@ Example:
]
},
{
"condition" : {
"typeReachable" : "org.example.CallingClass"
},
"name" : "java.lang.String$CaseInsensitiveComparator",
"methods" : [
{ "name" : "compare" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
One or several (comma-separated) paths to JSON files that specify lists of serialization configurations.
The structure is an array of elements specifying the target serialization/deserialization class.
The structure is described in the following schema:

https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/serialization-config-schema-v1.0.0.json

Example:

[
{
"condition":{"typeReachable":"app.DataSerializer"},
"name":"java.util.ArrayList"
"condition" : {
"typeReachable" : "app.DataSerializer"
},
"name" : "java.util.ArrayList"
}
]

For deserializing lambda classes, the capturing class of the lambda needs to be specified in a separate section of the configuration file, for example:

[
"types": [
{"name":"java.lang.Object"}
"types" : [
{
"name" : "java.lang.Object"
}
],
"lambdaCapturingTypes": [
{"name":"java.util.Comparator"}
"lambdaCapturingTypes" : [
{
"name" : "java.util.Comparator"
}
]
]

Expand All @@ -35,8 +43,10 @@ serialization-config.json:

[
{
"condition":{"typeReachable":"org.apache.spark.SparkContext"},
"name":"org.apache.spark.SparkContext$$anonfun$hadoopFile$1",
"customTargetConstructorClass":"java.lang.Object"
"condition" : {
"typeReachable" : "org.apache.spark.SparkContext"
},
"name" : "org.apache.spark.SparkContext$$anonfun$hadoopFile$1",
"customTargetConstructorClass" : "java.lang.Object"
}
]
Loading

0 comments on commit ca917af

Please sign in to comment.