# HG changeset patch # User jiangli # Date 1526930158 14400 # Node ID 83d8b3a25f25d20d061b89d4de7dbbb2fc496b3a # Parent db3c6bb7f856c6bab4be5427deccea442071f1d0 8199807: AppCDS performs overly restrictive path matching check. 8203377: Cleanup the usage of os::file_name_strcmp() in SharedPathsMiscInfo::check(). Summary: Relax CDS/AppCDS path check for modules image. Reviewed-by: iklam, ccheung diff -r db3c6bb7f856 -r 83d8b3a25f25 src/hotspot/os/aix/os_aix.inline.hpp --- a/src/hotspot/os/aix/os_aix.inline.hpp Mon May 21 11:43:57 2018 -0700 +++ b/src/hotspot/os/aix/os_aix.inline.hpp Mon May 21 15:15:58 2018 -0400 @@ -1,6 +1,6 @@ /* - * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2016 SAP SE. All rights reserved. + * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2018 SAP SE. 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 @@ -36,9 +36,9 @@ #include #include -// File names are case-sensitive on windows only. -inline int os::file_name_strcmp(const char* s1, const char* s2) { - return strcmp(s1, s2); +// 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::obsolete_option(const JavaVMOption *option) { diff -r db3c6bb7f856 -r 83d8b3a25f25 src/hotspot/os/bsd/os_bsd.inline.hpp --- a/src/hotspot/os/bsd/os_bsd.inline.hpp Mon May 21 11:43:57 2018 -0700 +++ b/src/hotspot/os/bsd/os_bsd.inline.hpp Mon May 21 15:15:58 2018 -0400 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2018, 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 @@ -34,9 +34,9 @@ #include #include -// File names are case-sensitive on windows only -inline int os::file_name_strcmp(const char* s1, const char* s2) { - return strcmp(s1, s2); +// 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::obsolete_option(const JavaVMOption *option) { diff -r db3c6bb7f856 -r 83d8b3a25f25 src/hotspot/os/linux/os_linux.inline.hpp --- a/src/hotspot/os/linux/os_linux.inline.hpp Mon May 21 11:43:57 2018 -0700 +++ b/src/hotspot/os/linux/os_linux.inline.hpp Mon May 21 15:15:58 2018 -0400 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2018, 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 @@ -34,9 +34,9 @@ #include #include -// File names are case-sensitive on windows only -inline int os::file_name_strcmp(const char* s1, const char* s2) { - return strcmp(s1, s2); +// 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::obsolete_option(const JavaVMOption *option) { diff -r db3c6bb7f856 -r 83d8b3a25f25 src/hotspot/os/solaris/os_solaris.inline.hpp --- a/src/hotspot/os/solaris/os_solaris.inline.hpp Mon May 21 11:43:57 2018 -0700 +++ b/src/hotspot/os/solaris/os_solaris.inline.hpp Mon May 21 15:15:58 2018 -0400 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2018, 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 @@ -37,9 +37,9 @@ #include #include -// File names are case-sensitive on windows only -inline int os::file_name_strcmp(const char* s1, const char* s2) { - return strcmp(s1, s2); +// 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() { diff -r db3c6bb7f856 -r 83d8b3a25f25 src/hotspot/os/windows/os_windows.inline.hpp --- a/src/hotspot/os/windows/os_windows.inline.hpp Mon May 21 11:43:57 2018 -0700 +++ b/src/hotspot/os/windows/os_windows.inline.hpp Mon May 21 15:15:58 2018 -0400 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2018, 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 @@ -33,8 +33,8 @@ 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_strcmp(const char* s, const char* t) { - return _stricmp(s, t); +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) { diff -r db3c6bb7f856 -r 83d8b3a25f25 src/hotspot/share/classfile/sharedPathsMiscInfo.cpp --- a/src/hotspot/share/classfile/sharedPathsMiscInfo.cpp Mon May 21 11:43:57 2018 -0700 +++ b/src/hotspot/share/classfile/sharedPathsMiscInfo.cpp Mon May 21 15:15:58 2018 -0400 @@ -28,6 +28,7 @@ #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" +#include "memory/filemap.hpp" #include "memory/metaspaceShared.hpp" #include "memory/resourceArea.hpp" #include "runtime/arguments.hpp" @@ -140,12 +141,80 @@ 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) { + assert(UseSharedSpaces, "runtime only"); switch (type) { case BOOT_PATH: - // In the future we should perform the check based on the content of the mapped archive. - if (os::file_name_strcmp(path, Arguments::get_sysclasspath()) != 0) { - return fail("[BOOT classpath mismatch, actual =", Arguments::get_sysclasspath()); + { + // + // - 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 = !FileMapInfo::current_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: @@ -160,7 +229,6 @@ break; case APP_PATH: { - // Prefix is OK: E.g., dump with -cp foo.jar, but run with -cp foo.jar:bar.jar size_t len = strlen(path); const char *appcp = Arguments::get_appclasspath(); assert(appcp != NULL, "NULL app classpath"); @@ -168,16 +236,8 @@ if (appcp_len < len) { return fail("Run time APP classpath is shorter than the one at dump time: ", appcp); } - ResourceMark rm; - char* tmp_path; - if (len == appcp_len) { - tmp_path = (char*)appcp; - } else { - tmp_path = NEW_RESOURCE_ARRAY(char, len + 1); - strncpy(tmp_path, appcp, len); - tmp_path[len] = 0; - } - if (os::file_name_strcmp(path, tmp_path) != 0) { + // 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]) { diff -r db3c6bb7f856 -r 83d8b3a25f25 src/hotspot/share/memory/filemap.cpp --- a/src/hotspot/share/memory/filemap.cpp Mon May 21 11:43:57 2018 -0700 +++ b/src/hotspot/share/memory/filemap.cpp Mon May 21 15:15:58 2018 -0400 @@ -205,17 +205,23 @@ _has_platform_or_app_classes = ClassLoaderExt::has_platform_or_app_classes(); } -void SharedClassPathEntry::init(const char* name, TRAPS) { +void SharedClassPathEntry::init(const char* name, bool is_modules_image, TRAPS) { + assert(DumpSharedSpaces, "dump time only"); _timestamp = 0; _filesize = 0; struct stat st; if (os::stat(name, &st) == 0) { if ((st.st_mode & S_IFMT) == S_IFDIR) { - _is_dir = true; + _type = dir_entry; } else { - _is_dir = false; - _timestamp = st.st_mtime; + // The timestamp of the modules_image is not checked at runtime. + if (is_modules_image) { + _type = modules_image_entry; + } else { + _type = jar_entry; + _timestamp = st.st_mtime; + } _filesize = st.st_size; } } else { @@ -236,7 +242,18 @@ assert(UseSharedSpaces, "runtime only"); struct stat st; - const char* name = this->name(); + const char* name; + + // In order to validate the runtime modules image file size against the archived + // size information, we need to obtain the runtime modules image path. The recorded + // dump time modules image path in the archive may be different from the runtime path + // if the JDK image has beed moved after generating the archive. + if (is_modules_image()) { + name = ClassLoader::get_jrt_entry()->name(); + } else { + name = this->name(); + } + bool ok = true; log_info(class, path)("checking shared classpath entry: %s", name); if (os::stat(name, &st) != 0 && is_class_path) { @@ -251,18 +268,16 @@ FileMapInfo::fail_continue("directory is not empty: %s", name); ok = false; } - } else if (is_jar_or_bootimage()) { - if (_timestamp != st.st_mtime || - _filesize != st.st_size) { - ok = false; - if (PrintSharedArchiveAndExit) { - FileMapInfo::fail_continue(_timestamp != st.st_mtime ? - "Timestamp mismatch" : - "File size mismatch"); - } else { - FileMapInfo::fail_continue("A jar/jimage file is not the one used while building" - " the shared archive file: %s", name); - } + } else if ((has_timestamp() && _timestamp != st.st_mtime) || + _filesize != st.st_size) { + ok = false; + if (PrintSharedArchiveAndExit) { + FileMapInfo::fail_continue(_timestamp != st.st_mtime ? + "Timestamp mismatch" : + "File size mismatch"); + } else { + FileMapInfo::fail_continue("A jar file is not the one used while building" + " the shared archive file: %s", name); } } return ok; @@ -298,11 +313,12 @@ int i = 0; ClassPathEntry* cpe = jrt; while (cpe != NULL) { - const char* type = ((cpe == jrt) ? "jrt" : (cpe->is_jar_file() ? "jar" : "dir")); + bool is_jrt = (cpe == jrt); + 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(), THREAD); - if (cpe != jrt) { // No need to do jimage. + ent->init(cpe->name(), is_jrt, 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); } @@ -317,7 +333,7 @@ while (acpe != NULL) { log_info(class, path)("add app shared path %s", acpe->name()); SharedClassPathEntry* ent = shared_path(i); - ent->init(acpe->name(), THREAD); + ent->init(acpe->name(), false, THREAD); EXCEPTION_MARK; update_shared_classpath(acpe, ent, THREAD); acpe = acpe->next(); @@ -329,7 +345,7 @@ while (mpe != NULL) { log_info(class, path)("add module path %s",mpe->name()); SharedClassPathEntry* ent = shared_path(i); - ent->init(mpe->name(), THREAD); + ent->init(mpe->name(), false, THREAD); EXCEPTION_MARK; update_shared_classpath(mpe, ent, THREAD); mpe = mpe->next(); @@ -417,16 +433,15 @@ ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data(); ResourceMark rm(THREAD); jint manifest_size; - bool isSigned; if (cpe->is_jar_file()) { + assert(ent->is_jar(), "the shared class path entry is not a JAR file"); char* manifest = ClassLoaderExt::read_manifest(cpe, &manifest_size, CHECK); if (manifest != NULL) { ManifestStream* stream = new ManifestStream((u1*)manifest, manifest_size); - isSigned = stream->check_is_signed(); - if (isSigned) { - ent->set_is_signed(true); + if (stream->check_is_signed()) { + ent->set_is_signed(); } else { // Copy the manifest into the shared archive manifest = ClassLoaderExt::read_raw_manifest(cpe, &manifest_size, CHECK); @@ -436,7 +451,6 @@ char* p = (char*)(buf->data()); memcpy(p, manifest, manifest_size); ent->set_manifest(buf); - ent->set_is_signed(false); } } } diff -r db3c6bb7f856 -r 83d8b3a25f25 src/hotspot/share/memory/filemap.hpp --- a/src/hotspot/share/memory/filemap.hpp Mon May 21 11:43:57 2018 -0700 +++ b/src/hotspot/share/memory/filemap.hpp Mon May 21 15:15:58 2018 -0400 @@ -43,25 +43,36 @@ static const int JVM_IDENT_MAX = 256; class SharedClassPathEntry { + enum { + modules_image_entry, + jar_entry, + signed_jar_entry, + dir_entry, + unknown_entry + }; protected: - bool _is_dir; - time_t _timestamp; // jar/jimage timestamp, 0 if is directory or other + u1 _type; + 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; - bool _is_signed; public: - void init(const char* name, TRAPS); + void init(const char* name, bool is_modules_image, TRAPS); void metaspace_pointers_do(MetaspaceClosure* it); bool validate(bool is_class_path = true); - // The _timestamp only gets set for jar files and "modules" jimage. - bool is_jar_or_bootimage() { + // The _timestamp only gets set for jar files. + bool has_timestamp() { return _timestamp != 0; } - bool is_dir() { return _is_dir; } - bool is_modules_image() { return ClassLoader::is_modules_image(name()); } + bool is_dir() { return _type == dir_entry; } + bool is_modules_image() { return _type == modules_image_entry; } + bool is_jar() { return _type == jar_entry; } + bool is_signed() { return _type == signed_jar_entry; } + void set_is_signed() { + _type = signed_jar_entry; + } time_t timestamp() const { return _timestamp; } long filesize() const { return _filesize; } const char* name() const { return _name->data(); } @@ -74,12 +85,6 @@ void set_manifest(Array* manifest) { _manifest = manifest; } - void set_is_signed(bool s) { - _is_signed = s; - } - bool is_signed() { - return _is_signed; - } }; class FileMapInfo : public CHeapObj { diff -r db3c6bb7f856 -r 83d8b3a25f25 src/hotspot/share/runtime/os.hpp --- a/src/hotspot/share/runtime/os.hpp Mon May 21 11:43:57 2018 -0700 +++ b/src/hotspot/share/runtime/os.hpp Mon May 21 15:15:58 2018 -0400 @@ -167,7 +167,7 @@ // File names are case-insensitive on windows only // Override me as needed - static int file_name_strcmp(const char* s1, const char* s2); + static int file_name_strncmp(const char* s1, const char* s2, size_t num); // unset environment variable static bool unsetenv(const char* name); diff -r db3c6bb7f856 -r 83d8b3a25f25 test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java --- a/test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java Mon May 21 11:43:57 2018 -0700 +++ b/test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java Mon May 21 15:15:58 2018 -0400 @@ -34,6 +34,8 @@ * @run main BootClassPathMismatch */ +import jdk.test.lib.cds.CDSOptions; +import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.process.OutputAnalyzer; import java.io.File; import java.nio.file.Files; @@ -51,40 +53,60 @@ BootClassPathMismatch test = new BootClassPathMismatch(); test.testBootClassPathMismatch(); - test.testBootClassPathMismatch2(); + test.testBootClassPathMismatchWithAppClass(); + test.testBootClassPathMismatchWithBadPath(); + test.testBootClassPathMatchWithAppend(); test.testBootClassPathMatch(); } - /* Error should be detected if: + /* Archive contains boot classes only, with Hello class on -Xbootclasspath/a path. + * + * Error should be detected if: * dump time: -Xbootclasspath/a:${testdir}/hello.jar * run-time : -Xbootclasspath/a:${testdir}/newdir/hello.jar + * + * or + * dump time: -Xbootclasspath/a:${testdir}/newdir/hello.jar + * run-time : -Xbootclasspath/a:${testdir}/hello.jar */ public void testBootClassPathMismatch() throws Exception { String appJar = JarBuilder.getOrCreateHelloJar(); String appClasses[] = {"Hello"}; - TestCommon.dump( - appJar, appClasses, "-Xbootclasspath/a:" + appJar); String testDir = TestCommon.getTestDir("newdir"); String otherJar = testDir + File.separator + "hello.jar"; + + TestCommon.dump(appJar, appClasses, "-Xbootclasspath/a:" + appJar); TestCommon.run( - "-cp", appJar, "-verbose:class", "-Xbootclasspath/a:" + otherJar, "Hello") + "-cp", appJar, "-Xbootclasspath/a:" + otherJar, "Hello") + .assertAbnormalExit(mismatchMessage); + + TestCommon.dump(appJar, appClasses, "-Xbootclasspath/a:" + otherJar); + TestCommon.run( + "-cp", appJar, "-Xbootclasspath/a:" + appJar, "Hello") .assertAbnormalExit(mismatchMessage); } - /* Error should be detected if: - * dump time: - * run-time : -Xbootclasspath/a:${testdir}/hello.jar + /* Archive contains boot classes only. + * + * Error should be detected if: + * dump time: -Xbootclasspath/a:${testdir}/newdir/hello.jar + * run-time : -Xbootclasspath/a:${testdir}/newdir/hello.jar1 */ - public void testBootClassPathMismatch2() throws Exception { - String appJar = JarBuilder.getOrCreateHelloJar(); + public void testBootClassPathMismatchWithBadPath() throws Exception { String appClasses[] = {"Hello"}; - TestCommon.dump(appJar, appClasses); + String testDir = TestCommon.getTestDir("newdir"); + String appJar = testDir + File.separator + "hello.jar"; + String otherJar = testDir + File.separator + "hello.jar1"; + + TestCommon.dump(appJar, appClasses, "-Xbootclasspath/a:" + appJar); TestCommon.run( - "-cp", appJar, "-verbose:class", "-Xbootclasspath/a:" + appJar, "Hello") + "-cp", appJar, "-Xbootclasspath/a:" + otherJar, "Hello") .assertAbnormalExit(mismatchMessage); } - /* No error if: + /* Archive contains boot classes only, with Hello loaded from -Xbootclasspath/a at dump time. + * + * No error if: * dump time: -Xbootclasspath/a:${testdir}/hello.jar * run-time : -Xbootclasspath/a:${testdir}/hello.jar */ @@ -99,6 +121,37 @@ .assertNormalExit("[class,load] Hello source: shared objects file"); } + /* Archive contains boot classes only, runtime add -Xbootclasspath/a path. + * + * No error: + * dump time: No -Xbootclasspath/a + * run-time : -Xbootclasspath/a:${testdir}/hello.jar + */ + public void testBootClassPathMatchWithAppend() throws Exception { + CDSOptions opts = new CDSOptions().setUseVersion(false); + OutputAnalyzer out = CDSTestUtils.createArchive(opts); + CDSTestUtils.checkDump(out); + + String appJar = JarBuilder.getOrCreateHelloJar(); + opts.addPrefix("-Xbootclasspath/a:" + appJar, "-showversion").addSuffix("Hello"); + CDSTestUtils.runWithArchiveAndCheck(opts); + } + + /* Archive contains app classes, with Hello on -cp path at dump time. + * + * Error should be detected if: + * dump time: + * run-time : -Xbootclasspath/a:${testdir}/hello.jar + */ + public void testBootClassPathMismatchWithAppClass() throws Exception { + String appJar = JarBuilder.getOrCreateHelloJar(); + String appClasses[] = {"Hello"}; + TestCommon.dump(appJar, appClasses); + TestCommon.run( + "-cp", appJar, "-Xbootclasspath/a:" + appJar, "Hello") + .assertAbnormalExit(mismatchMessage); + } + private static void copyHelloToNewDir() throws Exception { String classDir = System.getProperty("test.classes"); String dstDir = classDir + File.separator + "newdir"; @@ -106,8 +159,14 @@ Files.createDirectory(Paths.get(dstDir)); } catch (FileAlreadyExistsException e) { } + // copy as hello.jar Files.copy(Paths.get(classDir, "hello.jar"), Paths.get(dstDir, "hello.jar"), StandardCopyOption.REPLACE_EXISTING); + + // copy as hello.jar1 + Files.copy(Paths.get(classDir, "hello.jar"), + Paths.get(dstDir, "hello.jar1"), + StandardCopyOption.REPLACE_EXISTING); } } diff -r db3c6bb7f856 -r 83d8b3a25f25 test/hotspot/jtreg/runtime/appcds/MoveJDKTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/MoveJDKTest.java Mon May 21 15:15:58 2018 -0400 @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018, 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 that CDS still works when the JDK is moved to a new directory + * @requires vm.cds + * @requires os.family == "linux" + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * jdk.jartool/sun.tools.jar + * @compile test-classes/Hello.java + * @run main MoveJDKTest + */ + +// This test works only on Linux because it depends on symlinks and the name of the hotspot DLL (libjvm.so). +// It probably doesn't work on Windows. +// TODO: change libjvm.so to libjvm.dylib on MacOS, before adding "@requires os.family == mac" +// TODO: test on solaris, before adding "@requires os.family == solaris" + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import jdk.test.lib.process.OutputAnalyzer; + +public class MoveJDKTest { + public static void main(String[] args) throws Exception { + String java_home_src = System.getProperty("java.home"); + String java_home_dst = System.getProperty("user.dir") + File.separator + "moved_jdk"; + + TestCommon.startNewArchiveName(); + String jsaFile = TestCommon.getCurrentArchiveName(); + String jsaOpt = "-XX:SharedArchiveFile=" + jsaFile; + { + ProcessBuilder pb = makeBuilder(java_home_src + "/bin/java", "-Xshare:dump", jsaOpt); + TestCommon.executeAndLog(pb, "dump"); + } + { + ProcessBuilder pb = makeBuilder(java_home_src + "/bin/java", + "-Xshare:auto", + jsaOpt, + "-Xlog:class+path=info", + "-version"); + OutputAnalyzer out = TestCommon.executeAndLog(pb, "exec-src"); + out.shouldNotContain("shared class paths mismatch"); + out.shouldNotContain("BOOT classpath mismatch"); + } + + clone(new File(java_home_src), new File(java_home_dst)); + System.out.println("============== Cloned JDK at " + java_home_dst); + + // Test runtime with cloned JDK + { + ProcessBuilder pb = makeBuilder(java_home_dst + "/bin/java", + "-Xshare:auto", + jsaOpt, + "-Xlog:class+path=info", + "-version"); + OutputAnalyzer out = TestCommon.executeAndLog(pb, "exec-dst"); + out.shouldNotContain("shared class paths mismatch"); + out.shouldNotContain("BOOT classpath mismatch"); + } + + // Test with bad JAR file name, hello.modules + String helloJar = JarBuilder.getOrCreateHelloJar(); + String fake_modules = copyFakeModulesFromHelloJar(); + String dumptimeBootAppendOpt = "-Xbootclasspath/a:" + fake_modules; + { + ProcessBuilder pb = makeBuilder(java_home_src + "/bin/java", + "-Xshare:dump", + dumptimeBootAppendOpt, + jsaOpt); + TestCommon.executeAndLog(pb, "dump"); + } + { + String runtimeBootAppendOpt = dumptimeBootAppendOpt + System.getProperty("path.separator") + helloJar; + ProcessBuilder pb = makeBuilder(java_home_dst + "/bin/java", + "-Xshare:auto", + runtimeBootAppendOpt, + jsaOpt, + "-Xlog:class+path=info", + "-version"); + OutputAnalyzer out = TestCommon.executeAndLog(pb, "exec-dst"); + out.shouldNotContain("shared class paths mismatch"); + out.shouldNotContain("BOOT classpath mismatch"); + } + } + + // Do a cheap clone of the JDK. Most files can be sym-linked. However, $JAVA_HOME/bin/java and $JAVA_HOME/lib/.../libjvm.so" + // must be copied, because the java.home property is derived from the canonicalized paths of these 2 files. + static void clone(File src, File dst) throws Exception { + if (dst.exists()) { + if (!dst.isDirectory()) { + throw new RuntimeException("Not a directory :" + dst); + } + } else { + if (!dst.mkdir()) { + throw new RuntimeException("Cannot create directory: " + dst); + } + } + for (String child : src.list()) { + if (child.equals(".") || child.equals("..")) { + continue; + } + + File child_src = new File(src, child); + File child_dst = new File(dst, child); + if (child_dst.exists()) { + throw new RuntimeException("Already exists: " + child_dst); + } + if (child_src.isFile()) { + if (child.equals("libjvm.so") || child.equals("java")) { + Files.copy(child_src.toPath(), /* copy data to -> */ child_dst.toPath()); + } else { + Files.createSymbolicLink(child_dst.toPath(), /* link to -> */ child_src.toPath()); + } + } else { + clone(child_src, child_dst); + } + } + } + + static ProcessBuilder makeBuilder(String... args) throws Exception { + System.out.print("["); + for (String s : args) { + System.out.print(" " + s); + } + System.out.println(" ]"); + return new ProcessBuilder(args); + } + + private static String copyFakeModulesFromHelloJar() throws Exception { + String classDir = System.getProperty("test.classes"); + String newFile = "hello.modules"; + String path = classDir + File.separator + newFile; + + Files.copy(Paths.get(classDir, "hello.jar"), + Paths.get(classDir, newFile), + StandardCopyOption.REPLACE_EXISTING); + return path; + } +} diff -r db3c6bb7f856 -r 83d8b3a25f25 test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/MainModuleOnly.java --- a/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/MainModuleOnly.java Mon May 21 11:43:57 2018 -0700 +++ b/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/MainModuleOnly.java Mon May 21 15:15:58 2018 -0400 @@ -172,7 +172,7 @@ "--module-path", moduleDir.toString(), "-m", TEST_MODULE1) .assertAbnormalExit( - "A jar/jimage file is not the one used while building the shared archive file:"); + "A jar file is not the one used while building the shared archive file:"); // create an archive with a non-empty directory in the --module-path. // The dumping process will exit with an error due to non-empty directory // in the --module-path.