From 4450885c204c47a04971125754b0b6fb7f2ea879 Mon Sep 17 00:00:00 2001 From: Maxim Kartashev Date: Thu, 26 Sep 2024 15:40:31 +0000 Subject: [PATCH 1/2] 8339460: CDS error when module is located in a directory with space in the name Reviewed-by: ccheung, iklam --- src/hotspot/share/cds/classListParser.cpp | 4 +- src/hotspot/share/cds/classListWriter.cpp | 2 + src/hotspot/share/cds/filemap.cpp | 2 +- src/hotspot/share/classfile/classLoader.cpp | 48 ++++- src/hotspot/share/classfile/classLoader.hpp | 2 +- .../share/classfile/classLoaderExt.cpp | 10 +- test/hotspot/jtreg/TEST.groups | 1 + .../cds/appcds/complexURI/ComplexURITest.java | 167 ++++++++++++++++++ .../appcds/complexURI/mypackage/Another.java | 27 +++ .../cds/appcds/complexURI/mypackage/Main.java | 37 ++++ 10 files changed, 289 insertions(+), 11 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/complexURI/ComplexURITest.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/complexURI/mypackage/Another.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/complexURI/mypackage/Main.java diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index a1e1a413106..b3bfe79bb42 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -472,7 +472,9 @@ InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS THROW_NULL(vmSymbols::java_lang_ClassNotFoundException()); } - InstanceKlass* k = UnregisteredClasses::load_class(class_name, _source, CHECK_NULL); + ResourceMark rm; + char * source_path = os::strdup_check_oom(ClassLoader::uri_to_path(_source)); + InstanceKlass* k = UnregisteredClasses::load_class(class_name, source_path, CHECK_NULL); if (k->local_interfaces()->length() != _interfaces->length()) { print_specified_interfaces(); print_actual_interfaces(k); diff --git a/src/hotspot/share/cds/classListWriter.cpp b/src/hotspot/share/cds/classListWriter.cpp index 2a65ee51d6e..96cd6102c80 100644 --- a/src/hotspot/share/cds/classListWriter.cpp +++ b/src/hotspot/share/cds/classListWriter.cpp @@ -171,6 +171,8 @@ void ClassListWriter::write_to_stream(const InstanceKlass* k, outputStream* stre } } + // NB: the string following "source: " is not really a proper file name, but rather + // a truncated URI referring to a file. It must be decoded after reading. #ifdef _WINDOWS // "file:/C:/dir/foo.jar" -> "C:/dir/foo.jar" stream->print(" source: %s", cfs->source() + 6); diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 8efcfc37532..1c1147f031f 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -586,7 +586,7 @@ int FileMapInfo::get_module_shared_path_index(Symbol* location) { // skip_uri_protocol was also called during dump time -- see ClassLoaderExt::process_module_table() ResourceMark rm; - const char* file = ClassLoader::skip_uri_protocol(location->as_C_string()); + const char* file = ClassLoader::uri_to_path(location->as_C_string()); for (int i = ClassLoaderExt::app_module_paths_start_index(); i < get_number_of_shared_paths(); i++) { SharedClassPathEntry* ent = shared_path(i); assert(ent->in_named_module(), "must be"); diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index dde91c9358b..1d21920574e 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -78,6 +78,9 @@ #include "utilities/macros.hpp" #include "utilities/utf8.hpp" +#include +#include + // Entry point in java.dll for path canonicalization typedef int (*canonicalize_fn_t)(const char *orig, char *out, int len); @@ -1224,7 +1227,7 @@ InstanceKlass* ClassLoader::load_class(Symbol* name, bool search_append_only, TR } #if INCLUDE_CDS -char* ClassLoader::skip_uri_protocol(char* source) { +static const char* skip_uri_protocol(const char* source) { if (strncmp(source, "file:", 5) == 0) { // file: protocol path could start with file:/ or file:/// // locate the char after all the forward slashes @@ -1243,6 +1246,47 @@ char* ClassLoader::skip_uri_protocol(char* source) { return source; } +static char decode_percent_encoded(const char *str, size_t& index) { + if (str[index] == '%' + && isxdigit(str[index + 1]) + && isxdigit(str[index + 2])) { + char hex[3]; + hex[0] = str[index + 1]; + hex[1] = str[index + 2]; + hex[2] = '\0'; + index += 2; + return (char) strtol(hex, NULL, 16); + } + return str[index]; +} + +char* ClassLoader::uri_to_path(const char* uri) { + const size_t len = strlen(uri) + 1; + char* path = NEW_RESOURCE_ARRAY(char, len); + + uri = skip_uri_protocol(uri); + + if (strncmp(uri, "//", 2) == 0) { + // Skip the empty "authority" part + uri += 2; + } + +#ifdef _WINDOWS + if (uri[0] == '/') { + // Absolute path name on Windows does not begin with a slash + uri += 1; + } +#endif + + size_t path_index = 0; + for (size_t i = 0; i < strlen(uri); ++i) { + char decoded = decode_percent_encoded(uri, i); + path[path_index++] = decoded; + } + path[path_index] = '\0'; + return path; +} + // Record the shared classpath index and loader type for classes loaded // by the builtin loaders at dump time. void ClassLoader::record_result(JavaThread* current, InstanceKlass* ik, @@ -1276,7 +1320,7 @@ void ClassLoader::record_result(JavaThread* current, InstanceKlass* ik, // Save the path from the file: protocol or the module name from the jrt: protocol // if no protocol prefix is found, path is the same as stream->source(). This path // must be valid since the class has been successfully parsed. - char* path = skip_uri_protocol(src); + const char* path = ClassLoader::uri_to_path(src); assert(path != nullptr, "sanity"); for (int i = 0; i < FileMapInfo::get_number_of_shared_paths(); i++) { SharedClassPathEntry* ent = FileMapInfo::shared_path(i); diff --git a/src/hotspot/share/classfile/classLoader.hpp b/src/hotspot/share/classfile/classLoader.hpp index e03f5d76fc6..fc7da9e4034 100644 --- a/src/hotspot/share/classfile/classLoader.hpp +++ b/src/hotspot/share/classfile/classLoader.hpp @@ -370,7 +370,7 @@ class ClassLoader: AllStatic { // entries during shared classpath setup time. static int num_module_path_entries(); static void exit_with_path_failure(const char* error, const char* message); - static char* skip_uri_protocol(char* source); + static char* uri_to_path(const char* uri); static void record_result(JavaThread* current, InstanceKlass* ik, const ClassFileStream* stream, bool redefined); #endif diff --git a/src/hotspot/share/classfile/classLoaderExt.cpp b/src/hotspot/share/classfile/classLoaderExt.cpp index c9fd8173bca..2aa3ae0cd62 100644 --- a/src/hotspot/share/classfile/classLoaderExt.cpp +++ b/src/hotspot/share/classfile/classLoaderExt.cpp @@ -98,12 +98,10 @@ void ClassLoaderExt::process_module_table(JavaThread* current, ModuleEntryTable* ModulePathsGatherer(JavaThread* current, GrowableArray* module_paths) : _current(current), _module_paths(module_paths) {} void do_module(ModuleEntry* m) { - char* path = m->location()->as_C_string(); - if (strncmp(path, "file:", 5) == 0) { - path = ClassLoader::skip_uri_protocol(path); - char* path_copy = NEW_RESOURCE_ARRAY(char, strlen(path) + 1); - strcpy(path_copy, path); - _module_paths->append(path_copy); + char* uri = m->location()->as_C_string(); + if (strncmp(uri, "file:", 5) == 0) { + char* path = ClassLoader::uri_to_path(uri); + _module_paths->append(path); } } }; diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 9d4edd6f414..6fb2e2b0b35 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -439,6 +439,7 @@ hotspot_cds_only = \ hotspot_appcds_dynamic = \ runtime/cds/appcds/ \ -runtime/cds/appcds/cacheObject \ + -runtime/cds/appcds/complexURI \ -runtime/cds/appcds/customLoader \ -runtime/cds/appcds/dynamicArchive \ -runtime/cds/appcds/loaderConstraints/DynamicLoaderConstraintsTest.java \ diff --git a/test/hotspot/jtreg/runtime/cds/appcds/complexURI/ComplexURITest.java b/test/hotspot/jtreg/runtime/cds/appcds/complexURI/ComplexURITest.java new file mode 100644 index 00000000000..409e37e10a1 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/complexURI/ComplexURITest.java @@ -0,0 +1,167 @@ +/* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Verifies that CDS works with jar located in directories + * with names that need escaping + * @bug 8339460 + * @requires vm.cds + * @requires vm.cds.custom.loaders + * @requires vm.flagless + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds + * @compile mypackage/Main.java mypackage/Another.java + * @run main/othervm ComplexURITest + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.Platform; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ComplexURITest { + final static String moduleName = "mymodule"; + + public static void main(String[] args) throws Exception { + System.setProperty("test.noclasspath", "true"); + String jarFile = JarBuilder.build(moduleName, "mypackage/Main", "mypackage/Another"); + + Path subDir = Path.of(".", "dir with space"); + Files.createDirectory(subDir); + Path newJarFilePath = subDir.resolve(moduleName + ".jar"); + Files.move(Path.of(jarFile), newJarFilePath); + jarFile = newJarFilePath.toString(); + + final String listFileName = "test-classlist.txt"; + final String staticArchiveName = "test-static.jsa"; + final String dynamicArchiveName = "test-dynamic.jsa"; + + // Verify static archive creation and use + File fileList = new File(listFileName); + delete(fileList.toPath()); + File staticArchive = new File(staticArchiveName); + delete(staticArchive.toPath()); + + createClassList(jarFile, listFileName); + if (!fileList.exists()) { + throw new RuntimeException("No class list created at " + fileList); + } + + createArchive(jarFile, listFileName, staticArchiveName); + if (!staticArchive.exists()) { + throw new RuntimeException("No shared classes archive created at " + staticArchive); + } + + useArchive(jarFile, staticArchiveName); + + // Verify dynamic archive creation and use + File dynamicArchive = new File(dynamicArchiveName); + delete(dynamicArchive.toPath()); + + createDynamicArchive(jarFile, dynamicArchiveName); + if (!dynamicArchive.exists()) { + throw new RuntimeException("No dynamic archive created at " + dynamicArchive); + } + + testDynamicArchive(jarFile, dynamicArchiveName); + } + + private static void delete(Path path) throws Exception { + if (Files.exists(path)) { + if (Platform.isWindows()) { + Files.setAttribute(path, "dos:readonly", false); + } + Files.delete(path); + } + } + + private static void createClassList(String jarFile, String list) throws Exception { + String[] launchArgs = { + "-XX:DumpLoadedClassList=" + list, + "--module-path", + jarFile, + "--module", + moduleName + "/mypackage.Main"}; + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs); + OutputAnalyzer output = TestCommon.executeAndLog(pb, "create-list"); + output.shouldHaveExitValue(0); + } + + private static void createArchive(String jarFile, String list, String archive) throws Exception { + String[] launchArgs = { + "-Xshare:dump", + "-XX:SharedClassListFile=" + list, + "-XX:SharedArchiveFile=" + archive, + "--module-path", + jarFile, + "--module", + moduleName + "/mypackage.Main"}; + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs); + OutputAnalyzer output = TestCommon.executeAndLog(pb, "dump-archive"); + output.shouldHaveExitValue(0); + } + + private static void useArchive(String jarFile, String archive) throws Exception { + String[] launchArgs = { + "-Xshare:on", + "-XX:SharedArchiveFile=" + archive, + "--module-path", + jarFile, + "--module", + moduleName + "/mypackage.Main"}; + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs); + OutputAnalyzer output = TestCommon.executeAndLog(pb, "use-archive"); + output.shouldHaveExitValue(0); + } + + private static void createDynamicArchive(String jarFile, String archive) throws Exception { + String[] launchArgs = { + "-XX:ArchiveClassesAtExit=" + archive, + "--module-path", + jarFile, + "--module", + moduleName + "/mypackage.Main"}; + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs); + OutputAnalyzer output = TestCommon.executeAndLog(pb, "dynamic-archive"); + output.shouldHaveExitValue(0); + } + + private static void testDynamicArchive(String jarFile, String archive) throws Exception { + String[] launchArgs = { + "-XX:SharedArchiveFile=" + archive, + "-XX:+PrintSharedArchiveAndExit", + "--module-path", + jarFile, + "--module", + moduleName + "/mypackage.Main"}; + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(launchArgs); + OutputAnalyzer output = TestCommon.executeAndLog(pb, "dynamic-archive"); + output.shouldHaveExitValue(0); + output.shouldContain("archive is valid"); + output.shouldContain(": mypackage.Main app_loader"); + output.shouldContain(": mypackage.Another unregistered_loader"); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/complexURI/mypackage/Another.java b/test/hotspot/jtreg/runtime/cds/appcds/complexURI/mypackage/Another.java new file mode 100644 index 00000000000..106dfd4904c --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/complexURI/mypackage/Another.java @@ -0,0 +1,27 @@ +/* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package mypackage; + +public class Another { +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/complexURI/mypackage/Main.java b/test/hotspot/jtreg/runtime/cds/appcds/complexURI/mypackage/Main.java new file mode 100644 index 00000000000..fdb79e895d2 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/complexURI/mypackage/Main.java @@ -0,0 +1,37 @@ +/* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package mypackage; + +import java.net.URL; +import java.net.URLClassLoader; + +public class Main { + public static void main(String[] args) throws Exception { + URL url1 = Main.class.getProtectionDomain().getCodeSource().getLocation(); + System.out.println("Will load Another from " + url1); + ClassLoader cl = URLClassLoader.newInstance(new URL[] { url1 }, null); + var anotherClass = cl.loadClass("mypackage.Another"); + System.out.println("Class " + anotherClass + " loaded successfully"); + } +} From ec2af255360b4b931ec319a4b874364c9d5249bd Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Thu, 23 Jan 2025 22:02:48 +0000 Subject: [PATCH 2/2] 8348240: Remove SystemDictionaryShared::lookup_super_for_unregistered_class() Reviewed-by: ccheung, coleenp --- src/hotspot/share/cds/classListParser.cpp | 75 +++++------- src/hotspot/share/cds/classListParser.hpp | 13 +- src/hotspot/share/cds/unregisteredClasses.cpp | 78 ++++++------ src/hotspot/share/cds/unregisteredClasses.hpp | 23 +++- .../share/classfile/systemDictionary.cpp | 10 -- .../classfile/systemDictionaryShared.cpp | 46 ++----- src/hotspot/share/classfile/vmClassMacros.hpp | 3 +- src/hotspot/share/classfile/vmSymbols.hpp | 2 - .../share/classes/jdk/internal/misc/CDS.java | 114 +++++++++++++++++- .../appcds/customLoader/ClassListFormatE.java | 19 ++- 10 files changed, 231 insertions(+), 152 deletions(-) diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index b3bfe79bb42..f8ba385bd62 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -42,6 +42,7 @@ #include "jvm.h" #include "logging/log.hpp" #include "logging/logTag.hpp" +#include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "oops/constantPool.hpp" #include "runtime/atomic.hpp" @@ -97,6 +98,12 @@ ClassListParser::~ClassListParser() { _instance = nullptr; } +int ClassListParser::parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) { + UnregisteredClasses::initialize(CHECK_0); + ClassListParser parser(classlist_path, parse_mode); + return parser.parse(THREAD); // returns the number of classes loaded. +} + int ClassListParser::parse(TRAPS) { int class_count = 0; @@ -390,6 +397,19 @@ bool ClassListParser::parse_uint_option(const char* option_name, int* value) { return false; } +objArrayOop ClassListParser::get_specified_interfaces(TRAPS) { + const int n = _interfaces->length(); + if (n == 0) { + return nullptr; + } else { + objArrayOop array = oopFactory::new_objArray(vmClasses::Class_klass(), n, CHECK_NULL); + for (int i = 0; i < n; i++) { + array->obj_at_put(i, lookup_class_by_id(_interfaces->at(i))->java_mirror()); + } + return array; + } +} + void ClassListParser::print_specified_interfaces() { const int n = _interfaces->length(); jio_fprintf(defaultStream::error_stream(), "Currently specified interfaces[%d] = {\n", n); @@ -474,7 +494,17 @@ InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS ResourceMark rm; char * source_path = os::strdup_check_oom(ClassLoader::uri_to_path(_source)); - InstanceKlass* k = UnregisteredClasses::load_class(class_name, source_path, CHECK_NULL); + InstanceKlass* specified_super = lookup_class_by_id(_super); + Handle super_class(THREAD, specified_super->java_mirror()); + objArrayOop r = get_specified_interfaces(CHECK_NULL); + objArrayHandle interfaces(THREAD, r); + InstanceKlass* k = UnregisteredClasses::load_class(class_name, source_path, + super_class, interfaces, CHECK_NULL); + if (k->java_super() != specified_super) { + error("The specified super class %s (id %d) does not match actual super class %s", + specified_super->external_name(), _super, + k->java_super()->external_name()); + } if (k->local_interfaces()->length() != _interfaces->length()) { print_specified_interfaces(); print_actual_interfaces(k); @@ -682,46 +712,3 @@ InstanceKlass* ClassListParser::lookup_class_by_id(int id) { assert(*klass_ptr != nullptr, "must be"); return *klass_ptr; } - - -InstanceKlass* ClassListParser::lookup_super_for_current_class(Symbol* super_name) { - if (!is_loading_from_source()) { - return nullptr; - } - - InstanceKlass* k = lookup_class_by_id(super()); - if (super_name != k->name()) { - error("The specified super class %s (id %d) does not match actual super class %s", - k->name()->as_klass_external_name(), super(), - super_name->as_klass_external_name()); - } - return k; -} - -InstanceKlass* ClassListParser::lookup_interface_for_current_class(Symbol* interface_name) { - if (!is_loading_from_source()) { - return nullptr; - } - - const int n = _interfaces->length(); - if (n == 0) { - error("Class %s implements the interface %s, but no interface has been specified in the input line", - _class_name, interface_name->as_klass_external_name()); - ShouldNotReachHere(); - } - - int i; - for (i=0; iat(i)); - if (interface_name == k->name()) { - return k; - } - } - - // interface_name is not specified by the "interfaces:" keyword. - print_specified_interfaces(); - error("The interface %s implemented by class %s does not match any of the specified interface IDs", - interface_name->as_klass_external_name(), _class_name); - ShouldNotReachHere(); - return nullptr; -} diff --git a/src/hotspot/share/cds/classListParser.hpp b/src/hotspot/share/cds/classListParser.hpp index 74a2ff10515..4234cd436ee 100644 --- a/src/hotspot/share/cds/classListParser.hpp +++ b/src/hotspot/share/cds/classListParser.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -134,12 +134,10 @@ class ClassListParser : public StackObj { ClassListParser(const char* file, ParseMode _parse_mode); ~ClassListParser(); + objArrayOop get_specified_interfaces(TRAPS); public: - static int parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) { - ClassListParser parser(classlist_path, parse_mode); - return parser.parse(THREAD); // returns the number of classes loaded. - } + static int parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS); static bool is_parsing_thread(); static ClassListParser* instance() { @@ -192,11 +190,6 @@ class ClassListParser : public StackObj { bool lambda_form_line() { return _lambda_form_line; } - // Look up the super or interface of the current class being loaded - // (in this->load_current_class()). - InstanceKlass* lookup_super_for_current_class(Symbol* super_name); - InstanceKlass* lookup_interface_for_current_class(Symbol* interface_name); - static void populate_cds_indy_info(const constantPoolHandle &pool, int cp_index, CDSIndyInfo* cii, TRAPS); }; #endif // SHARE_CDS_CLASSLISTPARSER_HPP diff --git a/src/hotspot/share/cds/unregisteredClasses.cpp b/src/hotspot/share/cds/unregisteredClasses.cpp index 06d006ea1bb..641f84c3dcd 100644 --- a/src/hotspot/share/cds/unregisteredClasses.cpp +++ b/src/hotspot/share/cds/unregisteredClasses.cpp @@ -29,7 +29,7 @@ #include "classfile/classLoaderExt.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/symbolTable.hpp" -#include "classfile/systemDictionaryShared.hpp" +#include "classfile/systemDictionary.hpp" #include "classfile/vmSymbols.hpp" #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" @@ -39,10 +39,22 @@ #include "runtime/javaCalls.hpp" #include "services/threadService.hpp" +InstanceKlass* UnregisteredClasses::_UnregisteredClassLoader_klass = nullptr; + +void UnregisteredClasses::initialize(TRAPS) { + if (_UnregisteredClassLoader_klass == nullptr) { + // no need for synchronization as this function is called single-threaded. + Symbol* klass_name = SymbolTable::new_symbol("jdk/internal/misc/CDS$UnregisteredClassLoader"); + Klass* k = SystemDictionary::resolve_or_fail(klass_name, true, CHECK); + _UnregisteredClassLoader_klass = InstanceKlass::cast(k); + } +} + // Load the class of the given name from the location given by path. The path is specified by // the "source:" in the class list file (see classListParser.cpp), and can be a directory or // a JAR file. -InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path, TRAPS) { +InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path, + Handle super_class, objArrayHandle interfaces, TRAPS) { assert(name != nullptr, "invariant"); assert(DumpSharedSpaces, "this function is only used with -Xshare:dump"); @@ -50,19 +62,23 @@ InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path, T THREAD->get_thread_stat()->perf_timers_addr(), PerfClassTraceTime::CLASS_LOAD); + // Call CDS$UnregisteredClassLoader::load(String name, Class superClass, Class[] interfaces) + Symbol* methodName = SymbolTable::new_symbol("load"); + Symbol* methodSignature = SymbolTable::new_symbol("(Ljava/lang/String;Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/Class;"); Symbol* path_symbol = SymbolTable::new_symbol(path); - Handle url_classloader = get_url_classloader(path_symbol, CHECK_NULL); + Handle classloader = get_classloader(path_symbol, CHECK_NULL); Handle ext_class_name = java_lang_String::externalize_classname(name, CHECK_NULL); JavaValue result(T_OBJECT); - JavaCallArguments args(2); - args.set_receiver(url_classloader); + JavaCallArguments args(3); + args.set_receiver(classloader); args.push_oop(ext_class_name); - args.push_int(JNI_FALSE); + args.push_oop(super_class); + args.push_oop(interfaces); JavaCalls::call_virtual(&result, - vmClasses::URLClassLoader_klass(), - vmSymbols::loadClass_name(), - vmSymbols::string_boolean_class_signature(), + UnregisteredClassLoader_klass(), + methodName, + methodSignature, &args, CHECK_NULL); assert(result.get_type() == T_OBJECT, "just checking"); @@ -70,45 +86,35 @@ InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path, T return InstanceKlass::cast(java_lang_Class::as_Klass(obj)); } -class URLClassLoaderTable : public ResourceHashtable< +class UnregisteredClasses::ClassLoaderTable : public ResourceHashtable< Symbol*, OopHandle, 137, // prime number AnyObj::C_HEAP> {}; -static URLClassLoaderTable* _url_classloader_table = nullptr; +static UnregisteredClasses::ClassLoaderTable* _classloader_table = nullptr; -Handle UnregisteredClasses::create_url_classloader(Symbol* path, TRAPS) { +Handle UnregisteredClasses::create_classloader(Symbol* path, TRAPS) { ResourceMark rm(THREAD); JavaValue result(T_OBJECT); Handle path_string = java_lang_String::create_from_str(path->as_C_string(), CHECK_NH); - JavaCalls::call_static(&result, - vmClasses::jdk_internal_loader_ClassLoaders_klass(), - vmSymbols::toFileURL_name(), - vmSymbols::toFileURL_signature(), - path_string, CHECK_NH); - assert(result.get_type() == T_OBJECT, "just checking"); - oop url_h = result.get_oop(); - objArrayHandle urls = oopFactory::new_objArray_handle(vmClasses::URL_klass(), 1, CHECK_NH); - urls->obj_at_put(0, url_h); - - Handle url_classloader = JavaCalls::construct_new_instance( - vmClasses::URLClassLoader_klass(), - vmSymbols::url_array_classloader_void_signature(), - urls, Handle(), CHECK_NH); - return url_classloader; + Handle classloader = JavaCalls::construct_new_instance( + UnregisteredClassLoader_klass(), + vmSymbols::string_void_signature(), + path_string, CHECK_NH); + return classloader; } -Handle UnregisteredClasses::get_url_classloader(Symbol* path, TRAPS) { - if (_url_classloader_table == nullptr) { - _url_classloader_table = new (mtClass)URLClassLoaderTable(); +Handle UnregisteredClasses::get_classloader(Symbol* path, TRAPS) { + if (_classloader_table == nullptr) { + _classloader_table = new (mtClass)ClassLoaderTable(); } - OopHandle* url_classloader_ptr = _url_classloader_table->get(path); - if (url_classloader_ptr != nullptr) { - return Handle(THREAD, (*url_classloader_ptr).resolve()); + OopHandle* classloader_ptr = _classloader_table->get(path); + if (classloader_ptr != nullptr) { + return Handle(THREAD, (*classloader_ptr).resolve()); } else { - Handle url_classloader = create_url_classloader(path, CHECK_NH); - _url_classloader_table->put(path, OopHandle(Universe::vm_global(), url_classloader())); + Handle classloader = create_classloader(path, CHECK_NH); + _classloader_table->put(path, OopHandle(Universe::vm_global(), classloader())); path->increment_refcount(); - return url_classloader; + return classloader; } } diff --git a/src/hotspot/share/cds/unregisteredClasses.hpp b/src/hotspot/share/cds/unregisteredClasses.hpp index a9f7dceaead..ea3a308c2de 100644 --- a/src/hotspot/share/cds/unregisteredClasses.hpp +++ b/src/hotspot/share/cds/unregisteredClasses.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,15 +25,30 @@ #ifndef SHARE_CDS_UNREGISTEREDCLASSES_HPP #define SHARE_CDS_UNREGISTEREDCLASSES_HPP +#include "memory/allStatic.hpp" #include "runtime/handles.hpp" +class InstanceKlass; +class Symbol; + class UnregisteredClasses: AllStatic { public: - static InstanceKlass* load_class(Symbol* h_name, const char* path, TRAPS); + static InstanceKlass* load_class(Symbol* h_name, const char* path, + Handle super_class, objArrayHandle interfaces, + TRAPS); + static void initialize(TRAPS); + static InstanceKlass* UnregisteredClassLoader_klass() { + return _UnregisteredClassLoader_klass; + } + + class ClassLoaderTable; private: - static Handle create_url_classloader(Symbol* path, TRAPS); - static Handle get_url_classloader(Symbol* path, TRAPS); + // Don't put this in vmClasses as it's used only with CDS dumping. + static InstanceKlass* _UnregisteredClassLoader_klass; + + static Handle create_classloader(Symbol* path, TRAPS); + static Handle get_classloader(Symbol* path, TRAPS); }; #endif // SHARE_CDS_UNREGISTEREDCLASSES_HPP diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 7022bfca7a5..de4efebb278 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -412,16 +412,6 @@ InstanceKlass* SystemDictionary::resolve_super_or_fail(Symbol* class_name, assert(super_name != nullptr, "null superclass for resolving"); assert(!Signature::is_array(super_name), "invalid superclass name"); -#if INCLUDE_CDS - if (DumpSharedSpaces) { - // Special processing for handling UNREGISTERED shared classes. - InstanceKlass* k = SystemDictionaryShared::lookup_super_for_unregistered_class(class_name, - super_name, is_superclass); - if (k) { - return k; - } - } -#endif // INCLUDE_CDS // If klass is already loaded, just return the superclass or superinterface. // Make sure there's a placeholder for the class_name before resolving. diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index 4744c38b87e..22a94133a9a 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -34,6 +34,7 @@ #include "cds/dumpTimeClassInfo.inline.hpp" #include "cds/metaspaceShared.hpp" #include "cds/runTimeClassInfo.hpp" +#include "cds/unregisteredClasses.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" @@ -331,6 +332,12 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { } } + if (k == UnregisteredClasses::UnregisteredClassLoader_klass()) { + ResourceMark rm; + log_info(cds)("Skipping %s: used only when dumping CDS archive", k->name()->as_C_string()); + return true; + } + return false; // false == k should NOT be excluded } @@ -449,45 +456,6 @@ bool SystemDictionaryShared::add_unregistered_class(Thread* current, InstanceKla return (klass == *v); } -// This function is called to lookup the super/interfaces of shared classes for -// unregistered loaders. E.g., SharedClass in the below example -// where "super:" (and optionally "interface:") have been specified. -// -// java/lang/Object id: 0 -// Interface id: 2 super: 0 source: cust.jar -// SharedClass id: 4 super: 0 interfaces: 2 source: cust.jar -InstanceKlass* SystemDictionaryShared::lookup_super_for_unregistered_class( - Symbol* class_name, Symbol* super_name, bool is_superclass) { - - assert(DumpSharedSpaces, "only when static dumping"); - - if (!ClassListParser::is_parsing_thread()) { - // Unregistered classes can be created only by ClassListParser::_parsing_thread. - - return nullptr; - } - - ClassListParser* parser = ClassListParser::instance(); - if (parser == nullptr) { - // We're still loading the well-known classes, before the ClassListParser is created. - return nullptr; - } - if (class_name->equals(parser->current_class_name())) { - // When this function is called, all the numbered super and interface types - // must have already been loaded. Hence this function is never recursively called. - if (is_superclass) { - return parser->lookup_super_for_current_class(super_name); - } else { - return parser->lookup_interface_for_current_class(super_name); - } - } else { - // The VM is not trying to resolve a super type of parser->current_class_name(). - // Instead, it's resolving an error class (because parser->current_class_name() has - // failed parsing or verification). Don't do anything here. - return nullptr; - } -} - void SystemDictionaryShared::set_shared_class_misc_info(InstanceKlass* k, ClassFileStream* cfs) { Arguments::assert_is_dumping_archive(); assert(!is_builtin(k), "must be unregistered class"); diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index bb2babff25a..9ecbec41d6a 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -144,7 +144,6 @@ /* support for CDS */ \ do_klass(ByteArrayInputStream_klass, java_io_ByteArrayInputStream ) \ do_klass(URL_klass, java_net_URL ) \ - do_klass(URLClassLoader_klass, java_net_URLClassLoader ) \ do_klass(Enum_klass, java_lang_Enum ) \ do_klass(Jar_Manifest_klass, java_util_jar_Manifest ) \ do_klass(jdk_internal_loader_BuiltinClassLoader_klass,jdk_internal_loader_BuiltinClassLoader ) \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index d4cc1d9c570..4cd9a1a6d0c 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -127,7 +127,6 @@ template(java_security_ProtectionDomain, "java/security/ProtectionDomain") \ template(java_security_SecureClassLoader, "java/security/SecureClassLoader") \ template(java_net_URL, "java/net/URL") \ - template(java_net_URLClassLoader, "java/net/URLClassLoader") \ template(java_util_jar_Manifest, "java/util/jar/Manifest") \ template(java_io_OutputStream, "java/io/OutputStream") \ template(java_io_Reader, "java/io/Reader") \ @@ -788,7 +787,6 @@ template(toFileURL_name, "toFileURL") \ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ template(url_void_signature, "(Ljava/net/URL;)V") \ - template(url_array_classloader_void_signature, "([Ljava/net/URL;Ljava/lang/ClassLoader;)V") \ \ /* Thread.dump_to_file jcmd */ \ template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ diff --git a/src/java.base/share/classes/jdk/internal/misc/CDS.java b/src/java.base/share/classes/jdk/internal/misc/CDS.java index c41adcf9c4c..a963a45a813 100644 --- a/src/java.base/share/classes/jdk/internal/misc/CDS.java +++ b/src/java.base/share/classes/jdk/internal/misc/CDS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,10 @@ import java.io.InputStream; import java.io.IOException; import java.io.PrintStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.util.Arrays; import java.util.ArrayList; import java.util.List; @@ -333,4 +337,112 @@ private static String dumpSharedArchive(boolean isStatic, String fileName) throw System.out.println("The process was attached by jcmd and dumped a " + (isStatic ? "static" : "dynamic") + " archive " + archiveFilePath); return archiveFilePath; } + + /** + * This class is used only by native JVM code at CDS dump time for loading + * "unregistered classes", which are archived classes that are intended to + * be loaded by custom class loaders during runtime. + * See src/hotspot/share/cds/unregisteredClasses.cpp. + */ + private static class UnregisteredClassLoader extends URLClassLoader { + private String currentClassName; + private Class currentSuperClass; + private Class[] currentInterfaces; + + /** + * Used only by native code. Construct an UnregisteredClassLoader for loading + * unregistered classes from the specified file. If the file doesn't exist, + * the exception will be caughted by native code which will print a warning message and continue. + * + * @param fileName path of the the JAR file to load unregistered classes from. + */ + private UnregisteredClassLoader(String fileName) throws InvalidPathException, IOException { + super(toURLArray(fileName), /*parent*/null); + currentClassName = null; + currentSuperClass = null; + currentInterfaces = null; + } + + private static URL[] toURLArray(String fileName) throws InvalidPathException, IOException { + if (!((new File(fileName)).exists())) { + throw new IOException("No such file: " + fileName); + } + return new URL[] { + // Use an intermediate File object to construct a URI/URL without + // authority component as URLClassPath can't handle URLs with a UNC + // server name in the authority component. + Path.of(fileName).toRealPath().toFile().toURI().toURL() + }; + } + + + /** + * Load the class of the given /name from the JAR file that was given to + * the constructor of the current UnregisteredClassLoader instance. This class must be + * a direct subclass of superClass. This class must be declared to implement + * the specified interfaces. + *

