# HG changeset patch # User ccheung # Date 1561740550 25200 # Node ID b279ae9843b8cf63d476a35d2222a9638407d70a # Parent 52ef2c940423a10a7e4c8602c7d15fd8cf010670 8211723: AppCDS: referring to a jar file in any way other than exactly how it was done during dumping doesn't work Summary: Replaced os::file_name_strncmp() with os::same_files(). Reviewed-by: iklam, jiangli diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/os/aix/os_aix.inline.hpp --- a/src/hotspot/os/aix/os_aix.inline.hpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/os/aix/os_aix.inline.hpp Fri Jun 28 09:49:10 2019 -0700 @@ -37,11 +37,6 @@ #include #include -// File names are case-insensitive on windows only. -inline int os::file_name_strncmp(const char* s1, const char* s2, size_t num) { - return strncmp(s1, s2, num); -} - inline bool os::uses_stack_guard_pages() { return true; } diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/os/bsd/os_bsd.inline.hpp --- a/src/hotspot/os/bsd/os_bsd.inline.hpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/os/bsd/os_bsd.inline.hpp Fri Jun 28 09:49:10 2019 -0700 @@ -35,11 +35,6 @@ #include #include -// File names are case-insensitive on windows only -inline int os::file_name_strncmp(const char* s1, const char* s2, size_t num) { - return strncmp(s1, s2, num); -} - inline bool os::uses_stack_guard_pages() { return true; } diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/os/linux/os_linux.inline.hpp --- a/src/hotspot/os/linux/os_linux.inline.hpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/os/linux/os_linux.inline.hpp Fri Jun 28 09:49:10 2019 -0700 @@ -35,11 +35,6 @@ #include #include -// File names are case-insensitive on windows only -inline int os::file_name_strncmp(const char* s1, const char* s2, size_t num) { - return strncmp(s1, s2, num); -} - inline bool os::uses_stack_guard_pages() { return true; } diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/os/posix/os_posix.cpp --- a/src/hotspot/os/posix/os_posix.cpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/os/posix/os_posix.cpp Fri Jun 28 09:49:10 2019 -0700 @@ -1450,6 +1450,30 @@ return path; } +bool os::same_files(const char* file1, const char* file2) { + if (strcmp(file1, file2) == 0) { + return true; + } + + bool is_same = false; + struct stat st1; + struct stat st2; + + if (os::stat(file1, &st1) < 0) { + return false; + } + + if (os::stat(file2, &st2) < 0) { + return false; + } + + if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) { + // same files + is_same = true; + } + return is_same; +} + // Check minimum allowable stack sizes for thread creation and to initialize // the java system classes, including StackOverflowError - depends on page // size. diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/os/solaris/os_solaris.inline.hpp --- a/src/hotspot/os/solaris/os_solaris.inline.hpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/os/solaris/os_solaris.inline.hpp Fri Jun 28 09:49:10 2019 -0700 @@ -37,11 +37,6 @@ #include #include -// File names are case-insensitive on windows only -inline int os::file_name_strncmp(const char* s1, const char* s2, size_t num) { - return strncmp(s1, s2, num); -} - inline bool os::uses_stack_guard_pages() { return true; } diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/os/windows/os_windows.cpp --- a/src/hotspot/os/windows/os_windows.cpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/os/windows/os_windows.cpp Fri Jun 28 09:49:10 2019 -0700 @@ -4367,6 +4367,88 @@ return ret; } +static HANDLE create_read_only_file_handle(const char* file) { + if (file == NULL) { + return INVALID_HANDLE_VALUE; + } + + char* nativepath = (char*)os::strdup(file, mtInternal); + if (nativepath == NULL) { + errno = ENOMEM; + return INVALID_HANDLE_VALUE; + } + os::native_path(nativepath); + + size_t len = strlen(nativepath); + HANDLE handle = INVALID_HANDLE_VALUE; + + if (len < MAX_PATH) { + handle = ::CreateFile(nativepath, 0, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + } else { + errno_t err = ERROR_SUCCESS; + wchar_t* wfile = create_unc_path(nativepath, err); + if (err != ERROR_SUCCESS) { + if (wfile != NULL) { + destroy_unc_path(wfile); + } + os::free(nativepath); + return INVALID_HANDLE_VALUE; + } + handle = ::CreateFileW(wfile, 0, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + destroy_unc_path(wfile); + } + + os::free(nativepath); + return handle; +} + +bool os::same_files(const char* file1, const char* file2) { + + if (file1 == NULL && file2 == NULL) { + return true; + } + + if (file1 == NULL || file2 == NULL) { + return false; + } + + if (strcmp(file1, file2) == 0) { + return true; + } + + HANDLE handle1 = create_read_only_file_handle(file1); + HANDLE handle2 = create_read_only_file_handle(file2); + bool result = false; + + // if we could open both paths... + if (handle1 != INVALID_HANDLE_VALUE && handle2 != INVALID_HANDLE_VALUE) { + BY_HANDLE_FILE_INFORMATION fileInfo1; + BY_HANDLE_FILE_INFORMATION fileInfo2; + if (::GetFileInformationByHandle(handle1, &fileInfo1) && + ::GetFileInformationByHandle(handle2, &fileInfo2)) { + // the paths are the same if they refer to the same file (fileindex) on the same volume (volume serial number) + if (fileInfo1.dwVolumeSerialNumber == fileInfo2.dwVolumeSerialNumber && + fileInfo1.nFileIndexHigh == fileInfo2.nFileIndexHigh && + fileInfo1.nFileIndexLow == fileInfo2.nFileIndexLow) { + result = true; + } + } + } + + //free the handles + if (handle1 != INVALID_HANDLE_VALUE) { + ::CloseHandle(handle1); + } + + if (handle2 != INVALID_HANDLE_VALUE) { + ::CloseHandle(handle2); + } + + return result; +} + #define FT2INT64(ft) \ ((jlong)((jlong)(ft).dwHighDateTime << 32 | (julong)(ft).dwLowDateTime)) diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/os/windows/os_windows.inline.hpp --- a/src/hotspot/os/windows/os_windows.inline.hpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/os/windows/os_windows.inline.hpp Fri Jun 28 09:49:10 2019 -0700 @@ -32,11 +32,6 @@ inline const int os::default_file_open_flags() { return O_BINARY | O_NOINHERIT;} -// File names are case-insensitive on windows only -inline int os::file_name_strncmp(const char* s, const char* t, size_t num) { - return _strnicmp(s, t, num); -} - inline void os::dll_unload(void *lib) { ::FreeLibrary((HMODULE)lib); } diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/share/classfile/classLoader.cpp --- a/src/hotspot/share/classfile/classLoader.cpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/share/classfile/classLoader.cpp Fri Jun 28 09:49:10 2019 -0700 @@ -292,11 +292,13 @@ return NULL; } -ClassPathZipEntry::ClassPathZipEntry(jzfile* zip, const char* zip_name, bool is_boot_append) : ClassPathEntry() { +ClassPathZipEntry::ClassPathZipEntry(jzfile* zip, const char* zip_name, + bool is_boot_append, bool from_class_path_attr) : ClassPathEntry() { _zip = zip; char *copy = NEW_C_HEAP_ARRAY(char, strlen(zip_name)+1, mtClass); strcpy(copy, zip_name); _zip_name = copy; + _from_class_path_attr = from_class_path_attr; } ClassPathZipEntry::~ClassPathZipEntry() { @@ -577,7 +579,7 @@ strncpy(path, &class_path[start], end - start); path[end - start] = '\0'; - update_class_path_entry_list(path, false, false); + update_class_path_entry_list(path, false, false, false); while (class_path[end] == os::path_separator()[0]) { end++; @@ -612,7 +614,7 @@ // File or directory found ClassPathEntry* new_entry = NULL; new_entry = create_class_path_entry(path, &st, true /* throw_exception */, - false /*is_boot_append */, CHECK); + false /*is_boot_append */, false /* from_class_path_attr */, CHECK); if (new_entry == NULL) { return; } @@ -668,7 +670,7 @@ struct stat st; if (os::stat(path, &st) == 0) { // File or directory found - ClassPathEntry* new_entry = create_class_path_entry(path, &st, false, false, CHECK); + ClassPathEntry* new_entry = create_class_path_entry(path, &st, false, false, false, CHECK); // If the path specification is valid, enter it into this module's list if (new_entry != NULL) { module_cpl->add_to_list(new_entry); @@ -737,7 +739,7 @@ struct stat st; if (os::stat(path, &st) == 0) { // Directory found - ClassPathEntry* new_entry = create_class_path_entry(path, &st, false, false, CHECK); + ClassPathEntry* new_entry = create_class_path_entry(path, &st, false, false, false, CHECK); // Check for a jimage if (Arguments::has_jimage()) { @@ -754,7 +756,7 @@ } else { // Every entry on the system boot class path after the initial base piece, // which is set by os::set_boot_path(), is considered an appended entry. - update_class_path_entry_list(path, false, true); + update_class_path_entry_list(path, false, true, false); } while (class_path[end] == os::path_separator()[0]) { @@ -782,7 +784,7 @@ struct stat st; if (os::stat(path, &st) == 0) { // Directory found - ClassPathEntry* new_entry = create_class_path_entry(path, &st, false, false, CHECK); + ClassPathEntry* new_entry = create_class_path_entry(path, &st, false, false, false, CHECK); // If the path specification is valid, enter it into this module's list. // There is no need to check for duplicate modules in the exploded entry list, @@ -802,7 +804,9 @@ ClassPathEntry* ClassLoader::create_class_path_entry(const char *path, const struct stat* st, bool throw_exception, - bool is_boot_append, TRAPS) { + bool is_boot_append, + bool from_class_path_attr, + TRAPS) { JavaThread* thread = JavaThread::current(); ClassPathEntry* new_entry = NULL; if ((st->st_mode & S_IFMT) == S_IFREG) { @@ -832,7 +836,7 @@ zip = (*ZipOpen)(canonical_path, &error_msg); } if (zip != NULL && error_msg == NULL) { - new_entry = new ClassPathZipEntry(zip, path, is_boot_append); + new_entry = new ClassPathZipEntry(zip, path, is_boot_append, from_class_path_attr); } else { char *msg; if (error_msg == NULL) { @@ -882,7 +886,7 @@ } if (zip != NULL && error_msg == NULL) { // create using canonical path - return new ClassPathZipEntry(zip, canonical_path, is_boot_append); + return new ClassPathZipEntry(zip, canonical_path, is_boot_append, false); } } } @@ -956,13 +960,14 @@ bool ClassLoader::update_class_path_entry_list(const char *path, bool check_for_duplicates, bool is_boot_append, + bool from_class_path_attr, bool throw_exception) { struct stat st; if (os::stat(path, &st) == 0) { // File or directory found ClassPathEntry* new_entry = NULL; Thread* THREAD = Thread::current(); - new_entry = create_class_path_entry(path, &st, throw_exception, is_boot_append, CHECK_(false)); + new_entry = create_class_path_entry(path, &st, throw_exception, is_boot_append, from_class_path_attr, CHECK_(false)); if (new_entry == NULL) { return false; } diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/share/classfile/classLoader.hpp --- a/src/hotspot/share/classfile/classLoader.hpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/share/classfile/classLoader.hpp Fri Jun 28 09:49:10 2019 -0700 @@ -53,6 +53,8 @@ void set_next(ClassPathEntry* next); virtual bool is_modules_image() const = 0; virtual bool is_jar_file() const = 0; + // Is this entry created from the "Class-path" attribute from a JAR Manifest? + virtual bool from_class_path_attr() const = 0; virtual const char* name() const = 0; virtual JImageFile* jimage() const = 0; virtual void close_jimage() = 0; @@ -73,6 +75,7 @@ public: bool is_modules_image() const { return false; } bool is_jar_file() const { return false; } + bool from_class_path_attr() const { return false; } const char* name() const { return _dir; } JImageFile* jimage() const { return NULL; } void close_jimage() {} @@ -99,13 +102,15 @@ private: jzfile* _zip; // The zip archive const char* _zip_name; // Name of zip archive + bool _from_class_path_attr; // From the "Class-path" attribute of a jar file public: bool is_modules_image() const { return false; } bool is_jar_file() const { return true; } + bool from_class_path_attr() const { return _from_class_path_attr; } const char* name() const { return _zip_name; } JImageFile* jimage() const { return NULL; } void close_jimage() {} - ClassPathZipEntry(jzfile* zip, const char* zip_name, bool is_boot_append); + ClassPathZipEntry(jzfile* zip, const char* zip_name, bool is_boot_append, bool from_class_path_attr); virtual ~ClassPathZipEntry(); u1* open_entry(const char* name, jint* filesize, bool nul_terminate, TRAPS); ClassFileStream* open_stream(const char* name, TRAPS); @@ -122,6 +127,7 @@ public: bool is_modules_image() const; bool is_jar_file() const { return false; } + bool from_class_path_attr() const { return false; } bool is_open() const { return _jimage != NULL; } const char* name() const { return _name == NULL ? "" : _name; } JImageFile* jimage() const { return _jimage; } @@ -257,7 +263,8 @@ public: static ClassPathEntry* create_class_path_entry(const char *path, const struct stat* st, bool throw_exception, - bool is_boot_append, TRAPS); + bool is_boot_append, + bool from_class_path_attr, TRAPS); // If the package for the fully qualified class name is in the boot // loader's package entry table then add_package() sets the classpath_index @@ -281,6 +288,7 @@ static bool update_class_path_entry_list(const char *path, bool check_for_duplicates, bool is_boot_append, + bool from_class_path_attr, bool throw_exception=true); CDS_ONLY(static void update_module_path_entry_list(const char *path, TRAPS);) static void print_bootclasspath(); diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/share/classfile/classLoaderExt.cpp --- a/src/hotspot/share/classfile/classLoaderExt.cpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/share/classfile/classLoaderExt.cpp Fri Jun 28 09:49:10 2019 -0700 @@ -213,7 +213,7 @@ 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"); trace_class_path("library = ", libname); - ClassLoader::update_class_path_entry_list(libname, true, false); + ClassLoader::update_class_path_entry_list(libname, true, false, true /* from_class_path_attr */); } file_start = file_end; @@ -339,7 +339,7 @@ } ClassPathEntry* new_entry = NULL; - new_entry = create_class_path_entry(path, &st, false, false, CHECK_NULL); + new_entry = create_class_path_entry(path, &st, false, false, false, CHECK_NULL); if (new_entry == NULL) { return NULL; } diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/share/classfile/sharedPathsMiscInfo.cpp --- a/src/hotspot/share/classfile/sharedPathsMiscInfo.cpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/share/classfile/sharedPathsMiscInfo.cpp Fri Jun 28 09:49:10 2019 -0700 @@ -153,83 +153,10 @@ return true; } -char* skip_first_path_entry(const char* path) { - size_t path_sep_len = strlen(os::path_separator()); - char* p = strstr((char*)path, os::path_separator()); - if (p != NULL) { - debug_only( { - size_t image_name_len = strlen(MODULES_IMAGE_NAME); - assert(strncmp(p - image_name_len, MODULES_IMAGE_NAME, image_name_len) == 0, - "first entry must be the modules image"); - } ); - p += path_sep_len; - } else { - debug_only( { - assert(ClassLoader::string_ends_with(path, MODULES_IMAGE_NAME), - "first entry must be the modules image"); - } ); - } - return p; -} - bool SharedPathsMiscInfo::check(jint type, const char* path, bool is_static) { assert(UseSharedSpaces, "runtime only"); switch (type) { case BOOT_PATH: - { - // - // - Archive contains boot classes only - relaxed boot path check: - // Extra path elements appended to the boot path at runtime are allowed. - // - // - Archive contains application or platform classes - strict boot path check: - // Validate the entire runtime boot path, which must be compactible - // with the dump time boot path. Appending boot path at runtime is not - // allowed. - // - - // The first entry in boot path is the modules_image (guaranteed by - // ClassLoader::setup_boot_search_path()). Skip the first entry. The - // path of the runtime modules_image may be different from the dump - // time path (e.g. the JDK image is copied to a different location - // after generating the shared archive), which is acceptable. For most - // common cases, the dump time boot path might contain modules_image only. - char* runtime_boot_path = Arguments::get_sysclasspath(); - char* rp = skip_first_path_entry(runtime_boot_path); - char* dp = skip_first_path_entry(path); - - bool relaxed_check = is_static ? - !FileMapInfo::current_info()->header()->has_platform_or_app_classes() : - !FileMapInfo::dynamic_info()->header()->has_platform_or_app_classes(); - if (dp == NULL && rp == NULL) { - break; // ok, both runtime and dump time boot paths have modules_images only - } else if (dp == NULL && rp != NULL && relaxed_check) { - break; // ok, relaxed check, runtime has extra boot append path entries - } else if (dp != NULL && rp != NULL) { - size_t num; - size_t dp_len = strlen(dp); - size_t rp_len = strlen(rp); - if (rp_len >= dp_len) { - if (relaxed_check) { - // only check the leading entries in the runtime boot path, up to - // the length of the dump time boot path - num = dp_len; - } else { - // check the full runtime boot path, must match with dump time - num = rp_len; - } - - if (os::file_name_strncmp(dp, rp, num) == 0) { - // make sure it is the end of an entry in the runtime boot path - if (rp[dp_len] == '\0' || rp[dp_len] == os::path_separator()[0]) { - break; // ok, runtime and dump time paths match - } - } - } - } - - // The paths are different - return fail("[BOOT classpath mismatch, actual =", runtime_boot_path); - } break; case NON_EXIST: { @@ -242,22 +169,6 @@ } break; case APP_PATH: - { - size_t len = strlen(path); - const char *appcp = Arguments::get_appclasspath(); - assert(appcp != NULL, "NULL app classpath"); - size_t appcp_len = strlen(appcp); - if (appcp_len < len) { - return fail("Run time APP classpath is shorter than the one at dump time: ", appcp); - } - // Prefix is OK: E.g., dump with -cp foo.jar, but run with -cp foo.jar:bar.jar. - if (os::file_name_strncmp(path, appcp, len) != 0) { - return fail("[APP classpath mismatch, actual: -Djava.class.path=", appcp); - } - if (appcp[len] != '\0' && appcp[len] != os::path_separator()[0]) { - return fail("Dump time APP classpath is not a proper prefix of run time APP classpath: ", appcp); - } - } break; default: return fail("Corrupted archive file header"); diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/share/memory/filemap.cpp --- a/src/hotspot/share/memory/filemap.cpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/share/memory/filemap.cpp Fri Jun 28 09:49:10 2019 -0700 @@ -75,7 +75,7 @@ // an archive file should stop the process. Unrecoverable errors during // the reading of the archive file should stop the process. -static void fail(const char *msg, va_list ap) { +static void fail_exit(const char *msg, va_list ap) { // This occurs very early during initialization: tty is not initialized. jio_fprintf(defaultStream::error_stream(), "An error has occurred while processing the" @@ -90,7 +90,7 @@ void FileMapInfo::fail_stop(const char *msg, ...) { va_list ap; va_start(ap, msg); - fail(msg, ap); // Never returns. + fail_exit(msg, ap); // Never returns. va_end(ap); // for completeness. } @@ -118,7 +118,7 @@ tty->print_cr("]"); } else { if (RequireSharedSpaces) { - fail(msg, ap); + fail_exit(msg, ap); } else { if (log_is_enabled(Info, cds)) { ResourceMark rm; @@ -252,13 +252,15 @@ _base_archive_is_default = false; } -void SharedClassPathEntry::init(const char* name, bool is_modules_image, TRAPS) { +void SharedClassPathEntry::init(bool is_modules_image, + ClassPathEntry* cpe, TRAPS) { assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "dump time only"); _timestamp = 0; _filesize = 0; + _from_class_path_attr = false; struct stat st; - if (os::stat(name, &st) == 0) { + if (os::stat(cpe->name(), &st) == 0) { if ((st.st_mode & S_IFMT) == S_IFDIR) { _type = dir_entry; } else { @@ -268,6 +270,7 @@ } else { _type = jar_entry; _timestamp = st.st_mtime; + _from_class_path_attr = cpe->from_class_path_attr(); } _filesize = st.st_size; } @@ -277,12 +280,12 @@ // // If we can't access a jar file in the boot path, then we can't // make assumptions about where classes get loaded from. - FileMapInfo::fail_stop("Unable to open file %s.", name); + FileMapInfo::fail_stop("Unable to open file %s.", cpe->name()); } - size_t len = strlen(name) + 1; + size_t len = strlen(cpe->name()) + 1; _name = MetadataFactory::new_array(ClassLoaderData::the_null_class_loader_data(), (int)len, THREAD); - strcpy(_name->data(), name); + strcpy(_name->data(), cpe->name()); } bool SharedClassPathEntry::validate(bool is_class_path) { @@ -381,7 +384,7 @@ const char* type = (is_jrt ? "jrt" : (cpe->is_jar_file() ? "jar" : "dir")); log_info(class, path)("add main shared path (%s) %s", type, cpe->name()); SharedClassPathEntry* ent = shared_path(i); - ent->init(cpe->name(), is_jrt, THREAD); + ent->init(is_jrt, cpe, THREAD); if (!is_jrt) { // No need to do the modules image. EXCEPTION_MARK; // The following call should never throw, but would exit VM on error. update_shared_classpath(cpe, ent, THREAD); @@ -397,7 +400,7 @@ while (acpe != NULL) { log_info(class, path)("add app shared path %s", acpe->name()); SharedClassPathEntry* ent = shared_path(i); - ent->init(acpe->name(), false, THREAD); + ent->init(false, acpe, THREAD); EXCEPTION_MARK; update_shared_classpath(acpe, ent, THREAD); acpe = acpe->next(); @@ -409,7 +412,7 @@ while (mpe != NULL) { log_info(class, path)("add module path %s",mpe->name()); SharedClassPathEntry* ent = shared_path(i); - ent->init(mpe->name(), false, THREAD); + ent->init(false, mpe, THREAD); EXCEPTION_MARK; update_shared_classpath(mpe, ent, THREAD); mpe = mpe->next(); @@ -520,6 +523,182 @@ } } +char* FileMapInfo::skip_first_path_entry(const char* path) { + size_t path_sep_len = strlen(os::path_separator()); + char* p = strstr((char*)path, os::path_separator()); + if (p != NULL) { + debug_only( { + size_t image_name_len = strlen(MODULES_IMAGE_NAME); + assert(strncmp(p - image_name_len, MODULES_IMAGE_NAME, image_name_len) == 0, + "first entry must be the modules image"); + } ); + p += path_sep_len; + } else { + debug_only( { + assert(ClassLoader::string_ends_with(path, MODULES_IMAGE_NAME), + "first entry must be the modules image"); + } ); + } + return p; +} + +int FileMapInfo::num_paths(const char* path) { + if (path == NULL) { + return 0; + } + int npaths = 1; + char* p = (char*)path; + while (p != NULL) { + char* prev = p; + p = strstr((char*)p, os::path_separator()); + if (p != NULL) { + p++; + // don't count empty path + if ((p - prev) > 1) { + npaths++; + } + } + } + return npaths; +} + +GrowableArray* FileMapInfo::create_path_array(const char* path) { + GrowableArray* path_array = new(ResourceObj::RESOURCE_AREA, mtInternal) + GrowableArray(10); + char* begin_ptr = (char*)path; + char* end_ptr = strchr((char*)path, os::path_separator()[0]); + if (end_ptr == NULL) { + end_ptr = strchr((char*)path, '\0'); + } + while (end_ptr != NULL) { + if ((end_ptr - begin_ptr) > 1) { + struct stat st; + char* temp_name = NEW_RESOURCE_ARRAY(char, (size_t)(end_ptr - begin_ptr + 1)); + strncpy(temp_name, begin_ptr, end_ptr - begin_ptr); + temp_name[end_ptr - begin_ptr] = '\0'; + if (os::stat(temp_name, &st) == 0) { + path_array->append(temp_name); + } + } + if (end_ptr < (path + strlen(path))) { + begin_ptr = ++end_ptr; + end_ptr = strchr(begin_ptr, os::path_separator()[0]); + if (end_ptr == NULL) { + end_ptr = strchr(begin_ptr, '\0'); + } + } else { + break; + } + } + return path_array; +} + +bool FileMapInfo::fail(const char* msg, const char* name) { + ClassLoader::trace_class_path(msg, name); + MetaspaceShared::set_archive_loading_failed(); + return false; +} + +bool FileMapInfo::check_paths(int shared_path_start_idx, int num_paths, GrowableArray* rp_array) { + int i = 0; + int j = shared_path_start_idx; + bool mismatch = false; + while (i < num_paths && !mismatch) { + while (shared_path(j)->from_class_path_attr()) { + // shared_path(j) was expanded from the JAR file attribute "Class-Path:" + // during dump time. It's not included in the -classpath VM argument. + j++; + } + if (!os::same_files(shared_path(j)->name(), rp_array->at(i))) { + mismatch = true; + } + i++; + j++; + } + return mismatch; +} + +bool FileMapInfo::validate_boot_class_paths() { + // + // - Archive contains boot classes only - relaxed boot path check: + // Extra path elements appended to the boot path at runtime are allowed. + // + // - Archive contains application or platform classes - strict boot path check: + // Validate the entire runtime boot path, which must be compatible + // with the dump time boot path. Appending boot path at runtime is not + // allowed. + // + + // The first entry in boot path is the modules_image (guaranteed by + // ClassLoader::setup_boot_search_path()). Skip the first entry. The + // path of the runtime modules_image may be different from the dump + // time path (e.g. the JDK image is copied to a different location + // after generating the shared archive), which is acceptable. For most + // common cases, the dump time boot path might contain modules_image only. + char* runtime_boot_path = Arguments::get_sysclasspath(); + char* rp = skip_first_path_entry(runtime_boot_path); + assert(shared_path(0)->is_modules_image(), "first shared_path must be the modules image"); + int dp_len = _header->_app_class_paths_start_index - 1; // ignore the first path to the module image + bool mismatch = false; + + bool relaxed_check = !header()->has_platform_or_app_classes(); + if (dp_len == 0 && rp == NULL) { + return true; // ok, both runtime and dump time boot paths have modules_images only + } else if (dp_len == 0 && rp != NULL) { + if (relaxed_check) { + return true; // ok, relaxed check, runtime has extra boot append path entries + } else { + mismatch = true; + } + } else if (dp_len > 0 && rp != NULL) { + int num; + ResourceMark rm; + GrowableArray* rp_array = create_path_array(rp); + int rp_len = rp_array->length(); + if (rp_len >= dp_len) { + if (relaxed_check) { + // only check the leading entries in the runtime boot path, up to + // the length of the dump time boot path + num = dp_len; + } else { + // check the full runtime boot path, must match with dump time + num = rp_len; + } + mismatch = check_paths(1, num, rp_array); + } + } + + if (mismatch) { + // The paths are different + return fail("[BOOT classpath mismatch, actual =", runtime_boot_path); + } + return true; +} + +bool FileMapInfo::validate_app_class_paths(int shared_app_paths_len) { + const char *appcp = Arguments::get_appclasspath(); + assert(appcp != NULL, "NULL app classpath"); + int rp_len = num_paths(appcp); + bool mismatch = false; + if (rp_len < shared_app_paths_len) { + return fail("Run time APP classpath is shorter than the one at dump time: ", appcp); + } + if (shared_app_paths_len != 0 && rp_len != 0) { + // Prefix is OK: E.g., dump with -cp foo.jar, but run with -cp foo.jar:bar.jar. + ResourceMark rm; + GrowableArray* rp_array = create_path_array(appcp); + if (rp_array->length() == 0) { + // None of the jar file specified in the runtime -cp exists. + return fail("None of the jar file specified in the runtime -cp exists: -Djava.class.path=", appcp); + } + int j = _header->_app_class_paths_start_index; + mismatch = check_paths(j, shared_app_paths_len, rp_array); + if (mismatch) { + return fail("[APP classpath mismatch, actual: -Djava.class.path=", appcp); + } + } + return true; +} bool FileMapInfo::validate_shared_path_table() { assert(UseSharedSpaces, "runtime only"); @@ -550,11 +729,16 @@ } int module_paths_start_index = _header->_app_module_paths_start_index; + int shared_app_paths_len = 0; // validate the path entries up to the _max_used_path_index for (int i=0; i < _header->_max_used_path_index + 1; i++) { if (i < module_paths_start_index) { if (shared_path(i)->validate()) { + // Only count the app class paths not from the "Class-path" attribute of a jar manifest. + if (!shared_path(i)->from_class_path_attr() && i >= _header->_app_class_paths_start_index) { + shared_app_paths_len++; + } log_info(class, path)("ok"); } else { if (_dynamic_archive_info != NULL && _dynamic_archive_info->_is_static) { @@ -574,6 +758,16 @@ } } + if (_header->_max_used_path_index == 0) { + // default archive only contains the module image in the bootclasspath + assert(shared_path(0)->is_modules_image(), "first shared_path must be the modules image"); + } else { + if (!validate_boot_class_paths() || !validate_app_class_paths(shared_app_paths_len)) { + fail_continue("shared class paths mismatch (hint: enable -Xlog:class+path=info to diagnose the failure)"); + return false; + } + } + _validating_shared_path_table = false; #if INCLUDE_JVMTI @@ -588,39 +782,6 @@ return true; } -bool FileMapInfo::same_files(const char* file1, const char* file2) { - if (strcmp(file1, file2) == 0) { - return true; - } - - bool is_same = false; - // if the two paths diff only in case - struct stat st1; - struct stat st2; - int ret1; - int ret2; - ret1 = os::stat(file1, &st1); - ret2 = os::stat(file2, &st2); - if (ret1 < 0 || ret2 < 0) { - // one of the files is invalid. So they are not the same. - is_same = false; - } else if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) { - // different files - is_same = false; -#ifndef _WINDOWS - } else if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) { - // same files - is_same = true; -#else - } else if ((st1.st_size == st2.st_size) && (st1.st_ctime == st2.st_ctime) && - (st1.st_mtime == st2.st_mtime)) { - // same files - is_same = true; -#endif - } - return is_same; -} - bool FileMapInfo::check_archive(const char* archive_name, bool is_static) { int fd = os::open(archive_name, O_RDONLY | O_BINARY, 0); if (fd < 0) { @@ -1749,7 +1910,7 @@ jio_snprintf(msg, strlen(path) + 127, "error in opening JAR file %s", path); THROW_MSG_(vmSymbols::java_io_IOException(), msg, NULL); } else { - ent = ClassLoader::create_class_path_entry(path, &st, /*throw_exception=*/true, false, CHECK_NULL); + ent = ClassLoader::create_class_path_entry(path, &st, /*throw_exception=*/true, false, false, CHECK_NULL); } } diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/share/memory/filemap.hpp --- a/src/hotspot/share/memory/filemap.hpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/share/memory/filemap.hpp Fri Jun 28 09:49:10 2019 -0700 @@ -53,13 +53,14 @@ }; protected: u1 _type; + bool _from_class_path_attr; time_t _timestamp; // jar timestamp, 0 if is directory, modules image or other long _filesize; // jar/jimage file size, -1 if is directory, -2 if other Array* _name; Array* _manifest; public: - void init(const char* name, bool is_modules_image, TRAPS); + void init(bool is_modules_image, ClassPathEntry* cpe, TRAPS); void metaspace_pointers_do(MetaspaceClosure* it); bool validate(bool is_class_path = true); @@ -74,6 +75,7 @@ void set_is_signed() { _type = signed_jar_entry; } + bool from_class_path_attr() { return _from_class_path_attr; } time_t timestamp() const { return _timestamp; } long filesize() const { return _filesize; } const char* name() const { return _name->data(); } @@ -240,7 +242,6 @@ static bool get_base_archive_name_from_header(const char* archive_name, int* size, char** base_archive_name); static bool check_archive(const char* archive_name, bool is_static); - static bool same_files(const char* file1, const char* file2); void restore_shared_path_table(); bool init_from_file(int fd, bool is_static); static void metaspace_pointers_do(MetaspaceClosure* it); @@ -376,6 +377,15 @@ char* region_addr(int idx); private: + char* skip_first_path_entry(const char* path) NOT_CDS_RETURN_(NULL); + int num_paths(const char* path) NOT_CDS_RETURN_(0); + GrowableArray* create_path_array(const char* path) NOT_CDS_RETURN_(NULL); + bool fail(const char* msg, const char* name) NOT_CDS_RETURN_(FALSE); + bool check_paths(int shared_path_start_idx, + int num_paths, + GrowableArray* rp_array) NOT_CDS_RETURN_(FALSE); + bool validate_boot_class_paths() NOT_CDS_RETURN_(FALSE); + bool validate_app_class_paths(int shared_app_paths_len) NOT_CDS_RETURN_(FALSE); bool map_heap_data(MemRegion **heap_mem, int first, int max, int* num, bool is_open = false) NOT_CDS_JAVA_HEAP_RETURN_(false); bool region_crc_check(char* buf, size_t size, int expected_crc) NOT_CDS_RETURN_(false); diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/share/runtime/arguments.cpp --- a/src/hotspot/share/runtime/arguments.cpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/share/runtime/arguments.cpp Fri Jun 28 09:49:10 2019 -0700 @@ -3571,7 +3571,7 @@ "Cannot have more than 1 archive file specified in -XX:SharedArchiveFile during CDS dumping"); } if (DynamicDumpSharedSpaces) { - if (FileMapInfo::same_files(SharedArchiveFile, ArchiveClassesAtExit)) { + if (os::same_files(SharedArchiveFile, ArchiveClassesAtExit)) { vm_exit_during_initialization( "Cannot have the same archive file specified for -XX:SharedArchiveFile and -XX:ArchiveClassesAtExit", SharedArchiveFile); diff -r 52ef2c940423 -r b279ae9843b8 src/hotspot/share/runtime/os.hpp --- a/src/hotspot/share/runtime/os.hpp Fri Jun 28 18:01:36 2019 +0200 +++ b/src/hotspot/share/runtime/os.hpp Fri Jun 28 09:49:10 2019 -0700 @@ -171,10 +171,6 @@ init_globals_ext(); } - // File names are case-insensitive on windows only - // Override me as needed - static int file_name_strncmp(const char* s1, const char* s2, size_t num); - // unset environment variable static bool unsetenv(const char* name); @@ -544,6 +540,8 @@ static int compare_file_modified_times(const char* file1, const char* file2); + static bool same_files(const char* file1, const char* file2); + //File i/o operations static ssize_t read(int fd, void *buf, unsigned int nBytes); diff -r 52ef2c940423 -r b279ae9843b8 test/hotspot/jtreg/TEST.groups --- a/test/hotspot/jtreg/TEST.groups Fri Jun 28 18:01:36 2019 +0200 +++ b/test/hotspot/jtreg/TEST.groups Fri Jun 28 09:49:10 2019 -0700 @@ -328,6 +328,7 @@ -runtime/appcds/ExtraSymbols.java \ -runtime/appcds/LongClassListPath.java \ -runtime/appcds/LotsOfClasses.java \ + -runtime/appcds/RelativePath.java \ -runtime/appcds/SharedArchiveConsistency.java \ -runtime/appcds/UnusedCPDuringDump.java \ -runtime/appcds/VerifierTest_1B.java diff -r 52ef2c940423 -r b279ae9843b8 test/hotspot/jtreg/runtime/appcds/AppCDSOptions.java --- a/test/hotspot/jtreg/runtime/appcds/AppCDSOptions.java Fri Jun 28 18:01:36 2019 +0200 +++ b/test/hotspot/jtreg/runtime/appcds/AppCDSOptions.java Fri Jun 28 09:49:10 2019 -0700 @@ -28,9 +28,15 @@ public class AppCDSOptions extends CDSOptions { public String appJar; + public String appJarDir; public AppCDSOptions setAppJar(String appJar) { this.appJar = appJar; return this; } + + public AppCDSOptions setAppJarDir(String appJarDir) { + this.appJarDir = appJarDir; + return this; + } } diff -r 52ef2c940423 -r b279ae9843b8 test/hotspot/jtreg/runtime/appcds/AppendClasspath.java --- a/test/hotspot/jtreg/runtime/appcds/AppendClasspath.java Fri Jun 28 18:01:36 2019 +0200 +++ b/test/hotspot/jtreg/runtime/appcds/AppendClasspath.java Fri Jun 28 09:49:10 2019 -0700 @@ -36,6 +36,9 @@ */ import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import jdk.test.lib.process.OutputAnalyzer; public class AppendClasspath { @@ -53,9 +56,24 @@ "HelloMore") .assertNormalExit(); + // PASS: 2) runtime has an non-existing jar in the -cp + String classDir = System.getProperty("test.classes"); + String newFile = "non-exist.jar"; + String nonExistPath = classDir + File.separator + newFile; + String classPath = appJar + File.pathSeparator + nonExistPath; + File nonExistJar = new File(classDir, newFile); + if (nonExistJar.exists()) { + nonExistJar.delete(); + } + TestCommon.run( + "-cp", classPath, + "-Xlog:class+path=trace", + "Hello") + .assertNormalExit(); + final String errorMessage1 = "Unable to use shared archive"; final String errorMessage2 = "shared class paths mismatch"; - // FAIL: 2) runtime with classpath different from the one used in dump time + // FAIL: 1) runtime with classpath different from the one used in dump time // (runtime has an extra jar file prepended to the class path) TestCommon.run( "-Xlog:cds", @@ -63,7 +81,7 @@ "HelloMore") .assertAbnormalExit(errorMessage1, errorMessage2); - // FAIL: 3) runtime with classpath part of the one used in dump time + // FAIL: 2) runtime with classpath part of the one used in dump time TestCommon.testDump(appJar + File.pathSeparator + appJar2, TestCommon.list("Hello")); TestCommon.run( @@ -72,12 +90,26 @@ "Hello") .assertAbnormalExit(errorMessage1, errorMessage2); - // FAIL: 4) runtime with same set of jar files in the classpath but + // FAIL: 3) runtime with same set of jar files in the classpath but // with different order TestCommon.run( "-Xlog:cds", "-cp", appJar2 + File.pathSeparator + appJar, "HelloMore") .assertAbnormalExit(errorMessage1, errorMessage2); - } + + // FAIL: 4) non-existing jar during dump time but jar exists during runtime + TestCommon.testDump(classPath, TestCommon.list("Hello")); + + Files.copy(Paths.get(classDir, "hello.jar"), + Paths.get(classDir, newFile), + StandardCopyOption.REPLACE_EXISTING); + + TestCommon.run( + "-cp", classPath, + "-Xlog:class+path=trace", + "Hello") + .assertAbnormalExit(errorMessage1, errorMessage2); + + } } diff -r 52ef2c940423 -r b279ae9843b8 test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java --- a/test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java Fri Jun 28 18:01:36 2019 +0200 +++ b/test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java Fri Jun 28 09:49:10 2019 -0700 @@ -34,6 +34,7 @@ * @run driver BootClassPathMismatch */ +import jdk.test.lib.Platform; import jdk.test.lib.cds.CDSOptions; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.process.OutputAnalyzer; @@ -41,7 +42,9 @@ import java.nio.file.Files; import java.nio.file.FileAlreadyExistsException; import java.nio.file.StandardCopyOption; +import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; public class BootClassPathMismatch { @@ -126,6 +129,55 @@ "-cp", appJar, "-verbose:class", "-Xbootclasspath/a:" + appJar, "Hello") .assertNormalExit("[class,load] Hello source: shared objects file"); + + // test relative path to appJar + String newJar = TestCommon.composeRelPath(appJar); + TestCommon.run( + "-cp", newJar, "-verbose:class", + "-Xbootclasspath/a:" + newJar, "Hello") + .assertNormalExit("[class,load] Hello source: shared objects file"); + + int idx = appJar.lastIndexOf(File.separator); + String jarName = appJar.substring(idx + 1); + String jarDir = appJar.substring(0, idx); + // relative path starting with "." + TestCommon.runWithRelativePath( + jarDir, + "-Xshare:on", + "-XX:SharedArchiveFile=" + TestCommon.getCurrentArchiveName(), + "-cp", "." + File.separator + jarName, + "-Xbootclasspath/a:" + "." + File.separator + jarName, + "-Xlog:class+load=trace,class+path=info", + "Hello") + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldHaveExitValue(0); + }); + + // relative path starting with ".." + idx = jarDir.lastIndexOf(File.separator); + String jarSubDir = jarDir.substring(idx + 1); + TestCommon.runWithRelativePath( + jarDir, + "-Xshare:on", + "-XX:SharedArchiveFile=" + TestCommon.getCurrentArchiveName(), + "-cp", ".." + File.separator + jarSubDir + File.separator + jarName, + "-Xbootclasspath/a:" + ".." + File.separator + jarSubDir + File.separator + jarName, + "-Xlog:class+load=trace,class+path=info", + "Hello") + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldHaveExitValue(0); + }); + + // test sym link to appJar + if (!Platform.isWindows()) { + File linkedJar = TestCommon.createSymLink(appJar); + TestCommon.run( + "-cp", linkedJar.getPath(), "-verbose:class", + "-Xbootclasspath/a:" + linkedJar.getPath(), "Hello") + .assertNormalExit("[class,load] Hello source: shared objects file"); + } } /* Archive contains boot classes only, runtime add -Xbootclasspath/a path. @@ -158,6 +210,12 @@ "-Xlog:cds", "-cp", appJar, "-Xbootclasspath/a:" + appJar, "Hello") .assertAbnormalExit(mismatchMessage); + + // test relative path to appJar + String newJar = TestCommon.composeRelPath(appJar); + TestCommon.run( + "-cp", newJar, "-Xbootclasspath/a:" + newJar, "Hello") + .assertAbnormalExit(mismatchMessage); } private static void copyHelloToNewDir() throws Exception { @@ -168,13 +226,25 @@ } catch (FileAlreadyExistsException e) { } // copy as hello.jar + Path dstPath = Paths.get(dstDir, "hello.jar"); Files.copy(Paths.get(classDir, "hello.jar"), - Paths.get(dstDir, "hello.jar"), + dstPath, StandardCopyOption.REPLACE_EXISTING); + File helloJar = dstPath.toFile(); + long modTime = helloJar.lastModified(); + // copy as hello.jar1 + Path dstPath2 = Paths.get(dstDir, "hello.jar1"); Files.copy(Paths.get(classDir, "hello.jar"), - Paths.get(dstDir, "hello.jar1"), + dstPath2, StandardCopyOption.REPLACE_EXISTING); + + // On Windows, we rely on the file size, creation time, and + // modification time in order to differentiate between 2 files. + // Setting a different modification time on hello.jar1 so that this test + // runs more reliably on Windows. + modTime += 10000; + Files.setAttribute(dstPath2, "lastModifiedTime", FileTime.fromMillis(modTime)); } } diff -r 52ef2c940423 -r b279ae9843b8 test/hotspot/jtreg/runtime/appcds/PrintSharedArchiveAndExit.java --- a/test/hotspot/jtreg/runtime/appcds/PrintSharedArchiveAndExit.java Fri Jun 28 18:01:36 2019 +0200 +++ b/test/hotspot/jtreg/runtime/appcds/PrintSharedArchiveAndExit.java Fri Jun 28 09:49:10 2019 -0700 @@ -105,12 +105,13 @@ "-XX:+PrintSharedArchiveAndExit") .ifNoMappingFailure(output -> check(output, 1, true, lastCheckMsg, "Run time APP classpath is shorter than the one at dump time: .")); - log("Use an invalid App CP -- all the JAR paths should be checked"); + log("Use an invalid App CP -- all the JAR paths should be checked.\n" + + "Non-existing jar files will be ignored."); String invalidCP = "non-existing-dir" + File.pathSeparator + cp; TestCommon.run( "-cp", invalidCP, "-XX:+PrintSharedArchiveAndExit") - .ifNoMappingFailure(output -> check(output, 1, true, lastCheckMsg, "APP classpath mismatch, actual: -Djava.class.path=" + invalidCP)); + .ifNoMappingFailure(output -> check(output, 0, true, lastCheckMsg)); log("Changed modification time of hello.jar -- all the JAR paths should be checked"); (new File(appJar)).setLastModified(System.currentTimeMillis() + 2000); diff -r 52ef2c940423 -r b279ae9843b8 test/hotspot/jtreg/runtime/appcds/RelativePath.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/RelativePath.java Fri Jun 28 09:49:10 2019 -0700 @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2019, 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. + * + */ + +/* + * @test + * @summary Test relative paths specified in the -cp. + * @requires vm.cds + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * jdk.jartool/sun.tools.jar + * @compile test-classes/Hello.java + * @compile test-classes/HelloMore.java + * @run driver RelativePath + */ + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import java.util.Arrays; +import jdk.test.lib.Platform; + +public class RelativePath { + + private static final Path USER_DIR = Paths.get(System.getProperty("user.dir")); + + public static void main(String[] args) throws Exception { + String appJar = JarBuilder.getOrCreateHelloJar(); + String appJar2 = JarBuilder.build("AppendClasspath_HelloMore", "HelloMore"); + + // dump an archive with only the jar name in the -cp + int idx = appJar.lastIndexOf(File.separator); + String jarName = appJar.substring(idx + 1); + String jarDir = appJar.substring(0, idx); + TestCommon.testDump(jarDir, jarName, TestCommon.list("Hello")); + + // copy the jar file to another dir. Specify the jar file without + // a directory path. + Path srcPath = Paths.get(appJar); + Path destDir = Files.createTempDirectory(USER_DIR, "deploy"); + Path destPath = destDir.resolve(jarName); + Files.copy(srcPath, destPath, REPLACE_EXISTING, COPY_ATTRIBUTES); + TestCommon.runWithRelativePath( + destDir.toString(), + "-Xshare:on", + "-XX:SharedArchiveFile=" + TestCommon.getCurrentArchiveName(), + "-cp", jarName + File.pathSeparator + appJar2, + "-Xlog:class+load=trace,class+path=info", + "HelloMore") + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldHaveExitValue(0); + }); + + // Long path test + // Create a long directory path and copy the appJar there. + final int MAX_PATH = 260; + destDir = Paths.get(jarDir); + int subDirLen = MAX_PATH - jarDir.length() - 3; + if (subDirLen > 0) { + char[] chars = new char[subDirLen]; + Arrays.fill(chars, 'x'); + String subPath = new String(chars); + destDir = Paths.get(jarDir, subPath); + } + File longDir = destDir.toFile(); + longDir.mkdir(); + String destJar = longDir.getPath() + File.separator + jarName; + Files.copy(Paths.get(appJar), Paths.get(destJar), REPLACE_EXISTING); + // Create an archive with the appJar in the long directory path. + TestCommon.testDump(destJar, TestCommon.list("Hello")); + + // Run with -cp containing the appJar and another jar appended. + TestCommon.run( + "-cp", destJar + File.pathSeparator + appJar2, + "-Xlog:class+load=trace,class+path=info", + "HelloMore") + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldHaveExitValue(0); + }); + + // Dump an archive with a specified JAR file in -classpath + TestCommon.testDump(appJar, TestCommon.list("Hello")); + + // compose a relative path to the hello.jar + String newHello = TestCommon.composeRelPath(appJar); + + // create a sym link to the original hello.jar + File linkedHello = null; + if (!Platform.isWindows()) { + linkedHello = TestCommon.createSymLink(appJar); + } + + // PASS:1) same appJar but referred to via a relative path + TestCommon.run( + "-cp", newHello + File.pathSeparator + appJar2, + "-Xlog:class+load=trace,class+path=info", + "HelloMore") + .assertNormalExit(); + + // PASS:2) relative path starting with "." + TestCommon.runWithRelativePath( + jarDir, + "-Xshare:on", + "-XX:SharedArchiveFile=" + TestCommon.getCurrentArchiveName(), + "-cp", "." + File.separator + jarName + File.pathSeparator + appJar2, + "-Xlog:class+load=trace,class+path=info", + "HelloMore") + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldHaveExitValue(0); + }); + + // PASS:3) relative path starting with ".." + idx = jarDir.lastIndexOf(File.separator); + String jarSubDir = jarDir.substring(idx + 1); + TestCommon.runWithRelativePath( + jarDir, + "-Xshare:on", + "-XX:SharedArchiveFile=" + TestCommon.getCurrentArchiveName(), + "-cp", ".." + File.separator + jarSubDir + File.separator + jarName + + File.pathSeparator + appJar2, + "-Xlog:class+load=trace,class+path=info", + "HelloMore") + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldHaveExitValue(0); + }); + + // PASS:4) a jar linked to the original hello.jar + if (!Platform.isWindows()) { + TestCommon.run( + "-cp", linkedHello.getPath() + File.pathSeparator + appJar2, + "HelloMore") + .assertNormalExit(); + } + + final String errorMessage1 = "Unable to use shared archive"; + final String errorMessage2 = "shared class paths mismatch"; + // FAIL: 1) runtime with classpath different from the one used in dump time + // (runtime has an extra jar file prepended to the class path) + TestCommon.run( + "-cp", appJar2 + File.pathSeparator + newHello, + "HelloMore") + .assertAbnormalExit(errorMessage1, errorMessage2); + + } +} diff -r 52ef2c940423 -r b279ae9843b8 test/hotspot/jtreg/runtime/appcds/TestCommon.java --- a/test/hotspot/jtreg/runtime/appcds/TestCommon.java Fri Jun 28 18:01:36 2019 +0200 +++ b/test/hotspot/jtreg/runtime/appcds/TestCommon.java Fri Jun 28 09:49:10 2019 -0700 @@ -128,6 +128,10 @@ return createArchive(appJar, classList, suffix); } + public static OutputAnalyzer dump(String appJarDir, String appJar, String classList[], + String... suffix) throws Exception { + return createArchive(appJarDir, appJar, classList, suffix); + } // Create AppCDS archive using most common args - convenience method public static OutputAnalyzer createArchive(String appJar, String classList[], @@ -138,6 +142,15 @@ return createArchive(opts); } + public static OutputAnalyzer createArchive(String appJarDir, String appJar, String classList[], + String... suffix) throws Exception { + AppCDSOptions opts = (new AppCDSOptions()).setAppJar(appJar); + opts.setAppJarDir(appJarDir); + opts.setClassList(classList); + opts.addSuffix(suffix); + return createArchive(opts); + } + // Simulate -Xshare:dump with -XX:ArchiveClassesAtExit. See comments around patchJarForDynamicDump() private static final Class tmp = DynamicDumpHelper.class; @@ -222,6 +235,9 @@ String[] cmdLine = cmd.toArray(new String[cmd.size()]); ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); + if (opts.appJarDir != null) { + pb.directory(new File(opts.appJarDir)); + } return executeAndLog(pb, "dump"); } @@ -360,6 +376,9 @@ String[] cmdLine = cmd.toArray(new String[cmd.size()]); ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); + if (opts.appJarDir != null) { + pb.directory(new File(opts.appJarDir)); + } return executeAndLog(pb, "exec"); } @@ -378,6 +397,13 @@ return new Result(opts, runWithArchive(opts)); } + public static Result runWithRelativePath(String jarDir, String... suffix) throws Exception { + AppCDSOptions opts = (new AppCDSOptions()); + opts.setAppJarDir(jarDir); + opts.addSuffix(suffix); + return new Result(opts, runWithArchive(opts)); + } + public static OutputAnalyzer exec(String appJar, String... suffix) throws Exception { AppCDSOptions opts = (new AppCDSOptions()).setAppJar(appJar); opts.addSuffix(suffix); @@ -443,6 +469,20 @@ return output; } + public static OutputAnalyzer testDump(String appJarDir, String appJar, String classList[], + String... suffix) throws Exception { + OutputAnalyzer output = dump(appJarDir, appJar, classList, suffix); + if (DYNAMIC_DUMP) { + if (isUnableToMap(output)) { + throw new SkippedException(UnableToMapMsg); + } + output.shouldContain("Written dynamic archive"); + } else { + output.shouldContain("Loading classes to share"); + } + output.shouldHaveExitValue(0); + return output; + } /** * Simple test -- dump and execute appJar with the given classList in classlist. @@ -590,4 +630,32 @@ } } } + + public static String composeRelPath(String appJar) { + int idx = appJar.lastIndexOf(File.separator); + String jarName = appJar.substring(idx + 1); + String jarDir = appJar.substring(0, idx); + String lastDir = jarDir.substring(jarDir.lastIndexOf(File.separator)); + String relPath = jarDir + File.separator + ".." + File.separator + lastDir; + String newJar = relPath + File.separator + jarName; + return newJar; + } + + + public static File createSymLink(String appJar) throws Exception { + int idx = appJar.lastIndexOf(File.separator); + String jarName = appJar.substring(idx + 1); + String jarDir = appJar.substring(0, idx); + File origJar = new File(jarDir, jarName); + String linkedJarName = "linked_" + jarName; + File linkedJar = null; + if (!Platform.isWindows()) { + linkedJar = new File(jarDir, linkedJarName); + if (linkedJar.exists()) { + linkedJar.delete(); + } + Files.createSymbolicLink(linkedJar.toPath(), origJar.toPath()); + } + return linkedJar; + } } diff -r 52ef2c940423 -r b279ae9843b8 test/hotspot/jtreg/runtime/appcds/dynamicArchive/DynamicArchiveTestBase.java --- a/test/hotspot/jtreg/runtime/appcds/dynamicArchive/DynamicArchiveTestBase.java Fri Jun 28 18:01:36 2019 +0200 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/DynamicArchiveTestBase.java Fri Jun 28 09:49:10 2019 -0700 @@ -108,7 +108,7 @@ cmdLine = TestCommon.concat(cmdLine, "-XX:SharedArchiveFile=" + baseArchiveName); } cmdLine = TestCommon.concat(cmdLine, cmdLineSuffix); - return execProcess("dump", cmdLine); + return execProcess("dump", null, cmdLine); } public static Result dump2_WB(String baseArchiveName, String topArchiveName, String ... cmdLineSuffix) @@ -188,7 +188,23 @@ "-Xshare:on", "-XX:SharedArchiveFile=" + archiveFiles); cmdLine = TestCommon.concat(cmdLine, cmdLineSuffix); - return execProcess("exec", cmdLine); + return execProcess("exec", null, cmdLine); + } + + public static Result runWithRelativePath(String baseArchiveName, String topArchiveName, + String jarDir, String ... cmdLineSuffix) + throws Exception { + if (baseArchiveName == null && topArchiveName == null) { + throw new RuntimeException("Both baseArchiveName and topArchiveName cannot be null at the same time."); + } + String archiveFiles = (baseArchiveName == null) ? topArchiveName : + (topArchiveName == null) ? baseArchiveName : + baseArchiveName + File.pathSeparator + topArchiveName; + String[] cmdLine = TestCommon.concat( + "-Xshare:on", + "-XX:SharedArchiveFile=" + archiveFiles); + cmdLine = TestCommon.concat(cmdLine, cmdLineSuffix); + return execProcess("exec", jarDir, cmdLine); } public static Result run2_WB(String baseArchiveName, String topArchiveName, String ... cmdLineSuffix) @@ -221,11 +237,14 @@ } - private static Result execProcess(String mode, String[] cmdLine) throws Exception { + private static Result execProcess(String mode, String jarDir, String[] cmdLine) throws Exception { if (!executedIn_run) { throw new Exception("Test error: dynamic archive tests must be executed via DynamicArchiveTestBase.run()"); } ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); + if (jarDir != null) { + pb.directory(new File(jarDir)); + } OutputAnalyzer output = TestCommon.executeAndLog(pb, mode); CDSOptions opts = new CDSOptions(); String xShareMode = getXshareMode(cmdLine); diff -r 52ef2c940423 -r b279ae9843b8 test/hotspot/jtreg/runtime/appcds/dynamicArchive/RelativePath.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/RelativePath.java Fri Jun 28 09:49:10 2019 -0700 @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019, 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. + * + */ + +/* + * @test + * @summary Test relative paths specified in the -cp. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds + * @modules java.base/jdk.internal.misc + * java.management + * jdk.jartool/sun.tools.jar + * @compile ../test-classes/Hello.java + * @compile ../test-classes/HelloMore.java + * @run driver RelativePath + */ + +import java.io.File; + +public class RelativePath extends DynamicArchiveTestBase { + + public static void main(String[] args) throws Exception { + runTest(AppendClasspath::testDefaultBase); + } + + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + doTest(topArchiveName); + } + + private static void doTest(String topArchiveName) throws Exception { + String appJar = JarBuilder.getOrCreateHelloJar(); + String appJar2 = JarBuilder.build("AppendClasspath_HelloMore", "HelloMore"); + + int idx = appJar.lastIndexOf(File.separator); + String jarName = appJar.substring(idx + 1); + String jarDir = appJar.substring(0, idx); + // relative path starting with "." + runWithRelativePath(null, topArchiveName, jarDir, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", "." + File.separator + "hello.jar" + File.pathSeparator + appJar2, + "HelloMore") + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldContain("Hello World ... More") + .shouldHaveExitValue(0); + }); + + // relative path starting with ".." + idx = jarDir.lastIndexOf(File.separator); + String jarSubDir = jarDir.substring(idx + 1); + runWithRelativePath(null, topArchiveName, jarDir, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", + ".." + File.separator + jarSubDir + File.separator + "hello.jar" + File.pathSeparator + appJar2, + "HelloMore") + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldContain("Hello World ... More") + .shouldHaveExitValue(0); + }); + + } +}