-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Background
Spring Framework provides a Resource
abstraction for classpath resources that supports creation of relative resources. The Resource
abstraction also provides support to determine if any given Resource
exists based on various techniques for detecting the presence of such a resource. For a Spring UrlResource
, the exists()
method is inherited from AbstractFileResolvingResource
as can be seen below.
The NonexistentClasspathResource
test case provided in this issue reproduces a bug detected in the Spring Framework while running within a native image.
Overview
If a relative URL
for a nonexistent resource is created based on an existing classpath resource within a native image, the nonexistent resource appears to exist within the native image even though it is not present.
For example, given the NonexistentClasspathResource
test case below, the output when using a standard JDK results in the following.
+ SUCCESS: found classpath resource: NonexistentClasspathResource.class
+ getContentLengthLong() for file:/nonexistent_classpath_resource/NonexistentClasspathResource.class: 466
+ SUCCESS: classpath resource exists: file:/nonexistent_classpath_resource/NonexistentClasspathResource.class
+ SUCCESS: bogus classpath resource does not exist: file:/nonexistent_classpath_resource/Bogus.class
Whereas, if you compile the test case into a native image, the output is the following.
- FAILURE: could not find classpath resource: NonexistentClasspathResource.class
If you execute the test case with the GraalVM native image agent, it generates the following in resource-config.json
:
{
"resources":[
{"pattern":"\\QMETA-INF/services/jdk.vm.ci.hotspot.HotSpotJVMCIBackendFactory\\E"},
{"pattern":"\\QMETA-INF/services/jdk.vm.ci.services.JVMCIServiceLocator\\E"},
{"pattern":"\\QNonexistentClasspathResource.class\\E"}
],
"bundles":[]
}
If you then build the native image with the configuration generated by the agent, the output is the following.
+ SUCCESS: found classpath resource: NonexistentClasspathResource.class
+ getContentLengthLong() for resource:NonexistentClasspathResource.class: 466
+ SUCCESS: classpath resource exists: resource:NonexistentClasspathResource.class
- getContentLengthLong() for resource:Bogus.class: 466
- FAILURE: bogus classpath resource appears to exist: resource:Bogus.class
The above FAILURE points out the bug within a native image.
The first resource is now found; however, the second "bogus" resource also appears to exist within the native image. Note that java.net.URLConnection.getContentLengthLong()
returns a positive value for the nonexistent resource that is identical to the value returned for the existing resource (466
).
See https://github.com/sbrannen/graalvm-playground/tree/master/nonexistent_classpath_resource for full details.
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class NonexistentClasspathResource {
public static void main(String[] args) throws Exception {
new Tester().test();
}
static class Tester {
void test() throws Exception {
String name = "NonexistentClasspathResource.class";
URL resource1 = getClass().getResource(name);
if (resource1 == null) {
System.err.println("FAILURE: could not find classpath resource: " + name);
return;
}
System.err.println("SUCCESS: found classpath resource: " + name);
if (!exists(resource1)) {
System.err.println("FAILURE: classpath resource does not exist: " + resource1);
return;
}
System.err.println("SUCCESS: classpath resource exists: " + resource1);
URL resource2 = new URL(resource1, "Bogus.class");
if (exists(resource2)) {
System.err.println("FAILURE: bogus classpath resource appears to exist: " + resource2);
return;
}
System.err.println("SUCCESS: bogus classpath resource does not exist: " + resource2);
}
private boolean exists(URL url) {
try {
// Try a URL connection content-length header
URLConnection con = url.openConnection();
if (con.getContentLengthLong() > 0) {
System.err.format("getContentLengthLong() for %s: %d%n", url, con.getContentLengthLong());
return true;
}
// Fall back to stream existence: can we open the stream?
getInputStream(url).close();
System.err.println("opened InputStream for " + url);
return true;
}
catch (IOException ex) {
return false;
}
}
private InputStream getInputStream(URL url) throws IOException {
return url.openConnection().getInputStream();
}
}
}
Environment
- GraalVM version: CE 20.0
- JDK major version: 8
- OS: macOS 10.14.6
- Architecture: i386 / x86_64