+ * This method must be called in a single threaded context. It will never be recursed (thus + * the asserts) + * + * @param name the name of the class to be loaded. + * @param superClass must not be null. The named class must have a super class. + * @param interfaces could be null if the named class does not implement any interfaces. + */ + private Class load(String name, Class superClass, Class[] interfaces) + throws ClassNotFoundException + { + assert currentClassName == null; + assert currentSuperClass == null; + assert currentInterfaces == null; + + try { + currentClassName = name; + currentSuperClass = superClass; + currentInterfaces = interfaces; + + return findClass(name); + } finally { + currentClassName = null; + currentSuperClass = null; + currentInterfaces = null; + } + } + + /** + * This method must be called from inside the load() method. The /name + * can be only: + *

    + *
  • the name parameter for load() + *
  • the name of the superClass parameter for load() + *
  • the name of one of the interfaces in interfaces parameter for load() + *
      + * + * For all other cases, a ClassNotFoundException will be thrown. + */ + protected Class findClass(final String name) + throws ClassNotFoundException + { + Objects.requireNonNull(currentClassName); + Objects.requireNonNull(currentSuperClass); + + if (name.equals(currentClassName)) { + // Note: the following call will call back to this.findClass(name) to + // resolve the super types of the named class. + return super.findClass(name); + } + if (name.equals(currentSuperClass.getName())) { + return currentSuperClass; + } + if (currentInterfaces != null) { + for (Class c : currentInterfaces) { + if (name.equals(c.getName())) { + return c; + } + } + } + + throw new ClassNotFoundException(name); + } + } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java index b5386c54afb..2beb39d0b39 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,14 +49,15 @@ public static void main(String[] args) throws Throwable { //---------------------------------------------------------------------- // TESTGROUP E: super class and interfaces //---------------------------------------------------------------------- - dumpShouldFail( + dumpShouldPass( "TESTCASE E1: missing interfaces: keyword", appJar, classlist( "Hello", "java/lang/Object id: 1", "CustomLoadee2 id: 1 super: 1 source: " + customJarPath ), - "Class CustomLoadee2 implements the interface CustomInterface2_ia, but no interface has been specified in the input line"); + "java.lang.NoClassDefFoundError: CustomInterface2_ia", + "Cannot find CustomLoadee2"); dumpShouldFail( "TESTCASE E2: missing one interface", @@ -67,7 +68,7 @@ appJar, classlist( "CustomInterface2_ib id: 3 super: 1 source: " + customJarPath, "CustomLoadee2 id: 4 super: 1 interfaces: 2 source: " + customJarPath ), - "The interface CustomInterface2_ib implemented by class CustomLoadee2 does not match any of the specified interface IDs"); + "The number of interfaces (1) specified in class list does not match the class file (2)"); dumpShouldFail( "TESTCASE E3: specifying an interface that's not implemented by the class", @@ -101,5 +102,15 @@ appJar, classlist( "CustomLoadee2 id: 5 super: 4 interfaces: 2 3 source: " + customJarPath ), "The specified super class CustomLoadee (id 4) does not match actual super class java.lang.Object"); + + dumpShouldPass( + "TESTCASE E6: JAR file doesn't exist", + appJar, classlist( + "Hello", + "java/lang/Object id: 1", + "NoSuchClass id: 2 super: 1 source: no_such_file.jar" + ), + "Cannot find NoSuchClass", + "java.io.IOException: No such file: no_such_file.jar"); } }