Skip to content

Nonexistent relative classpath resource appears to exist in native image #2291

@sbrannen

Description

@sbrannen

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.

https://github.com/spring-projects/spring-framework/blob/9fb614a5c60bf6f269edee6b69e543d8ca6af1c3/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java#L46-L86

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions