Skip to content

Commit aceae76

Browse files
author
Maxim Kartashev
committed
8339460: CDS error when module is located in a directory with space in the name
Reviewed-by: ccheung, iklam
1 parent aeaa4f7 commit aceae76

File tree

10 files changed

+289
-11
lines changed

10 files changed

+289
-11
lines changed

src/hotspot/share/cds/classListParser.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,9 @@ InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS
508508
THROW_NULL(vmSymbols::java_lang_ClassNotFoundException());
509509
}
510510

511-
InstanceKlass* k = UnregisteredClasses::load_class(class_name, _source, CHECK_NULL);
511+
ResourceMark rm;
512+
char * source_path = os::strdup_check_oom(ClassLoader::uri_to_path(_source));
513+
InstanceKlass* k = UnregisteredClasses::load_class(class_name, source_path, CHECK_NULL);
512514
if (k->local_interfaces()->length() != _interfaces->length()) {
513515
print_specified_interfaces();
514516
print_actual_interfaces(k);

src/hotspot/share/cds/classListWriter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ void ClassListWriter::write_to_stream(const InstanceKlass* k, outputStream* stre
174174
}
175175
}
176176

177+
// NB: the string following "source: " is not really a proper file name, but rather
178+
// a truncated URI referring to a file. It must be decoded after reading.
177179
#ifdef _WINDOWS
178180
// "file:/C:/dir/foo.jar" -> "C:/dir/foo.jar"
179181
stream->print(" source: %s", cfs->source() + 6);

