mirror of
https://github.com/openjdk/jdk.git
synced 2026-07-30 20:55:34 +00:00
1038 lines
38 KiB
C++
1038 lines
38 KiB
C++
/*
|
|
* Copyright (c) 2003, 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
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include "cds/aotClassLocation.hpp"
|
|
#include "cds/archiveBuilder.hpp"
|
|
#include "cds/cdsConfig.hpp"
|
|
#include "cds/dynamicArchive.hpp"
|
|
#include "cds/filemap.hpp"
|
|
#include "cds/metaspaceShared.hpp"
|
|
#include "cds/serializeClosure.hpp"
|
|
#include "classfile/classLoader.hpp"
|
|
#include "classfile/classLoaderData.hpp"
|
|
#include "classfile/javaClasses.hpp"
|
|
#include "logging/log.hpp"
|
|
#include "logging/logStream.hpp"
|
|
#include "memory/metadataFactory.hpp"
|
|
#include "memory/metaspaceClosure.hpp"
|
|
#include "memory/resourceArea.hpp"
|
|
#include "oops/array.hpp"
|
|
#include "oops/objArrayKlass.hpp"
|
|
#include "runtime/arguments.hpp"
|
|
#include "utilities/classpathStream.hpp"
|
|
#include "utilities/formatBuffer.hpp"
|
|
#include "utilities/stringUtils.hpp"
|
|
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
|
|
Array<ClassPathZipEntry*>* AOTClassLocationConfig::_dumptime_jar_files = nullptr;
|
|
AOTClassLocationConfig* AOTClassLocationConfig::_dumptime_instance = nullptr;
|
|
const AOTClassLocationConfig* AOTClassLocationConfig::_runtime_instance = nullptr;
|
|
|
|
// A ClassLocationStream represents a list of code locations, which can be iterated using
|
|
// start() and has_next().
|
|
class ClassLocationStream {
|
|
protected:
|
|
GrowableArray<const char*> _array;
|
|
int _current;
|
|
|
|
// Add one path to this stream.
|
|
void add_one_path(const char* path) {
|
|
_array.append(path);
|
|
}
|
|
|
|
// Add all paths specified in cp; cp must be from -classpath or -Xbootclasspath/a.
|
|
void add_paths_in_classpath(const char* cp) {
|
|
ClasspathStream cp_stream(cp);
|
|
while (cp_stream.has_next()) {
|
|
add_one_path(cp_stream.get_next());
|
|
}
|
|
}
|
|
|
|
public:
|
|
ClassLocationStream() : _array(), _current(0) {}
|
|
|
|
void print(outputStream* st) const {
|
|
const char* sep = "";
|
|
for (int i = 0; i < _array.length(); i++) {
|
|
st->print("%s%s", sep, _array.at(i));
|
|
sep = os::path_separator();
|
|
}
|
|
}
|
|
|
|
void add(ClassLocationStream& css) {
|
|
for (css.start(); css.has_next();) {
|
|
add_one_path(css.get_next());
|
|
}
|
|
}
|
|
|
|
// Iteration
|
|
void start() { _current = 0; }
|
|
bool has_next() const { return _current < _array.length(); }
|
|
const char* get_next() {
|
|
return _array.at(_current++);
|
|
}
|
|
|
|
int current() const { return _current; }
|
|
bool is_empty() const { return _array.length() == 0; }
|
|
};
|
|
|
|
class BootCpClassLocationStream : public ClassLocationStream {
|
|
public:
|
|
BootCpClassLocationStream() : ClassLocationStream() {
|
|
// Arguments::get_boot_class_path() contains $JAVA_HOME/lib/modules, but we treat that separately
|
|
for (const char* bootcp = Arguments::get_boot_class_path(); *bootcp != '\0'; ++bootcp) {
|
|
if (*bootcp == *os::path_separator()) {
|
|
++bootcp;
|
|
add_paths_in_classpath(bootcp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class AppCpClassLocationStream : public ClassLocationStream {
|
|
public:
|
|
AppCpClassLocationStream() : ClassLocationStream() {
|
|
const char* appcp = Arguments::get_appclasspath();
|
|
if (strcmp(appcp, ".") == 0) {
|
|
appcp = "";
|
|
}
|
|
add_paths_in_classpath(appcp);
|
|
}
|
|
};
|
|
|
|
class ModulePathClassLocationStream : public ClassLocationStream {
|
|
bool _has_non_jar_modules;
|
|
public:
|
|
ModulePathClassLocationStream();
|
|
bool has_non_jar_modules() { return _has_non_jar_modules; }
|
|
};
|
|
|
|
// AllClassLocationStreams is used to iterate over all the code locations that
|
|
// are available to the application from -Xbootclasspath, -classpath and --module-path.
|
|
// When creating an AOT cache, we store the contents from AllClassLocationStreams
|
|
// into an array of AOTClassLocations. See AOTClassLocationConfig::dumptime_init_helper().
|
|
// When loading the AOT cache in a production run, we compare the contents of the
|
|
// stored AOTClassLocations against the current AllClassLocationStreams to determine whether
|
|
// the AOT cache is compatible with the current JVM. See AOTClassLocationConfig::validate().
|
|
class AllClassLocationStreams {
|
|
BootCpClassLocationStream _boot_cp; // Specified by -Xbootclasspath/a
|
|
AppCpClassLocationStream _app_cp; // Specified by -classpath
|
|
ModulePathClassLocationStream _module_path; // Specified by --module-path
|
|
ClassLocationStream _boot_and_app_cp; // Convenience for iterating over both _boot and _app
|
|
public:
|
|
BootCpClassLocationStream& boot_cp() { return _boot_cp; }
|
|
AppCpClassLocationStream& app_cp() { return _app_cp; }
|
|
ModulePathClassLocationStream& module_path() { return _module_path; }
|
|
ClassLocationStream& boot_and_app_cp() { return _boot_and_app_cp; }
|
|
|
|
AllClassLocationStreams() : _boot_cp(), _app_cp(), _module_path(), _boot_and_app_cp() {
|
|
_boot_and_app_cp.add(_boot_cp);
|
|
_boot_and_app_cp.add(_app_cp);
|
|
}
|
|
};
|
|
|
|
static bool has_jar_suffix(const char* filename) {
|
|
// In jdk.internal.module.ModulePath.readModule(), it checks for the ".jar" suffix.
|
|
// Performing the same check here.
|
|
const char* dot = strrchr(filename, '.');
|
|
if (dot != nullptr && strcmp(dot + 1, "jar") == 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int compare_module_path_by_name(const char** p1, const char** p2) {
|
|
return strcmp(*p1, *p2);
|
|
}
|
|
|
|
ModulePathClassLocationStream::ModulePathClassLocationStream() : ClassLocationStream(), _has_non_jar_modules(false) {
|
|
// Note: for handling of --module-path, see
|
|
// https://openjdk.org/jeps/261#Module-paths
|
|
// https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/module/ModuleFinder.html#of(java.nio.file.Path...)
|
|
|
|
const char* jdk_module_path = Arguments::get_property("jdk.module.path");
|
|
if (jdk_module_path == nullptr) {
|
|
return;
|
|
}
|
|
|
|
ClasspathStream cp_stream(jdk_module_path);
|
|
while (cp_stream.has_next()) {
|
|
const char* path = cp_stream.get_next();
|
|
DIR* dirp = os::opendir(path);
|
|
if (dirp == nullptr && errno == ENOTDIR && has_jar_suffix(path)) {
|
|
add_one_path(path);
|
|
} else if (dirp != nullptr) {
|
|
struct dirent* dentry;
|
|
bool found_jar = false;
|
|
while ((dentry = os::readdir(dirp)) != nullptr) {
|
|
const char* file_name = dentry->d_name;
|
|
if (has_jar_suffix(file_name)) {
|
|
size_t full_name_len = strlen(path) + strlen(file_name) + strlen(os::file_separator()) + 1;
|
|
char* full_name = NEW_RESOURCE_ARRAY(char, full_name_len);
|
|
int n = os::snprintf(full_name, full_name_len, "%s%s%s", path, os::file_separator(), file_name);
|
|
assert((size_t)n == full_name_len - 1, "Unexpected number of characters in string");
|
|
add_one_path(full_name);
|
|
found_jar = true;
|
|
} else if (strcmp(file_name, ".") != 0 && strcmp(file_name, "..") != 0) {
|
|
// Found some non jar entries
|
|
_has_non_jar_modules = true;
|
|
log_info(class, path)("Found non-jar path: '%s%s%s'", path, os::file_separator(), file_name);
|
|
}
|
|
}
|
|
if (!found_jar) {
|
|
log_info(class, path)("Found exploded module path: '%s'", path);
|
|
_has_non_jar_modules = true;
|
|
}
|
|
os::closedir(dirp);
|
|
} else {
|
|
_has_non_jar_modules = true;
|
|
}
|
|
}
|
|
|
|
_array.sort(compare_module_path_by_name);
|
|
}
|
|
|
|
AOTClassLocation* AOTClassLocation::allocate(JavaThread* current, const char* path, int index,
|
|
Group group, bool from_cpattr, bool is_jrt) {
|
|
size_t path_length = 0;
|
|
size_t manifest_length = 0;
|
|
bool check_time = false;
|
|
time_t timestamp = 0;
|
|
int64_t filesize = 0;
|
|
FileType type = FileType::NORMAL;
|
|
// Do not record the actual path of the jrt, as the entire JDK can be moved to a different
|
|
// directory.
|
|
const char* recorded_path = is_jrt ? "" : path;
|
|
path_length = strlen(recorded_path);
|
|
|
|
struct stat st;
|
|
if (os::stat(path, &st) == 0) {
|
|
if ((st.st_mode & S_IFMT) == S_IFDIR) {
|
|
type = FileType::DIR;
|
|
} else {
|
|
timestamp = st.st_mtime;
|
|
filesize = st.st_size;
|
|
|
|
// The timestamp of $JAVA_HOME/lib/modules is not checked at runtime.
|
|
check_time = !is_jrt;
|
|
}
|
|
#ifdef _WINDOWS
|
|
} else if (errno == ERROR_FILE_NOT_FOUND || errno == ERROR_PATH_NOT_FOUND) {
|
|
// On Windows, the errno could be ERROR_PATH_NOT_FOUND (3) in case the directory
|
|
// path doesn't exist.
|
|
type = FileType::NOT_EXIST;
|
|
#endif
|
|
} else if (errno == ENOENT) {
|
|
// We allow the file to not exist, as long as it also doesn't exist during runtime.
|
|
type = FileType::NOT_EXIST;
|
|
} else {
|
|
log_error(cds)("Unable to open file %s.", path);
|
|
MetaspaceShared::unrecoverable_loading_error();
|
|
}
|
|
|
|
ResourceMark rm(current);
|
|
char* manifest = nullptr;
|
|
|
|
if (!is_jrt && type == FileType::NORMAL) {
|
|
manifest = read_manifest(current, path, manifest_length); // resource allocated
|
|
}
|
|
|
|
size_t cs_size = header_size() +
|
|
+ path_length + 1 /* nul-terminated */
|
|
+ manifest_length + 1; /* nul-terminated */
|
|
|
|
AOTClassLocation* cs = (AOTClassLocation*)os::malloc(cs_size, mtClassShared);
|
|
memset(cs, 0, cs_size);
|
|
cs->_path_length = path_length;
|
|
cs->_manifest_length = manifest_length;
|
|
cs->_check_time = check_time;
|
|
cs->_from_cpattr = from_cpattr;
|
|
cs->_timestamp = check_time ? timestamp : 0;
|
|
cs->_filesize = filesize;
|
|
cs->_file_type = type;
|
|
cs->_group = group;
|
|
cs->_index = index;
|
|
|
|
strcpy(((char*)cs) + cs->path_offset(), recorded_path);
|
|
if (manifest_length > 0) {
|
|
memcpy(((char*)cs) + cs->manifest_offset(), manifest, manifest_length);
|
|
}
|
|
assert(*(cs->manifest() + cs->manifest_length()) == '\0', "should be nul-terminated");
|
|
|
|
if (strstr(cs->manifest(), "Multi-Release: true") != nullptr) {
|
|
cs->_is_multi_release_jar = true;
|
|
}
|
|
|
|
if (strstr(cs->manifest(), "Extension-List:") != nullptr) {
|
|
vm_exit_during_cds_dumping(err_msg("-Xshare:dump does not support Extension-List in JAR manifest: %s", path));
|
|
}
|
|
|
|
return cs;
|
|
}
|
|
|
|
char* AOTClassLocation::read_manifest(JavaThread* current, const char* path, size_t& manifest_length) {
|
|
manifest_length = 0;
|
|
|
|
struct stat st;
|
|
if (os::stat(path, &st) != 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
ClassPathEntry* cpe = ClassLoader::create_class_path_entry(current, path, &st);
|
|
if (cpe == nullptr) {
|
|
// <path> is a file, but not a JAR file
|
|
return nullptr;
|
|
}
|
|
assert(cpe->is_jar_file(), "should not be called with a directory");
|
|
|
|
const char* name = "META-INF/MANIFEST.MF";
|
|
char* manifest;
|
|
jint size;
|
|
manifest = (char*) ((ClassPathZipEntry*)cpe)->open_entry(current, name, &size, true);
|
|
|
|
if (manifest == nullptr || size <= 0) { // No Manifest
|
|
manifest_length = 0;
|
|
} else {
|
|
manifest_length = (size_t)size;
|
|
}
|
|
|
|
delete cpe;
|
|
return manifest;
|
|
}
|
|
|
|
// The result is resource allocated.
|
|
char* AOTClassLocation::get_cpattr() const {
|
|
if (_manifest_length == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
size_t buf_size = _manifest_length + 1;
|
|
char* buf = NEW_RESOURCE_ARRAY(char, buf_size);
|
|
memcpy(buf, manifest(), _manifest_length);
|
|
buf[_manifest_length] = 0; // make sure it's 0-terminated
|
|
|
|
// See http://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#JAR%20Manifest
|
|
// Replace all CR/LF and CR with LF
|
|
StringUtils::replace_no_expand(buf, "\r\n", "\n");
|
|
// Remove all new-line continuation (remove all "\n " substrings)
|
|
StringUtils::replace_no_expand(buf, "\n ", "");
|
|
|
|
const char* tag = "Class-Path: ";
|
|
size_t tag_len = strlen(tag);
|
|
char* found = nullptr;
|
|
char* line_start = buf;
|
|
char* end = buf + _manifest_length;
|
|
|
|
assert(*end == 0, "must be nul-terminated");
|
|
|
|
while (line_start < end) {
|
|
char* line_end = strchr(line_start, '\n');
|
|
if (line_end == nullptr) {
|
|
// JAR spec require the manifest file to be terminated by a new line.
|
|
break;
|
|
}
|
|
if (strncmp(tag, line_start, tag_len) == 0) {
|
|
if (found != nullptr) {
|
|
// Same behavior as jdk/src/share/classes/java/util/jar/Attributes.java
|
|
// If duplicated entries are found, the last one is used.
|
|
log_warning(cds)("Warning: Duplicate name in Manifest: %s.\n"
|
|
"Ensure that the manifest does not have duplicate entries, and\n"
|
|
"that blank lines separate individual sections in both your\n"
|
|
"manifest and in the META-INF/MANIFEST.MF entry in the jar file:\n%s\n", tag, path());
|
|
}
|
|
found = line_start + tag_len;
|
|
assert(found <= line_end, "sanity");
|
|
*line_end = '\0';
|
|
}
|
|
line_start = line_end + 1;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
AOTClassLocation* AOTClassLocation::write_to_archive() const {
|
|
AOTClassLocation* archived_copy = (AOTClassLocation*)ArchiveBuilder::ro_region_alloc(total_size());
|
|
memcpy((char*)archived_copy, (char*)this, total_size());
|
|
return archived_copy;
|
|
}
|
|
|
|
const char* AOTClassLocation::file_type_string() const {
|
|
switch (_file_type) {
|
|
case FileType::NORMAL: return "file";
|
|
case FileType::DIR: return "dir";
|
|
case FileType::NOT_EXIST: default: return "not-exist";
|
|
}
|
|
}
|
|
|
|
bool AOTClassLocation::check(const char* runtime_path, bool has_aot_linked_classes) const {
|
|
struct stat st;
|
|
if (os::stat(runtime_path, &st) != 0) {
|
|
if (_file_type != FileType::NOT_EXIST) {
|
|
log_warning(cds)("Required classpath entry does not exist: %s", runtime_path);
|
|
return false;
|
|
}
|
|
} else if ((st.st_mode & S_IFMT) == S_IFDIR) {
|
|
if (_file_type == FileType::NOT_EXIST) {
|
|
log_warning(cds)("'%s' must not exist", runtime_path);
|
|
return false;
|
|
}
|
|
if (_file_type == FileType::NORMAL) {
|
|
log_warning(cds)("'%s' must be a file", runtime_path);
|
|
return false;
|
|
}
|
|
if (!os::dir_is_empty(runtime_path)) {
|
|
log_warning(cds)("directory is not empty: '%s'", runtime_path);
|
|
return false;
|
|
}
|
|
} else {
|
|
if (_file_type == FileType::NOT_EXIST) {
|
|
log_warning(cds)("'%s' must not exist", runtime_path);
|
|
if (has_aot_linked_classes) {
|
|
log_error(cds)("CDS archive has aot-linked classes. It cannot be used because the "
|
|
"file %s exists", runtime_path);
|
|
return false;
|
|
} else {
|
|
log_warning(cds)("Archived non-system classes are disabled because the "
|
|
"file %s exists", runtime_path);
|
|
FileMapInfo::current_info()->set_has_platform_or_app_classes(false);
|
|
if (DynamicArchive::is_mapped()) {
|
|
FileMapInfo::dynamic_info()->set_has_platform_or_app_classes(false);
|
|
}
|
|
}
|
|
}
|
|
if (_file_type == FileType::DIR) {
|
|
log_warning(cds)("'%s' must be a directory", runtime_path);
|
|
return false;
|
|
}
|
|
bool size_differs = _filesize != st.st_size;
|
|
bool time_differs = _check_time && (_timestamp != st.st_mtime);
|
|
if (size_differs || time_differs) {
|
|
log_warning(cds)("This file is not the one used while building the shared archive file: '%s'%s%s",
|
|
runtime_path,
|
|
time_differs ? ", timestamp has changed" : "",
|
|
size_differs ? ", size has changed" : "");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
log_info(class, path)("ok");
|
|
return true;
|
|
}
|
|
|
|
void AOTClassLocationConfig::dumptime_init(JavaThread* current) {
|
|
assert(CDSConfig::is_dumping_archive(), "");
|
|
_dumptime_instance = NEW_C_HEAP_OBJ(AOTClassLocationConfig, mtClassShared);
|
|
_dumptime_instance->dumptime_init_helper(current);
|
|
if (current->has_pending_exception()) {
|
|
// we can get an exception only when we run out of metaspace, but that
|
|
// shouldn't happen this early in bootstrap.
|
|
java_lang_Throwable::print(current->pending_exception(), tty);
|
|
vm_exit_during_initialization("AOTClassLocationConfig::dumptime_init_helper() failed unexpectedly");
|
|
}
|
|
}
|
|
|
|
void AOTClassLocationConfig::dumptime_init_helper(TRAPS) {
|
|
ResourceMark rm;
|
|
GrowableClassLocationArray tmp_array;
|
|
AllClassLocationStreams all_css;
|
|
|
|
AOTClassLocation* jrt = AOTClassLocation::allocate(THREAD, ClassLoader::get_jrt_entry()->name(),
|
|
0, Group::MODULES_IMAGE,
|
|
/*from_cpattr*/false, /*is_jrt*/true);
|
|
tmp_array.append(jrt);
|
|
|
|
parse(THREAD, tmp_array, all_css.boot_cp(), Group::BOOT_CLASSPATH, /*parse_manifest*/true);
|
|
_boot_classpath_end = tmp_array.length();
|
|
|
|
parse(THREAD, tmp_array, all_css.app_cp(), Group::APP_CLASSPATH, /*parse_manifest*/true);
|
|
_app_classpath_end = tmp_array.length();
|
|
|
|
parse(THREAD, tmp_array, all_css.module_path(), Group::MODULE_PATH, /*parse_manifest*/false);
|
|
_module_end = tmp_array.length();
|
|
|
|
_class_locations = MetadataFactory::new_array<AOTClassLocation*>(ClassLoaderData::the_null_class_loader_data(),
|
|
tmp_array.length(), CHECK);
|
|
for (int i = 0; i < tmp_array.length(); i++) {
|
|
_class_locations->at_put(i, tmp_array.at(i));
|
|
}
|
|
|
|
_dumptime_jar_files = MetadataFactory::new_array<ClassPathZipEntry*>(ClassLoaderData::the_null_class_loader_data(),
|
|
tmp_array.length(), CHECK);
|
|
for (int i = 1; i < tmp_array.length(); i++) {
|
|
ClassPathZipEntry* jar_file = ClassLoader::create_class_path_zip_entry(tmp_array.at(i)->path());
|
|
_dumptime_jar_files->at_put(i, jar_file); // may be null if the path is not a valid JAR file
|
|
}
|
|
|
|
const char* lcp = find_lcp(all_css.boot_and_app_cp(), _dumptime_lcp_len);
|
|
if (_dumptime_lcp_len > 0) {
|
|
os::free((void*)lcp);
|
|
log_info(class, path)("Longest common prefix = %s (%zu chars)", lcp, _dumptime_lcp_len);
|
|
} else {
|
|
assert(_dumptime_lcp_len == 0, "sanity");
|
|
log_info(class, path)("Longest common prefix = <none> (0 chars)");
|
|
}
|
|
|
|
_has_non_jar_modules = all_css.module_path().has_non_jar_modules();
|
|
_has_platform_classes = false;
|
|
_has_app_classes = false;
|
|
_max_used_index = 0;
|
|
}
|
|
|
|
// Find the longest common prefix of two paths, up to max_lcp_len.
|
|
// E.g. p1 = "/a/b/foo"
|
|
// p2 = "/a/b/bar"
|
|
// max_lcp_len = 3
|
|
// -> returns 3
|
|
static size_t find_lcp_of_two_paths(const char* p1, const char* p2, size_t max_lcp_len) {
|
|
size_t lcp_len = 0;
|
|
char sep = os::file_separator()[0];
|
|
for (size_t i = 0; ; i++) {
|
|
char c1 = *p1++;
|
|
char c2 = *p2++;
|
|
if (c1 == 0 || c2 == 0 || c1 != c2) {
|
|
break;
|
|
}
|
|
if (c1 == sep) {
|
|
lcp_len = i + 1;
|
|
assert(lcp_len <= max_lcp_len, "sanity");
|
|
if (lcp_len == max_lcp_len) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return lcp_len;
|
|
}
|
|
|
|
// cheap-allocated if lcp_len > 0
|
|
const char* AOTClassLocationConfig::find_lcp(ClassLocationStream& css, size_t& lcp_len) {
|
|
const char* first_path = nullptr;
|
|
char sep = os::file_separator()[0];
|
|
|
|
for (css.start(); css.has_next(); ) {
|
|
const char* path = css.get_next();
|
|
if (first_path == nullptr) {
|
|
first_path = path;
|
|
const char* p = strrchr(first_path, sep);
|
|
if (p == nullptr) {
|
|
lcp_len = 0;
|
|
return "";
|
|
} else {
|
|
lcp_len = p - first_path + 1;
|
|
}
|
|
} else {
|
|
lcp_len = find_lcp_of_two_paths(first_path, path, lcp_len);
|
|
if (lcp_len == 0) {
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (first_path != nullptr && lcp_len > 0) {
|
|
char* lcp = NEW_C_HEAP_ARRAY(char, lcp_len + 1, mtClassShared);
|
|
lcp[0] = 0;
|
|
strncat(lcp, first_path, lcp_len);
|
|
return lcp;
|
|
} else {
|
|
lcp_len = 0;
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void AOTClassLocationConfig::parse(JavaThread* current, GrowableClassLocationArray& tmp_array,
|
|
ClassLocationStream& css, Group group, bool parse_manifest) {
|
|
for (css.start(); css.has_next(); ) {
|
|
add_class_location(current, tmp_array, css.get_next(), group, parse_manifest, /*from_cpattr*/false);
|
|
}
|
|
}
|
|
|
|
void AOTClassLocationConfig::add_class_location(JavaThread* current, GrowableClassLocationArray& tmp_array,
|
|
const char* path, Group group, bool parse_manifest, bool from_cpattr) {
|
|
AOTClassLocation* cs = AOTClassLocation::allocate(current, path, tmp_array.length(), group, from_cpattr);
|
|
tmp_array.append(cs);
|
|
|
|
if (!parse_manifest) {
|
|
// parse_manifest is true for -classpath and -Xbootclasspath/a, and false for --module-path.
|
|
return;
|
|
}
|
|
|
|
ResourceMark rm;
|
|
char* cp_attr = cs->get_cpattr(); // resource allocated
|
|
if (cp_attr != nullptr && strlen(cp_attr) > 0) {
|
|
//trace_class_path("found Class-Path: ", cp_attr); FIXME
|
|
|
|
char sep = os::file_separator()[0];
|
|
const char* dir_name = cs->path();
|
|
const char* dir_tail = strrchr(dir_name, sep);
|
|
#ifdef _WINDOWS
|
|
// On Windows, we also support forward slash as the file separator when locating entries in the classpath entry.
|
|
const char* dir_tail2 = strrchr(dir_name, '/');
|
|
if (dir_tail == nullptr) {
|
|
dir_tail = dir_tail2;
|
|
} else if (dir_tail2 != nullptr && dir_tail2 > dir_tail) {
|
|
dir_tail = dir_tail2;
|
|
}
|
|
#endif
|
|
int dir_len;
|
|
if (dir_tail == nullptr) {
|
|
dir_len = 0;
|
|
} else {
|
|
dir_len = pointer_delta_as_int(dir_tail, dir_name) + 1;
|
|
}
|
|
|
|
// Split the cp_attr by spaces, and add each file
|
|
char* file_start = cp_attr;
|
|
char* end = file_start + strlen(file_start);
|
|
|
|
while (file_start < end) {
|
|
char* file_end = strchr(file_start, ' ');
|
|
if (file_end != nullptr) {
|
|
*file_end = 0;
|
|
file_end += 1;
|
|
} else {
|
|
file_end = end;
|
|
}
|
|
|
|
size_t name_len = strlen(file_start);
|
|
if (name_len > 0) {
|
|
ResourceMark rm(current);
|
|
size_t libname_len = dir_len + name_len;
|
|
char* libname = NEW_RESOURCE_ARRAY(char, libname_len + 1);
|
|
int n = os::snprintf(libname, libname_len + 1, "%.*s%s", dir_len, dir_name, file_start);
|
|
assert((size_t)n == libname_len, "Unexpected number of characters in string");
|
|
|
|
// Avoid infinite recursion when two JAR files refer to each
|
|
// other via cpattr.
|
|
bool found_duplicate = false;
|
|
for (int i = boot_cp_start_index(); i < tmp_array.length(); i++) {
|
|
if (strcmp(tmp_array.at(i)->path(), libname) == 0) {
|
|
found_duplicate = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found_duplicate) {
|
|
add_class_location(current, tmp_array, libname, group, parse_manifest, /*from_cpattr*/true);
|
|
}
|
|
}
|
|
|
|
file_start = file_end;
|
|
}
|
|
}
|
|
}
|
|
|
|
AOTClassLocation const* AOTClassLocationConfig::class_location_at(int index) const {
|
|
return _class_locations->at(index);
|
|
}
|
|
|
|
int AOTClassLocationConfig::get_module_shared_path_index(Symbol* location) const {
|
|
if (location->starts_with("jrt:", 4)) {
|
|
assert(class_location_at(0)->is_modules_image(), "sanity");
|
|
return 0;
|
|
}
|
|
|
|
if (num_module_paths() == 0) {
|
|
// The archive(s) were created without --module-path option
|
|
return -1;
|
|
}
|
|
|
|
if (!location->starts_with("file:", 5)) {
|
|
return -1;
|
|
}
|
|
|
|
// skip_uri_protocol was also called during dump time -- see ClassLoaderExt::process_module_table()
|
|
ResourceMark rm;
|
|
const char* file = ClassLoader::uri_to_path(location->as_C_string());
|
|
for (int i = module_path_start_index(); i < module_path_end_index(); i++) {
|
|
const AOTClassLocation* cs = class_location_at(i);
|
|
assert(!cs->has_unnamed_module(), "must be");
|
|
bool same = os::same_files(file, cs->path());
|
|
log_debug(class, path)("get_module_shared_path_index (%d) %s : %s = %s", i,
|
|
location->as_C_string(), cs->path(), same ? "same" : "different");
|
|
if (same) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// We allow non-empty dirs as long as no classes have been loaded from them.
|
|
void AOTClassLocationConfig::check_nonempty_dirs() const {
|
|
assert(CDSConfig::is_dumping_archive(), "sanity");
|
|
|
|
bool has_nonempty_dir = false;
|
|
dumptime_iterate([&](AOTClassLocation* cs) {
|
|
if (cs->index() > _max_used_index) {
|
|
return false; // stop iterating
|
|
}
|
|
if (cs->is_dir()) {
|
|
if (!os::dir_is_empty(cs->path())) {
|
|
log_error(cds)("Error: non-empty directory '%s'", cs->path());
|
|
has_nonempty_dir = true;
|
|
}
|
|
}
|
|
return true; // keep iterating
|
|
});
|
|
|
|
if (has_nonempty_dir) {
|
|
vm_exit_during_cds_dumping("Cannot have non-empty directory in paths", nullptr);
|
|
}
|
|
}
|
|
|
|
// It's possible to use reflection+setAccessible to call into ClassLoader::defineClass() to
|
|
// pretend that a dynamically generated class comes from a JAR file in the classpath.
|
|
// Detect such classes so that they can be excluded from the archive.
|
|
bool AOTClassLocationConfig::is_valid_classpath_index(int classpath_index, InstanceKlass* ik) {
|
|
if (1 <= classpath_index && classpath_index < length()) {
|
|
ClassPathZipEntry *zip = _dumptime_jar_files->at(classpath_index);
|
|
if (zip != nullptr) {
|
|
JavaThread* current = JavaThread::current();
|
|
ResourceMark rm(current);
|
|
const char* const class_name = ik->name()->as_C_string();
|
|
const char* const file_name = ClassLoader::file_name_for_class_name(class_name,
|
|
ik->name()->utf8_length());
|
|
if (!zip->has_entry(current, file_name)) {
|
|
log_warning(cds)("class %s cannot be archived because it was not defined from %s as claimed",
|
|
class_name, zip->name());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
AOTClassLocationConfig* AOTClassLocationConfig::write_to_archive() const {
|
|
Array<AOTClassLocation*>* archived_copy = ArchiveBuilder::new_ro_array<AOTClassLocation*>(_class_locations->length());
|
|
for (int i = 0; i < _class_locations->length(); i++) {
|
|
archived_copy->at_put(i, _class_locations->at(i)->write_to_archive());
|
|
ArchivePtrMarker::mark_pointer((address*)archived_copy->adr_at(i));
|
|
}
|
|
|
|
AOTClassLocationConfig* dumped = (AOTClassLocationConfig*)ArchiveBuilder::ro_region_alloc(sizeof(AOTClassLocationConfig));
|
|
memcpy(dumped, this, sizeof(AOTClassLocationConfig));
|
|
dumped->_class_locations = archived_copy;
|
|
ArchivePtrMarker::mark_pointer(&dumped->_class_locations);
|
|
|
|
return dumped;
|
|
}
|
|
|
|
bool AOTClassLocationConfig::check_classpaths(bool is_boot_classpath, bool has_aot_linked_classes,
|
|
int index_start, int index_end,
|
|
ClassLocationStream& runtime_css,
|
|
bool use_lcp_match, const char* runtime_lcp,
|
|
size_t runtime_lcp_len) const {
|
|
if (index_start >= index_end && runtime_css.is_empty()) { // nothing to check
|
|
return true;
|
|
}
|
|
|
|
ResourceMark rm;
|
|
const char* which = is_boot_classpath ? "boot" : "app";
|
|
LogTarget(Info, class, path) lt;
|
|
if (lt.is_enabled()) {
|
|
LogStream ls(lt);
|
|
ls.print("Checking %s classpath", which);
|
|
ls.print_cr("%s", use_lcp_match ? " (with longest common prefix substitution)" : "");
|
|
ls.print("- expected : '");
|
|
print_dumptime_classpath(ls, index_start, index_end, use_lcp_match, _dumptime_lcp_len, runtime_lcp, runtime_lcp_len);
|
|
ls.print_cr("'");
|
|
ls.print("- actual : '");
|
|
runtime_css.print(&ls);
|
|
ls.print_cr("'");
|
|
}
|
|
|
|
runtime_css.start();
|
|
for (int i = index_start; i < index_end; i++) {
|
|
ResourceMark rm;
|
|
const AOTClassLocation* cs = class_location_at(i);
|
|
const char* effective_dumptime_path = cs->path();
|
|
if (use_lcp_match && _dumptime_lcp_len > 0) {
|
|
effective_dumptime_path = substitute(effective_dumptime_path, _dumptime_lcp_len, runtime_lcp, runtime_lcp_len);
|
|
}
|
|
|
|
log_info(class, path)("Checking '%s' %s%s", effective_dumptime_path, cs->file_type_string(),
|
|
cs->from_cpattr() ? " (from JAR manifest ClassPath attribute)" : "");
|
|
if (!cs->from_cpattr() && file_exists(effective_dumptime_path)) {
|
|
if (!runtime_css.has_next()) {
|
|
log_warning(cds)("%s classpath has fewer elements than expected", which);
|
|
return false;
|
|
}
|
|
const char* runtime_path = runtime_css.get_next();
|
|
while (!file_exists(runtime_path) && runtime_css.has_next()) {
|
|
runtime_path = runtime_css.get_next();
|
|
}
|
|
if (!os::same_files(effective_dumptime_path, runtime_path)) {
|
|
log_warning(cds)("The name of %s classpath [%d] does not match: expected '%s', got '%s'",
|
|
which, runtime_css.current(), effective_dumptime_path, runtime_path);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!cs->check(effective_dumptime_path, has_aot_linked_classes)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check if the runtime boot classpath has more entries than the one stored in the archive and if the app classpath
|
|
// or the module path requires validation.
|
|
if (is_boot_classpath && runtime_css.has_next() && (need_to_check_app_classpath() || num_module_paths() > 0)) {
|
|
// the check passes if all the extra runtime boot classpath entries are non-existent
|
|
if (check_paths_existence(runtime_css)) {
|
|
log_warning(cds)("boot classpath is longer than expected");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AOTClassLocationConfig::file_exists(const char* filename) const{
|
|
struct stat st;
|
|
return (os::stat(filename, &st) == 0 && st.st_size > 0);
|
|
}
|
|
|
|
bool AOTClassLocationConfig::check_paths_existence(ClassLocationStream& runtime_css) const {
|
|
bool exist = false;
|
|
while (runtime_css.has_next()) {
|
|
const char* path = runtime_css.get_next();
|
|
if (file_exists(path)) {
|
|
exist = true;
|
|
break;
|
|
}
|
|
}
|
|
return exist;
|
|
}
|
|
|
|
bool AOTClassLocationConfig::check_module_paths(bool has_aot_linked_classes, int index_start, int index_end,
|
|
ClassLocationStream& runtime_css,
|
|
bool* has_extra_module_paths) const {
|
|
if (index_start >= index_end && runtime_css.is_empty()) { // nothing to check
|
|
return true;
|
|
}
|
|
|
|
ResourceMark rm;
|
|
|
|
LogTarget(Info, class, path) lt;
|
|
if (lt.is_enabled()) {
|
|
LogStream ls(lt);
|
|
ls.print_cr("Checking module paths");
|
|
ls.print("- expected : '");
|
|
print_dumptime_classpath(ls, index_start, index_end, false, 0, nullptr, 0);
|
|
ls.print_cr("'");
|
|
ls.print("- actual : '");
|
|
runtime_css.print(&ls);
|
|
ls.print_cr("'");
|
|
}
|
|
|
|
// Make sure all the dumptime module paths exist and are unchanged
|
|
for (int i = index_start; i < index_end; i++) {
|
|
const AOTClassLocation* cs = class_location_at(i);
|
|
const char* dumptime_path = cs->path();
|
|
|
|
assert(!cs->from_cpattr(), "not applicable for module path");
|
|
log_info(class, path)("Checking '%s' %s", dumptime_path, cs->file_type_string());
|
|
|
|
if (!cs->check(dumptime_path, has_aot_linked_classes)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We allow runtime_css to be a superset of the module paths specified in dumptime. E.g.,
|
|
// Dumptime: A:C
|
|
// Runtime: A:B:C
|
|
runtime_css.start();
|
|
for (int i = index_start; i < index_end; i++) {
|
|
const AOTClassLocation* cs = class_location_at(i);
|
|
const char* dumptime_path = cs->path();
|
|
|
|
while (true) {
|
|
if (!runtime_css.has_next()) {
|
|
log_warning(cds)("module path has fewer elements than expected");
|
|
*has_extra_module_paths = true;
|
|
return true;
|
|
}
|
|
// Both this->class_locations() and runtime_css are alphabetically sorted. Skip
|
|
// items in runtime_css until we see dumptime_path.
|
|
const char* runtime_path = runtime_css.get_next();
|
|
if (!os::same_files(dumptime_path, runtime_path)) {
|
|
*has_extra_module_paths = true;
|
|
return true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (runtime_css.has_next()) {
|
|
*has_extra_module_paths = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AOTClassLocationConfig::print_dumptime_classpath(LogStream& ls, int index_start, int index_end,
|
|
bool do_substitute, size_t remove_prefix_len,
|
|
const char* prepend, size_t prepend_len) const {
|
|
const char* sep = "";
|
|
for (int i = index_start; i < index_end; i++) {
|
|
ResourceMark rm;
|
|
const AOTClassLocation* cs = class_location_at(i);
|
|
const char* path = cs->path();
|
|
if (!cs->from_cpattr()) {
|
|
ls.print("%s", sep);
|
|
if (do_substitute) {
|
|
path = substitute(path, remove_prefix_len, prepend, prepend_len);
|
|
}
|
|
ls.print("%s", path);
|
|
sep = os::path_separator();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returned path is resource-allocated
|
|
const char* AOTClassLocationConfig::substitute(const char* path, // start with this path (which was recorded from dump time)
|
|
size_t remove_prefix_len, // remove this number of chars from the beginning
|
|
const char* prepend, // prepend this string
|
|
size_t prepend_len) { // length of the prepended string
|
|
size_t len = strlen(path);
|
|
assert(len > remove_prefix_len, "sanity");
|
|
assert(prepend_len == strlen(prepend), "sanity");
|
|
len -= remove_prefix_len;
|
|
len += prepend_len;
|
|
|
|
char* buf = NEW_RESOURCE_ARRAY(char, len + 1);
|
|
int n = os::snprintf(buf, len + 1, "%s%s", prepend, path + remove_prefix_len);
|
|
assert(size_t(n) == len, "sanity");
|
|
|
|
return buf;
|
|
}
|
|
|
|
// For performance, we avoid using LCP match if there's at least one
|
|
// AOTClassLocation can be matched exactly: this means all other AOTClassLocations must be
|
|
// matched exactly.
|
|
bool AOTClassLocationConfig::need_lcp_match(AllClassLocationStreams& all_css) const {
|
|
if (app_cp_end_index() == boot_cp_start_index()) {
|
|
// No need to use lcp-match when there are no boot/app paths.
|
|
// TODO: LCP-match not yet supported for modules.
|
|
return false;
|
|
}
|
|
|
|
if (need_lcp_match_helper(boot_cp_start_index(), boot_cp_end_index(), all_css.boot_cp()) &&
|
|
need_lcp_match_helper(app_cp_start_index(), app_cp_end_index(), all_css.app_cp())) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool AOTClassLocationConfig::need_lcp_match_helper(int start, int end, ClassLocationStream& css) const {
|
|
int i = start;
|
|
for (css.start(); i < end && css.has_next(); ) {
|
|
const AOTClassLocation* cs = class_location_at(i++);
|
|
const char* runtime_path = css.get_next();
|
|
if (cs->must_exist() && os::same_files(cs->path(), runtime_path)) {
|
|
// Most likely, we will come to here at the first iteration.
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AOTClassLocationConfig::validate(bool has_aot_linked_classes, bool* has_extra_module_paths) const {
|
|
ResourceMark rm;
|
|
AllClassLocationStreams all_css;
|
|
|
|
const char* jrt = ClassLoader::get_jrt_entry()->name();
|
|
bool success = class_location_at(0)->check(jrt, has_aot_linked_classes);
|
|
log_info(class, path)("Modules image %s validation: %s", jrt, success ? "passed" : "failed");
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
if (class_locations()->length() == 1) {
|
|
if ((module_path_start_index() >= module_path_end_index()) && Arguments::get_property("jdk.module.path") != nullptr) {
|
|
*has_extra_module_paths = true;
|
|
} else {
|
|
*has_extra_module_paths = false;
|
|
}
|
|
} else {
|
|
bool use_lcp_match = need_lcp_match(all_css);
|
|
const char* runtime_lcp;
|
|
size_t runtime_lcp_len;
|
|
|
|
log_info(class, path)("Longest common prefix substitution in boot/app classpath matching: %s",
|
|
use_lcp_match ? "yes" : "no");
|
|
if (use_lcp_match) {
|
|
runtime_lcp = find_lcp(all_css.boot_and_app_cp(), runtime_lcp_len);
|
|
log_info(class, path)("Longest common prefix: %s (%zu chars)", runtime_lcp, runtime_lcp_len);
|
|
} else {
|
|
runtime_lcp = nullptr;
|
|
runtime_lcp_len = 0;
|
|
}
|
|
|
|
success = check_classpaths(true, has_aot_linked_classes, boot_cp_start_index(), boot_cp_end_index(), all_css.boot_cp(),
|
|
use_lcp_match, runtime_lcp, runtime_lcp_len);
|
|
log_info(class, path)("Archived boot classpath validation: %s", success ? "passed" : "failed");
|
|
|
|
if (success && need_to_check_app_classpath()) {
|
|
success = check_classpaths(false, has_aot_linked_classes, app_cp_start_index(), app_cp_end_index(), all_css.app_cp(),
|
|
use_lcp_match, runtime_lcp, runtime_lcp_len);
|
|
log_info(class, path)("Archived app classpath validation: %s", success ? "passed" : "failed");
|
|
}
|
|
|
|
if (success) {
|
|
success = check_module_paths(has_aot_linked_classes, module_path_start_index(), module_path_end_index(),
|
|
all_css.module_path(), has_extra_module_paths);
|
|
log_info(class, path)("Archived module path validation: %s%s", success ? "passed" : "failed",
|
|
(*has_extra_module_paths) ? " (extra module paths found)" : "");
|
|
}
|
|
|
|
if (runtime_lcp_len > 0) {
|
|
os::free((void*)runtime_lcp);
|
|
}
|
|
}
|
|
|
|
if (success) {
|
|
_runtime_instance = this;
|
|
} else {
|
|
const char* mismatch_msg = "shared class paths mismatch";
|
|
const char* hint_msg = log_is_enabled(Info, class, path) ?
|
|
"" : " (hint: enable -Xlog:class+path=info to diagnose the failure)";
|
|
if (RequireSharedSpaces && !PrintSharedArchiveAndExit) {
|
|
if (CDSConfig::is_dumping_final_static_archive()) {
|
|
log_error(cds)("class path and/or module path are not compatible with the "
|
|
"ones specified when the AOTConfiguration file was recorded%s", hint_msg);
|
|
vm_exit_during_initialization("Unable to use create AOT cache.", nullptr);
|
|
} else {
|
|
log_error(cds)("%s%s", mismatch_msg, hint_msg);
|
|
MetaspaceShared::unrecoverable_loading_error();
|
|
}
|
|
} else {
|
|
log_warning(cds)("%s%s", mismatch_msg, hint_msg);
|
|
MetaspaceShared::report_loading_error(nullptr);
|
|
}
|
|
}
|
|
return success;
|
|
}
|