--- old/src/hotspot/share/classfile/sharedPathsMiscInfo.cpp 2018-05-11 18:01:15.843793273 -0400 +++ new/src/hotspot/share/classfile/sharedPathsMiscInfo.cpp 2018-05-11 18:01:14.547718514 -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" @@ -141,11 +142,76 @@ } 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. + // + + bool relaxed_check = !FileMapInfo::current_info()->header()->has_platform_or_app_classes(); + char* runtime_boot_path; + ResourceMark rm; + if (relaxed_check) { + // only check the begining portion of the runtime boot path, up to + // the length of the dump time boot path + size_t len = strlen(path); + runtime_boot_path = NEW_RESOURCE_ARRAY(char, len + 1); + strncpy(runtime_boot_path, Arguments::get_sysclasspath(), len); + runtime_boot_path[len] = '\0'; + } else { + // check the full runtime boot path + runtime_boot_path = Arguments::get_sysclasspath(); + } + + // Do a quick check first with a simple + // strcmp(dump_time_boot_path, runtime_boot_path). If the paths are the + // same, the check has succeeded. + if (os::file_name_strcmp(path, runtime_boot_path) == 0) { + break; // ok + } + + // The paths are different. + // + // - 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. + // - The rest of the 'runtime_boot_path' and 'dump_time_boot_path' strings + // must be the same. + size_t path_sep_len = strlen(os::path_separator()); + char* rp = strstr(runtime_boot_path, os::path_separator()); + if (rp != NULL) { + rp += path_sep_len; + } + + char* dp = strstr((char*)path, os::path_separator()); + if (dp != NULL) { + dp ++; + } + + 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) { + if (os::file_name_strcmp(dp, rp) == 0) { + break; // ok, runtime and dump time paths match + } + } + + // The paths are different + return fail("[BOOT classpath mismatch, actual =", + Arguments::get_sysclasspath()); } break; case NON_EXIST: --- old/src/hotspot/share/memory/filemap.cpp 2018-05-11 18:01:19.996032778 -0400 +++ new/src/hotspot/share/memory/filemap.cpp 2018-05-11 18:01:18.691957558 -0400 @@ -206,6 +206,7 @@ } void SharedClassPathEntry::init(const char* name, TRAPS) { + assert(DumpSharedSpaces, "dump time only"); _timestamp = 0; _filesize = 0; @@ -215,7 +216,10 @@ _is_dir = true; } else { _is_dir = false; - _timestamp = st.st_mtime; + // The timestamp of the modules_image is not checked at runtime. + if (!ClassLoader::is_modules_image(name)) { + _timestamp = st.st_mtime; + } _filesize = st.st_size; } } else { @@ -237,6 +241,9 @@ struct stat st; const char* name = this->name(); + if (ClassLoader::is_modules_image(name)) { + name = ClassLoader::get_jrt_entry()->name(); // use the runtime modules_image path + } bool ok = true; log_info(class, path)("checking shared classpath entry: %s", name); if (os::stat(name, &st) != 0 && is_class_path) { @@ -251,18 +258,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; --- old/src/hotspot/share/memory/filemap.hpp 2018-05-11 18:01:23.712247133 -0400 +++ new/src/hotspot/share/memory/filemap.hpp 2018-05-11 18:01:22.420172605 -0400 @@ -56,8 +56,8 @@ 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; } --- old/test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java 2018-05-11 18:01:27.512466333 -0400 +++ new/test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java 2018-05-11 18:01:26.192390190 -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; @@ -52,10 +54,13 @@ BootClassPathMismatch test = new BootClassPathMismatch(); test.testBootClassPathMismatch(); test.testBootClassPathMismatch2(); + test.testBootClassPathMismatchWithAppend(); 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 */ @@ -71,20 +76,9 @@ .assertAbnormalExit(mismatchMessage); } - /* Error should be detected if: - * dump time: - * run-time : -Xbootclasspath/a:${testdir}/hello.jar - */ - public void testBootClassPathMismatch2() throws Exception { - String appJar = JarBuilder.getOrCreateHelloJar(); - String appClasses[] = {"Hello"}; - TestCommon.dump(appJar, appClasses); - TestCommon.run( - "-cp", appJar, "-verbose:class", "-Xbootclasspath/a:" + appJar, "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 +93,36 @@ .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 testBootClassPathMismatchWithAppend() throws Exception { + String appJar = JarBuilder.getOrCreateHelloJar(); + CDSOptions opts = new CDSOptions().setUseVersion(false); + OutputAnalyzer out = CDSTestUtils.createArchive(opts); + CDSTestUtils.checkDump(out); + 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 testBootClassPathMismatch2() throws Exception { + String appJar = JarBuilder.getOrCreateHelloJar(); + String appClasses[] = {"Hello"}; + TestCommon.dump(appJar, appClasses); + TestCommon.run( + "-cp", appJar, "-verbose:class", "-Xbootclasspath/a:" + appJar, "Hello") + .assertAbnormalExit(mismatchMessage); + } + private static void copyHelloToNewDir() throws Exception { String classDir = System.getProperty("test.classes"); String dstDir = classDir + File.separator + "newdir"; --- old/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/MainModuleOnly.java 2018-05-11 18:01:31.220680229 -0400 +++ new/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/MainModuleOnly.java 2018-05-11 18:01:29.904604315 -0400 @@ -172,6 +172,6 @@ "--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:"); } }