Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-47056] Do not print reflection warnings in JSON files. #7157

Merged
merged 2 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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