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-44291] Automatically register resources when the class and the pattern are constant. #6177

Merged
merged 5 commits into from
Mar 17, 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
15 changes: 10 additions & 5 deletions docs/reference-manual/native-image/ReachabilityMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,15 @@ Java is capable of accessing any resource on the application class path, or the
Resource metadata instructs the `native-image` builder to include specified resources and resource bundles in the produced binary.
A consequence of this approach is that some parts of the application that use resources for configuration (such as logging) are effectively configured at build time.

The code below accesses a text file and requires providing resource metadata:
### Resource Metadata In Code
Native Image will detect calls to `java.lang.Class#getResource` and `java.lang.Class#getResourceAsStream` in which:
- The class on which these methods are called is constant
- The first parameter, `name`, is a constant
and automatically register such resources.

The code below will work out of the box, because:
- We are using a class literal (`Example.class`)
- We are using a string literal as the `name` parameter
```java
class Example {
public void conquerTheWorld() {
Expand All @@ -232,9 +240,6 @@ class Example {
}
```

### Resource Metadata In Code
It is not possible to specify used resources and resource bundles in code.

### Resource Metadata in JSON
Metadata for resources is provided in `resource-config.json` files.
```json
Expand Down Expand Up @@ -401,4 +406,4 @@ The JSON schema is accompanied by the `agent-extracted-predefined-classes` direc

* [Metadata Collection with the Tracing Agent](AutomaticMetadataCollection.md)
* [Native Image Compatibility Guide](Compatibility.md)
* [GraalVM Reachability Metadata Repository](https://github.com/oracle/graalvm-reachability-metadata)
* [GraalVM Reachability Metadata Repository](https://github.com/oracle/graalvm-reachability-metadata)
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@
import static com.oracle.svm.core.jdk.Resources.RESOURCES_INTERNAL_PATH_SEPARATOR;

import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.nio.file.FileSystem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -43,15 +49,24 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.nodes.ValueNode;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.options.OptionType;
import org.graalvm.compiler.phases.util.Providers;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.RuntimeResourceAccess;
import org.graalvm.nativeimage.impl.ConfigurationCondition;
import org.graalvm.nativeimage.impl.RuntimeResourceSupport;

import com.oracle.svm.core.ClassLoaderSupport;
import com.oracle.svm.core.ClassLoaderSupport.ResourceCollector;
import com.oracle.svm.core.ParsingReason;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.configure.ConfigurationFile;
import com.oracle.svm.core.configure.ConfigurationFiles;
Expand All @@ -67,9 +82,13 @@
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.option.LocatableMultiOptionValue;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
import com.oracle.svm.hosted.config.ConfigurationParserUtils;
import com.oracle.svm.hosted.jdk.localization.LocalizationFeature;
import com.oracle.svm.util.ReflectionUtil;

import jdk.vm.ci.meta.ResolvedJavaMethod;

/**
* <p>
Expand Down Expand Up @@ -406,4 +425,41 @@ private static void registerDirectoryResource(DebugContext debugContext, String
Resources.registerDirectoryResource(moduleName, dir, content, fromJar);
}
}

@Override
public void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, GraphBuilderConfiguration.Plugins plugins, ParsingReason reason) {
Method[] resourceMethods = {
ReflectionUtil.lookupMethod(Class.class, "getResource", String.class),
ReflectionUtil.lookupMethod(Class.class, "getResourceAsStream", String.class)
};
Method resolveResourceName = ReflectionUtil.lookupMethod(Class.class, "resolveName", String.class);

for (Method method : resourceMethods) {
registerResourceRegistrationPlugin(plugins.getInvocationPlugins(), method, snippetReflection, resolveResourceName);
}
}

private void registerResourceRegistrationPlugin(InvocationPlugins plugins, Method method, SnippetReflectionProvider snippetReflectionProvider, Method resolveResourceName) {
List<Class<?>> parameterTypes = new ArrayList<>();
assert !Modifier.isStatic(method.getModifiers());
parameterTypes.add(InvocationPlugin.Receiver.class);
parameterTypes.addAll(Arrays.asList(method.getParameterTypes()));

plugins.register(method.getDeclaringClass(), new InvocationPlugin.RequiredInvocationPlugin(method.getName(), parameterTypes.toArray(new Class<?>[0])) {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode arg) {
try {
if (!sealed && receiver.isConstant() && arg.isJavaConstant() && !arg.isNullConstant()) {
String resource = snippetReflectionProvider.asObject(String.class, arg.asJavaConstant());
Class<?> clazz = snippetReflectionProvider.asObject(Class.class, receiver.get().asJavaConstant());
String resourceName = (String) resolveResourceName.invoke(clazz, resource);
RuntimeResourceAccess.addResource(clazz.getModule(), resourceName);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw VMError.shouldNotReachHere(e);
}
return false;
}
});
}
}