-
Bug
-
Resolution: Fixed
-
P2
-
9
-
b167
-
Not verified
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8179791 | 10 | Volker Simonis | P2 | Resolved | Fixed | b07 |
If we set the classpath to a UNC share (e.g. '-cp \\foo\bar\classes') java won't be able to find classes there. This is also true if we set the classpath to a drive letter which maps to a network drive (e.g. '-cp Z:\classes' where 'Z:' is a short-cut for '\\foo\bar').
This error is clearly a regression and should be fixed before Java 9 GA.
The problem is caused by the refactoring of the application class loader:
Before Java 9 the application class loader was a URLClassLoader (actually sun.misc.Launcher$AppClassLoader which extends URLClassLoader). It took the classpath from the "java.class.path" property and transfomred it into a File[] by calling Launcher.getClassPath(). This File[] was then converted into a URL[] by calling Launcher.pathToURLs() which in turn called Launcher.getFileURL() which finally called sun.net.www.ParseUtil.fileToEncodedURL() to convert a File into an URL. This last function explicitely creates URLs with protocol 'file' and empty authority (i.e. if the File contained an authority part like "\\foo" in "\\foo\bar\classs"), this was saved in the path part of the URL (i.e. 'return new URL("file", "", path)');
Later, these URLs were used to access classes on the classpath. This was done by creating new File objects from the path components of the corresponding URLs and because these path components already contained the full UNC path, everything worked fine.
In JDK 9 now, the legacy class path is constructed in jdk.internal.loader.ClassLoaders. It also takes the "java.class.path" property as a starting point but calls addClassPathToUCP() which in turn calls toFileURL() which finally uses 'Paths.get(s).toRealPath().toUri().toURL()' to transform a string 's' into an URL. This conversion creates URLs where the autority part contains the hostname and the path component contains the remaining path from the UNC paths in the classpath.
The problem is now that during class loading, the new application class loader jdk.internal.loader.ClassLoaders$AppClassLoader calls jdk.internal.loader.BuiltinClassLoader.loadClass() which calls loadClassOrNull() which calls findClassOnClassPathOrNull() which calls jdk.internal.loader.URLClassPath.getResource(). The class URLClassPath has a list of all the previously created URLs for each entry of the classpath. For each of these URLs it creates a jdk.internal.loader.URLClassPath$Loader by calling getLoader(). If the corresponding URL represents a file, it will create a jdk.internal.loader.URLClassPath$FileLoader (which derives from URLClassPath$Loader). The FileLoader class uses the file component of the URL to construct a File object for accessing the underlying ressources. But unlike the jdk8 case, the file part of the URL now only contains the path component WITHOUT the authority (i.e. 'host') part. This will be interpreted as a relative path realtively to the current drive and obviously fail.
I have a quick fix for this issue which uses java.nio.files.Paths to get a file for a corresponding Path objected constructed from a URL:
if (!"file".equals(url.getProtocol())) {
throw new IllegalArgumentException("url");
}
- String path = url.getFile().replace('/', File.separatorChar);
- path = ParseUtil.decode(path);
- dir = (new File(path)).getCanonicalFile();
+ try {
+ dir = Paths.get(url.toURI()).toFile();
+ } catch (URISyntaxException urise) {
+ throw new IllegalArgumentException("uri", urise);
+ }
}
This seems to work fine and I could easily run JVM98 fromn a network share with this patch, but of course more testing is required.
This error is clearly a regression and should be fixed before Java 9 GA.
The problem is caused by the refactoring of the application class loader:
Before Java 9 the application class loader was a URLClassLoader (actually sun.misc.Launcher$AppClassLoader which extends URLClassLoader). It took the classpath from the "java.class.path" property and transfomred it into a File[] by calling Launcher.getClassPath(). This File[] was then converted into a URL[] by calling Launcher.pathToURLs() which in turn called Launcher.getFileURL() which finally called sun.net.www.ParseUtil.fileToEncodedURL() to convert a File into an URL. This last function explicitely creates URLs with protocol 'file' and empty authority (i.e. if the File contained an authority part like "\\foo" in "\\foo\bar\classs"), this was saved in the path part of the URL (i.e. 'return new URL("file", "", path)');
Later, these URLs were used to access classes on the classpath. This was done by creating new File objects from the path components of the corresponding URLs and because these path components already contained the full UNC path, everything worked fine.
In JDK 9 now, the legacy class path is constructed in jdk.internal.loader.ClassLoaders. It also takes the "java.class.path" property as a starting point but calls addClassPathToUCP() which in turn calls toFileURL() which finally uses 'Paths.get(s).toRealPath().toUri().toURL()' to transform a string 's' into an URL. This conversion creates URLs where the autority part contains the hostname and the path component contains the remaining path from the UNC paths in the classpath.
The problem is now that during class loading, the new application class loader jdk.internal.loader.ClassLoaders$AppClassLoader calls jdk.internal.loader.BuiltinClassLoader.loadClass() which calls loadClassOrNull() which calls findClassOnClassPathOrNull() which calls jdk.internal.loader.URLClassPath.getResource(). The class URLClassPath has a list of all the previously created URLs for each entry of the classpath. For each of these URLs it creates a jdk.internal.loader.URLClassPath$Loader by calling getLoader(). If the corresponding URL represents a file, it will create a jdk.internal.loader.URLClassPath$FileLoader (which derives from URLClassPath$Loader). The FileLoader class uses the file component of the URL to construct a File object for accessing the underlying ressources. But unlike the jdk8 case, the file part of the URL now only contains the path component WITHOUT the authority (i.e. 'host') part. This will be interpreted as a relative path realtively to the current drive and obviously fail.
I have a quick fix for this issue which uses java.nio.files.Paths to get a file for a corresponding Path objected constructed from a URL:
if (!"file".equals(url.getProtocol())) {
throw new IllegalArgumentException("url");
}
- String path = url.getFile().replace('/', File.separatorChar);
- path = ParseUtil.decode(path);
- dir = (new File(path)).getCanonicalFile();
+ try {
+ dir = Paths.get(url.toURI()).toFile();
+ } catch (URISyntaxException urise) {
+ throw new IllegalArgumentException("uri", urise);
+ }
}
This seems to work fine and I could easily run JVM98 fromn a network share with this patch, but of course more testing is required.
- backported by
-
JDK-8179791 Can't load classes from classpath if it is a UNC share
-
- Resolved
-