src/hotspot/share/cds/filemap.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ int FileMapInfo::get_module_shared_path_index(Symbol* location) {
581581

582582
// skip_uri_protocol was also called during dump time -- see ClassLoaderExt::process_module_table()
583583
ResourceMark rm;
584-
const char* file = ClassLoader::skip_uri_protocol(location->as_C_string());
584+
const char* file = ClassLoader::uri_to_path(location->as_C_string());
585585
for (int i = ClassLoaderExt::app_module_paths_start_index(); i < get_number_of_shared_paths(); i++) {
586586
SharedClassPathEntry* ent = shared_path(i);
587587
if (!ent->is_non_existent()) {

src/hotspot/share/classfile/classLoader.cpp

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@
8181
#include "utilities/ostream.hpp"
8282
#include "utilities/utf8.hpp"
8383

84+
#include <stdlib.h>
85+
#include <ctype.h>
86+
8487
// Entry point in java.dll for path canonicalization
8588

8689
typedef int (*canonicalize_fn_t)(const char *orig, char *out, int len);
@@ -1209,7 +1212,7 @@ InstanceKlass* ClassLoader::load_class(Symbol* name, PackageEntry* pkg_entry, bo
12091212
}
12101213

12111214
#if INCLUDE_CDS
1212-
char* ClassLoader::skip_uri_protocol(char* source) {
1215+
static const char* skip_uri_protocol(const char* source) {
12131216
if (strncmp(source, "file:", 5) == 0) {
12141217
// file: protocol path could start with file:/ or file:///
12151218
// locate the char after all the forward slashes
@@ -1228,6 +1231,47 @@ char* ClassLoader::skip_uri_protocol(char* source) {
12281231
return source;
12291232
}
12301233

1234+
static char decode_percent_encoded(const char *str, size_t& index) {
1235+
if (str[index] == '%'
1236+
&& isxdigit(str[index + 1])
1237+
&& isxdigit(str[index + 2])) {
1238+
char hex[3];
1239+
hex[0] = str[index + 1];
1240+
hex[1] = str[index + 2];
1241+
hex[2] = '\0';
1242+
index += 2;
1243+
return (char) strtol(hex, NULL, 16);
1244+
}
1245+
return str[index];
1246+
}
1247+
1248+
char* ClassLoader::uri_to_path(const char* uri) {
1249+
const size_t len = strlen(uri) + 1;
1250+
char* path = NEW_RESOURCE_ARRAY(char, len);
1251+
1252+
uri = skip_uri_protocol(uri);
1253+
1254+
if (strncmp(uri, "//", 2) == 0) {
1255+
// Skip the empty "authority" part
1256+
uri += 2;
1257+
}
1258+
1259+
#ifdef _WINDOWS
1260+
if (uri[0] == '/') {
1261+
// Absolute path name on Windows does not begin with a slash
1262+
uri += 1;
1263+
}
1264+
#endif
1265+
1266+
size_t path_index = 0;
1267+
for (size_t i = 0; i < strlen(uri); ++i) {
1268+
char decoded = decode_percent_encoded(uri, i);
1269+
path[path_index++] = decoded;
1270+
}
1271+
path[path_index] = '\0';
1272+
return path;
1273+
}
1274+
12311275
// Record the shared classpath index and loader type for classes loaded
12321276
// by the builtin loaders at dump time.
12331277
void ClassLoader::record_result(JavaThread* current, InstanceKlass* ik,
@@ -1261,7 +1305,7 @@ void ClassLoader::record_result(JavaThread* current, InstanceKlass* ik,
12611305
// Save the path from the file: protocol or the module name from the jrt: protocol
12621306
// if no protocol prefix is found, path is the same as stream->source(). This path
12631307
// must be valid since the class has been successfully parsed.
1264-
char* path = skip_uri_protocol(src);
1308+
const char* path = ClassLoader::uri_to_path(src);
12651309
assert(path != nullptr, "sanity");
12661310
for (int i = 0; i < FileMapInfo::get_number_of_shared_paths(); i++) {
12671311
SharedClassPathEntry* ent = FileMapInfo::shared_path(i);

src/hotspot/share/classfile/classLoader.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ class ClassLoader: AllStatic {
382382
// entries during shared classpath setup time.
383383
static int num_module_path_entries();
384384
static void exit_with_path_failure(const char* error, const char* message);
385-
static char* skip_uri_protocol(char* source);
385+
static char* uri_to_path(const char* uri);
386386
static void record_result(JavaThread* current, InstanceKlass* ik,
387387
const ClassFileStream* stream, bool redefined);
388388
#endif

src/hotspot/share/classfile/classLoaderExt.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,10 @@ void ClassLoaderExt::process_module_table(JavaThread* current, ModuleEntryTable*
100100
ModulePathsGatherer(JavaThread* current, GrowableArray<char*>* module_paths) :
101101
_current(current), _module_paths(module_paths) {}
102102
void do_module(ModuleEntry* m) {
103-
char* path = m->location()->as_C_string();
104-
if (strncmp(path, "file:", 5) == 0) {
105-
path = ClassLoader::skip_uri_protocol(path);
106-
char* path_copy = NEW_RESOURCE_ARRAY(char, strlen(path) + 1);
107-
strcpy(path_copy, path);
108-
_module_paths->append(path_copy);
103+
char* uri = m->location()->as_C_string();
104+
if (strncmp(uri, "file:", 5) == 0) {
105+
char* path = ClassLoader::uri_to_path(uri);
106+
_module_paths->append(path);
109107
}
110108
}
111109
};

test/hotspot/jtreg/TEST.groups

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ hotspot_cds_only = \
435435
hotspot_appcds_dynamic = \
436436
runtime/cds/appcds/ \
437437
-runtime/cds/appcds/cacheObject \
438+
-runtime/cds/appcds/complexURI \
438439
-runtime/cds/appcds/customLoader \
439440
-runtime/cds/appcds/dynamicArchive \
440441
-runtime/cds/appcds/loaderConstraints/DynamicLoaderConstraintsTest.java \
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024 JetBrains s.r.o.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @summary Verifies that CDS works with jar located in directories
27+
* with names that need escaping
28+
* @bug 8339460
29+
* @requires vm.cds
30+
* @requires vm.cds.custom.loaders
31+
* @requires vm.flagless
32+
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
33+
* @compile mypackage/Main.java mypackage/Another.java
34+
* @run main/othervm ComplexURITest
35+
*/
36+
37+
import jdk.test.lib.process.OutputAnalyzer;
38+
import jdk.test.lib.process.ProcessTools;
39+
import jdk.test.lib.Platform;
40+
41+
import java.io.File;
42+
import java.nio.file.Files;
43+
import java.nio.file.Path;
44+
45+
public class ComplexURITest {
46+
final static String moduleName = "mymodule";
47+
48+
public static void main(String[] args) throws Exception {
49+
System.setProperty("test.noclasspath", "true");
50+
String jarFile = JarBuilder.build(moduleName, "mypackage/Main", "mypackage/Another");
51+
52+
Path subDir = Path.of(".", "dir with space");
53+
Files.createDirectory(subDir);
54+
Path newJarFilePath = subDir.resolve(moduleName + ".jar");
55+
Files.move(Path.of(jarFile), newJarFilePath);
56+
jarFile = newJarFilePath.toString();
57+
58+
final String listFileName = "test-classlist.txt";
59+
final String staticArchiveName = "test-static.jsa";
60+
final String dynamicArchiveName = "test-dynamic.jsa";
61+
62+
// Verify static archive creation and use
63+
File fileList = new File(listFileName);
64+
delete(fileList.toPath());
65+
File staticArchive = new File(staticArchiveName);
66+
delete(staticArchive.toPath());
67+
68+
createClassList(jarFile, listFileName);
69+
if (!fileList.exists()) {
70+
throw new RuntimeException("No class list created at " + fileList);
71+
}
72+
73+
createArchive(jarFile, listFileName, staticArchiveName);
74+
if (!staticArchive.exists()) {
75+
throw new RuntimeException("No shared classes archive created at " + staticArchive);
76+
}
77+
78+
useArchive(jarFile, staticArchiveName);
79+
80+
// Verify dynamic archive creation and use
81+
File dynamicArchive = new File(dynamicArchiveName);
82+
delete(dynamicArchive.toPath());
83+
84+
createDynamicArchive(jarFile, dynamicArchiveName);
85+
if (!dynamicArchive.exists()) {
86+
throw new RuntimeException("No dynamic archive created at " + dynamicArchive);
87+
}
88+
89+
testDynamicArchive(jarFile, dynamicArchiveName);
90+
}
91+
92+
private static void delete(Path path) throws Exception {
93+
if (Files.exists(path)) {
94+
if (Platform.isWindows()) {
95+
Files.setAttribute(path, "dos:readonly", false);
96+
}
97+
Files.delete(path);
98+
}
99+
}
100+
101+
private static void createClassList(String jarFile, String list) throws Exception {
102+
String[] launchArgs = {
103+
"-XX:DumpLoadedClassList=" + list,
104+
"--module-path",
105+
jarFile,
106+
"--module",
107+
moduleName + "/mypackage.Main"};
108+
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs);
109+
OutputAnalyzer output = TestCommon.executeAndLog(pb, "create-list");
110+
output.shouldHaveExitValue(0);
111+
}
112+
113+
private static void createArchive(String jarFile, String list, String archive) throws Exception {
114+
String[] launchArgs = {
115+
"-Xshare:dump",
116+
"-XX:SharedClassListFile=" + list,
117+
"-XX:SharedArchiveFile=" + archive,
118+
"--module-path",
119+
jarFile,
120+
"--module",
121+
moduleName + "/mypackage.Main"};
122+
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs);
123+
OutputAnalyzer output = TestCommon.executeAndLog(pb, "dump-archive");
124+
output.shouldHaveExitValue(0);
125+
}
126+
127+
private static void useArchive(String jarFile, String archive) throws Exception {
128+
String[] launchArgs = {
129+
"-Xshare:on",
130+
"-XX:SharedArchiveFile=" + archive,
131+
"--module-path",
132+
jarFile,
133+
"--module",
134+
moduleName + "/mypackage.Main"};
135+
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs);
136+
OutputAnalyzer output = TestCommon.executeAndLog(pb, "use-archive");
137+
output.shouldHaveExitValue(0);
138+
}
139+
140+
private static void createDynamicArchive(String jarFile, String archive) throws Exception {
141+
String[] launchArgs = {
142+
"-XX:ArchiveClassesAtExit=" + archive,
143+
"--module-path",
144+
jarFile,
145+
"--module",
146+
moduleName + "/mypackage.Main"};
147+
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs);
148+
OutputAnalyzer output = TestCommon.executeAndLog(pb, "dynamic-archive");
149+
output.shouldHaveExitValue(0);
150+
}
151+
152+
private static void testDynamicArchive(String jarFile, String archive) throws Exception {
153+
String[] launchArgs = {
154+
"-XX:SharedArchiveFile=" + archive,
155+
"-XX:+PrintSharedArchiveAndExit",
156+
"--module-path",
157+
jarFile,
158+
"--module",
159+
moduleName + "/mypackage.Main"};
160+
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs);
161+
OutputAnalyzer output = TestCommon.executeAndLog(pb, "dynamic-archive");
162+
output.shouldHaveExitValue(0);
163+
output.shouldContain("archive is valid");
164+
output.shouldContain(": mypackage.Main app_loader");
165+
output.shouldContain(": mypackage.Another unregistered_loader");
166+
}
167+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024 JetBrains s.r.o.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
package mypackage;
25+
26+
public class Another {
27+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024 JetBrains s.r.o.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
package mypackage;
25+
26+
import java.net.URL;
27+
import java.net.URLClassLoader;
28+
29+
public class Main {
30+
public static void main(String[] args) throws Exception {
31+
URL url1 = Main.class.getProtectionDomain().getCodeSource().getLocation();
32+
System.out.println("Will load Another from " + url1);
33+
ClassLoader cl = URLClassLoader.newInstance(new URL[] { url1 }, null);
34+
var anotherClass = cl.loadClass("mypackage.Another");
35+
System.out.println("Class " + anotherClass + " loaded successfully");
36+
}
37+
}

0 commit comments

Comments
 (0)