8279366: CDS should allow alternative locations for JAR files in classpath

Reviewed-by: iklam, coleenp
This commit is contained in:
Calvin Cheung 2022-10-25 17:52:18 +00:00
parent 3a873d3c5b
commit 427f50624f
10 changed files with 492 additions and 23 deletions

@ -35,6 +35,7 @@ CDSConst CDSConstants::offsets[] = {
{ "GenericCDSFileMapHeader::_crc", offset_of(GenericCDSFileMapHeader, _crc) },
{ "GenericCDSFileMapHeader::_version", offset_of(GenericCDSFileMapHeader, _version) },
{ "GenericCDSFileMapHeader::_header_size", offset_of(GenericCDSFileMapHeader, _header_size) },
{ "GenericCDSFileMapHeader::_common_app_classpath_prefix_size", offset_of(GenericCDSFileMapHeader, _common_app_classpath_prefix_size) },
{ "GenericCDSFileMapHeader::_base_archive_name_offset", offset_of(GenericCDSFileMapHeader, _base_archive_name_offset) },
{ "GenericCDSFileMapHeader::_base_archive_name_size", offset_of(GenericCDSFileMapHeader, _base_archive_name_size) },
{ "CDSFileMapHeaderBase::_space[0]", offset_of(CDSFileMapHeaderBase, _space) },

@ -204,6 +204,7 @@ void FileMapInfo::populate_header(size_t core_region_alignment) {
size_t header_size;
size_t base_archive_name_size = 0;
size_t base_archive_name_offset = 0;
size_t longest_common_prefix_size = 0;
if (is_static()) {
c_header_size = sizeof(FileMapHeader);
header_size = c_header_size;
@ -221,24 +222,30 @@ void FileMapInfo::populate_header(size_t core_region_alignment) {
}
FREE_C_HEAP_ARRAY(const char, default_base_archive_name);
}
ResourceMark rm;
GrowableArray<const char*>* app_cp_array = create_dumptime_app_classpath_array();
int len = app_cp_array->length();
longest_common_prefix_size = longest_common_app_classpath_prefix_len(len, app_cp_array);
_header = (FileMapHeader*)os::malloc(header_size, mtInternal);
memset((void*)_header, 0, header_size);
_header->populate(this,
core_region_alignment,
header_size,
base_archive_name_size,
base_archive_name_offset);
base_archive_name_offset,
longest_common_prefix_size);
}
void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment,
size_t header_size, size_t base_archive_name_size,
size_t base_archive_name_offset) {
size_t base_archive_name_offset, size_t common_app_classpath_prefix_size) {
// 1. We require _generic_header._magic to be at the beginning of the file
// 2. FileMapHeader also assumes that _generic_header is at the beginning of the file
assert(offset_of(FileMapHeader, _generic_header) == 0, "must be");
set_header_size((unsigned int)header_size);
set_base_archive_name_offset((unsigned int)base_archive_name_offset);
set_base_archive_name_size((unsigned int)base_archive_name_size);
set_common_app_classpath_prefix_size((unsigned int)common_app_classpath_prefix_size);
set_magic(DynamicDumpSharedSpaces ? CDS_DYNAMIC_ARCHIVE_MAGIC : CDS_ARCHIVE_MAGIC);
set_version(CURRENT_CDS_ARCHIVE_VERSION);
@ -311,6 +318,7 @@ void FileMapHeader::print(outputStream* st) {
st->print_cr("- crc: 0x%08x", crc());
st->print_cr("- version: 0x%x", version());
st->print_cr("- header_size: " UINT32_FORMAT, header_size());
st->print_cr("- common_app_classpath_size: " UINT32_FORMAT, common_app_classpath_prefix_size());
st->print_cr("- base_archive_name_offset: " UINT32_FORMAT, base_archive_name_offset());
st->print_cr("- base_archive_name_size: " UINT32_FORMAT, base_archive_name_size());
@ -808,6 +816,17 @@ bool FileMapInfo::check_paths_existence(const char* paths) {
return exist;
}
GrowableArray<const char*>* FileMapInfo::create_dumptime_app_classpath_array() {
Arguments::assert_is_dumping_archive();
GrowableArray<const char*>* path_array = new GrowableArray<const char*>(10);
ClassPathEntry* cpe = ClassLoader::app_classpath_entries();
while (cpe != NULL) {
path_array->append(cpe->name());
cpe = cpe->next();
}
return path_array;
}
GrowableArray<const char*>* FileMapInfo::create_path_array(const char* paths) {
GrowableArray<const char*>* path_array = new GrowableArray<const char*>(10);
JavaThread* current = JavaThread::current();
@ -842,23 +861,48 @@ bool FileMapInfo::classpath_failure(const char* msg, const char* name) {
return false;
}
bool FileMapInfo::check_paths(int shared_path_start_idx, int num_paths, GrowableArray<const char*>* rp_array) {
unsigned int FileMapInfo::longest_common_app_classpath_prefix_len(int num_paths,
GrowableArray<const char*>* rp_array) {
if (num_paths == 0) {
return 0;
}
unsigned int pos;
for (pos = 0; ; pos++) {
for (int i = 0; i < num_paths; i++) {
if (rp_array->at(i)[pos] != '\0' && rp_array->at(i)[pos] == rp_array->at(0)[pos]) {
continue;
}
// search backward for the pos before the file separator char
while (pos > 0 && rp_array->at(0)[--pos] != *os::file_separator());
// return the file separator char position
return pos + 1;
}
}
return 0;
}
bool FileMapInfo::check_paths(int shared_path_start_idx, int num_paths, GrowableArray<const char*>* rp_array,
unsigned int dumptime_prefix_len, unsigned int runtime_prefix_len) {
int i = 0;
int j = shared_path_start_idx;
bool mismatch = false;
while (i < num_paths && !mismatch) {
while (i < num_paths) {
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;
assert(strlen(shared_path(j)->name()) > (size_t)dumptime_prefix_len, "sanity");
const char* dumptime_path = shared_path(j)->name() + dumptime_prefix_len;
assert(strlen(rp_array->at(i)) > (size_t)runtime_prefix_len, "sanity");
const char* runtime_path = runtime_path = rp_array->at(i) + runtime_prefix_len;
if (!os::same_files(dumptime_path, runtime_path)) {
return true;
}
i++;
j++;
}
return mismatch;
return false;
}
bool FileMapInfo::validate_boot_class_paths() {
@ -912,7 +956,7 @@ bool FileMapInfo::validate_boot_class_paths() {
// check the full runtime boot path, must match with dump time
num = rp_len;
}
mismatch = check_paths(1, num, rp_array);
mismatch = check_paths(1, num, rp_array, 0, 0);
} else {
// create_path_array() ignores non-existing paths. Although the dump time and runtime boot classpath lengths
// are the same initially, after the call to create_path_array(), the runtime boot classpath length could become
@ -961,9 +1005,20 @@ bool FileMapInfo::validate_app_class_paths(int shared_app_paths_len) {
// run 2: -cp x.jar:NE4:b.jar -> x.jar:b.jar -> mismatched
int j = header()->app_class_paths_start_index();
mismatch = check_paths(j, shared_app_paths_len, rp_array);
mismatch = check_paths(j, shared_app_paths_len, rp_array, 0, 0);
if (mismatch) {
return classpath_failure("[APP classpath mismatch, actual: -Djava.class.path=", appcp);
// To facilitate app deployment, we allow the JAR files to be moved *together* to
// a different location, as long as they are still stored under the same directory
// structure. E.g., the following is OK.
// java -Xshare:dump -cp /a/Foo.jar:/a/b/Bar.jar ...
// java -Xshare:auto -cp /x/y/Foo.jar:/x/y/b/Bar.jar ...
unsigned int dumptime_prefix_len = header()->common_app_classpath_prefix_size();
unsigned int runtime_prefix_len = longest_common_app_classpath_prefix_len(shared_app_paths_len, rp_array);
mismatch = check_paths(j, shared_app_paths_len, rp_array,
dumptime_prefix_len, runtime_prefix_len);
if (mismatch) {
return classpath_failure("[APP classpath mismatch, actual: -Djava.class.path=", appcp);
}
}
}
return true;
@ -991,7 +1046,7 @@ bool FileMapInfo::check_module_paths() {
}
ResourceMark rm;
GrowableArray<const char*>* rp_array = create_path_array(rp);
return check_paths(header()->app_module_paths_start_index(), num_paths, rp_array);
return check_paths(header()->app_module_paths_start_index(), num_paths, rp_array, 0, 0);
}
bool FileMapInfo::validate_shared_path_table() {
@ -1205,6 +1260,10 @@ public:
return false;
}
if (!check_common_app_classpath_prefix_len()) {
return false;
}
// All fields in the GenericCDSFileMapHeader has been validated.
_is_valid = true;
return true;
@ -1282,6 +1341,16 @@ public:
_base_archive_name = name;
}
}
return true;
}
bool check_common_app_classpath_prefix_len() {
int common_path_size = _header->_common_app_classpath_prefix_size;
if (common_path_size < 0) {
FileMapInfo::fail_continue("common app classpath prefix len < 0");
return false;
}
return true;
}
};
@ -1364,8 +1433,9 @@ bool FileMapInfo::init_from_file(int fd) {
if (base_offset != 0 && name_size != 0) {
if (header_size != base_offset + name_size) {
log_info(cds)("_header_size: " UINT32_FORMAT, header_size);
log_info(cds)("base_archive_name_size: " UINT32_FORMAT, name_size);
log_info(cds)("base_archive_name_offset: " UINT32_FORMAT, base_offset);
log_info(cds)("common_app_classpath_size: " UINT32_FORMAT, header()->common_app_classpath_prefix_size());
log_info(cds)("base_archive_name_size: " UINT32_FORMAT, header()->base_archive_name_size());
log_info(cds)("base_archive_name_offset: " UINT32_FORMAT, header()->base_archive_name_offset());
FileMapInfo::fail_continue("The shared archive file has an incorrect header size.");
return false;
}

@ -245,6 +245,7 @@ public:
unsigned int header_size() const { return _generic_header._header_size; }
unsigned int base_archive_name_offset() const { return _generic_header._base_archive_name_offset; }
unsigned int base_archive_name_size() const { return _generic_header._base_archive_name_size; }
unsigned int common_app_classpath_prefix_size() const { return _generic_header._common_app_classpath_prefix_size; }
void set_magic(unsigned int m) { _generic_header._magic = m; }
void set_crc(int crc_value) { _generic_header._crc = crc_value; }
@ -252,6 +253,7 @@ public:
void set_header_size(unsigned int s) { _generic_header._header_size = s; }
void set_base_archive_name_offset(unsigned int s) { _generic_header._base_archive_name_offset = s; }
void set_base_archive_name_size(unsigned int s) { _generic_header._base_archive_name_size = s; }
void set_common_app_classpath_prefix_size(unsigned int s) { _generic_header._common_app_classpath_prefix_size = s; }
size_t core_region_alignment() const { return _core_region_alignment; }
int obj_alignment() const { return _obj_alignment; }
@ -311,7 +313,8 @@ public:
}
void populate(FileMapInfo *info, size_t core_region_alignment, size_t header_size,
size_t base_archive_name_size, size_t base_archive_name_offset);
size_t base_archive_name_size, size_t base_archive_name_offset,
size_t common_app_classpath_size);
static bool is_valid_region(int region) {
return (0 <= region && region < NUM_CDS_REGIONS);
}
@ -556,10 +559,16 @@ public:
char* skip_first_path_entry(const char* path) NOT_CDS_RETURN_(NULL);
int num_paths(const char* path) NOT_CDS_RETURN_(0);
bool check_paths_existence(const char* paths) NOT_CDS_RETURN_(false);
GrowableArray<const char*>* create_dumptime_app_classpath_array() NOT_CDS_RETURN_(NULL);
GrowableArray<const char*>* create_path_array(const char* path) NOT_CDS_RETURN_(NULL);
bool classpath_failure(const char* msg, const char* name) NOT_CDS_RETURN_(false);
unsigned int longest_common_app_classpath_prefix_len(int num_paths,
GrowableArray<const char*>* rp_array)
NOT_CDS_RETURN_(0);
bool check_paths(int shared_path_start_idx, int num_paths,
GrowableArray<const char*>* rp_array) NOT_CDS_RETURN_(false);
GrowableArray<const char*>* rp_array,
unsigned int dumptime_prefix_len,
unsigned int runtime_prefix_len) 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_regions(int first, int max, bool is_open_archive,

@ -73,6 +73,8 @@ typedef struct GenericCDSFileMapHeader {
int _crc; // header crc checksum, start from _base_archive_name_offset
int _version; // CURRENT_CDS_ARCHIVE_VERSION of the jdk that dumped the this archive
unsigned int _header_size; // total size of the header, in bytes
unsigned int _common_app_classpath_prefix_size; // size of the common prefix of app class paths
// 0 if no common prefix exists
unsigned int _base_archive_name_offset; // offset where the base archive name is stored
// static archive: 0
// dynamic archive:
@ -85,6 +87,7 @@ typedef struct GenericCDSFileMapHeader {
// dynamic:
// 0 for default base archive
// non-zero for non-default base archive
} GenericCDSFileMapHeader;
// This type is used by the Serviceability Agent to access the contents of

@ -0,0 +1,148 @@
/*
* Copyright (c) 2022, 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
* @bug 8279366
* @summary Test app class paths checking with the longest common path taken into account.
* @requires vm.cds
* @library /test/lib
* @compile test-classes/Hello.java
* @compile test-classes/HelloMore.java
* @run driver CommonAppClasspath
*/
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import jdk.test.lib.cds.CDSTestUtils;
public class CommonAppClasspath {
private static final Path USER_DIR = Paths.get(CDSTestUtils.getOutputDir());
private static final String failedMessage = "APP classpath mismatch";
private static final String successMessage1 = "Hello source: shared objects file";
private static final String successMessage2 = "HelloMore source: shared objects file";
private static void runtimeTest(String classPath, String mainClass, int expectedExitValue,
String ... checkMessages) throws Exception {
CDSTestUtils.Result result = TestCommon.run(
"-Xshare:on",
"-XX:SharedArchiveFile=" + TestCommon.getCurrentArchiveName(),
"-cp", classPath,
"-Xlog:class+load=trace,class+path=info",
mainClass);
if (expectedExitValue == 0) {
result.assertNormalExit( output -> {
for (String s : checkMessages) {
output.shouldContain(s);
}
});
} else {
result.assertAbnormalExit( output -> {
for (String s : checkMessages) {
output.shouldContain(s);
}
});
}
}
public static void main(String[] args) throws Exception {
String appJar = JarBuilder.getOrCreateHelloJar();
String appJar2 = JarBuilder.build("AppendClasspath_HelloMore", "HelloMore");
// dump an archive with only appJar in the original location
String jars = appJar;
TestCommon.testDump(jars, TestCommon.list("Hello"));
// copy hello.jar to tmp dir
Path destPath = CDSTestUtils.copyFile(appJar, System.getProperty("java.io.tmpdir"));
// Run with appJar relocated to tmp dir - should PASS
runtimeTest(destPath.toString(), "Hello", 0, successMessage1);
// dump an archive with both jars in the original location
jars = appJar + File.pathSeparator + appJar2;
TestCommon.testDump(jars, TestCommon.list("Hello", "HelloMore"));
// copy hello.jar to USER_DIR/deploy
String newDir = USER_DIR.toString() + File.separator + "deploy";
destPath = CDSTestUtils.copyFile(appJar, newDir);
// copy AppendClasspath_HelloMore.jar to USER_DIR/deploy
Path destPath2 = CDSTestUtils.copyFile(appJar2, newDir);
// Run with both jars relocated to USER_DIR/dpeloy - should PASS
runtimeTest(destPath.toString() + File.pathSeparator + destPath2.toString(),
"HelloMore", 0, successMessage1, successMessage2);
// Run with relocation of only the second jar - should FAIL
runtimeTest(appJar + File.pathSeparator + destPath2.toString(),
"HelloMore", 1, failedMessage);
// Run with relocation of only the first jar - should FAIL
runtimeTest(destPath.toString() + File.pathSeparator + appJar2,
"HelloMore", 1, failedMessage);
// Dump CDS archive with the first jar relocated.
jars = destPath.toString() + File.pathSeparator + appJar2;
TestCommon.testDump(jars, TestCommon.list("Hello", "HelloMore"));
// Run with first jar relocated - should PASS
runtimeTest(destPath.toString() + File.pathSeparator + appJar2,
"HelloMore", 0, successMessage1, successMessage2);
// Run with both jars relocated - should FAIL
runtimeTest(destPath.toString() + File.pathSeparator + destPath2.toString(),
"HelloMore", 1, failedMessage);
// Copy hello.jar to USER_DIR/a
destPath = CDSTestUtils.copyFile(appJar, USER_DIR.toString() + File.separator + "a");
// copy AppendClasspath_HelloMore.jar to USER_DIR/aa
destPath2 = CDSTestUtils.copyFile(appJar2, USER_DIR.toString() + File.separator + "aa");
// Dump CDS archive with the both jar files relocated
// appJar to USER_DIR/a
// appJar2 to USER_DIR/aa
jars = destPath.toString() + File.pathSeparator + destPath2.toString();
TestCommon.testDump(jars, TestCommon.list("Hello", "HelloMore"));
// Copy hello.jar to USER_DIR/x/a
Path runPath = CDSTestUtils.copyFile(appJar, USER_DIR.toString() + File.separator + "x" + File.separator + "a");
// copy AppendClasspath_HelloMore.jar to USER_DIR/x/aa
Path runPath2= CDSTestUtils.copyFile(appJar2, USER_DIR.toString() + File.separator + "x" + File.separator + "aa");
// Run with both jars relocated to USER_DIR/x/a and USER_DIR/x/aa dirs - should PASS
runtimeTest(runPath.toString() + File.pathSeparator + runPath2.toString(),
"HelloMore", 0, successMessage1, successMessage2);
// copy AppendClasspath_HelloMore.jar to USER_DIR/x/a
runPath2= CDSTestUtils.copyFile(appJar2, USER_DIR.toString() + File.separator + "x" + File.separator + "a");
// Run with both jars relocated to USER_DIR/x/a dir - should FAIL
runtimeTest(runPath.toString() + File.pathSeparator + runPath2.toString(),
"HelloMore", 1, failedMessage);
}
}

@ -271,5 +271,15 @@ public class SharedArchiveConsistency {
baseArchiveNameOffset = CDSArchiveUtils.baseArchiveNameOffset(copiedJsa);
System.out.println("new baseArchiveNameOffset = " + baseArchiveNameOffset);
testAndCheck(verifyExecArgs);
// modify _common_app_classpath_size
String wrongCommonAppClasspathOffset = startNewArchive("wrongCommonAppClasspathOffset");
copiedJsa = CDSArchiveUtils.copyArchiveFile(orgJsaFile, wrongCommonAppClasspathOffset);
int commonAppClasspathPrefixSize = CDSArchiveUtils.commonAppClasspathPrefixSize(copiedJsa);
System.out.println(" commonAppClasspathPrefixSize = " + commonAppClasspathPrefixSize);
CDSArchiveUtils.writeData(copiedJsa, CDSArchiveUtils.offsetCommonAppClasspathPrefixSize(), commonAppClasspathPrefixSize * 2);
commonAppClasspathPrefixSize = CDSArchiveUtils.commonAppClasspathPrefixSize(copiedJsa);
System.out.println("new commonAppClasspathPrefixSize = " + commonAppClasspathPrefixSize);
testAndCheck(verifyExecArgs);
}
}

@ -150,7 +150,23 @@ public class ArchiveConsistency extends DynamicArchiveTestBase {
appJar, mainClass, isAuto ? 0 : 1,
"Base archive name is damaged");
startTest("5. Make base archive name not terminated with '\0'");
startTest("5a. Modify common app classpath size");
String wrongCommonAppClasspathOffset = getNewArchiveName("wrongCommonAppClasspathOffset");
copiedJsa = CDSArchiveUtils.copyArchiveFile(jsa, wrongCommonAppClasspathOffset);
int commonAppClasspathPrefixSize = CDSArchiveUtils.commonAppClasspathPrefixSize(copiedJsa);
CDSArchiveUtils.writeData(copiedJsa, CDSArchiveUtils.offsetCommonAppClasspathPrefixSize(), -1);
runTwo(baseArchiveName, wrongCommonAppClasspathOffset,
appJar, mainClass, isAuto ? 0 : 1,
"common app classpath prefix len < 0");
startTest("5b. Modify common app classpath size, run with -XX:-VerifySharedSpaces");
VERIFY_CRC = true;
runTwo(baseArchiveName, modTop,
appJar, mainClass, isAuto ? 0 : 1,
"Header checksum verification failed");
VERIFY_CRC = false;
startTest("6. Make base archive name not terminated with '\0'");
String wrongBaseName = getNewArchiveName("wrongBaseName");
copiedJsa = CDSArchiveUtils.copyArchiveFile(jsa, wrongBaseName);
baseArchiveNameOffset = CDSArchiveUtils.baseArchiveNameOffset(copiedJsa);
@ -162,7 +178,7 @@ public class ArchiveConsistency extends DynamicArchiveTestBase {
appJar, mainClass, isAuto ? 0 : 1,
"Base archive name is damaged");
startTest("6. Modify base archive name to a file that doesn't exist");
startTest("7. Modify base archive name to a file that doesn't exist");
String wrongBaseName2 = getNewArchiveName("wrongBaseName2");
copiedJsa = CDSArchiveUtils.copyArchiveFile(jsa, wrongBaseName2);
baseArchiveNameOffset = CDSArchiveUtils.baseArchiveNameOffset(copiedJsa);
@ -182,7 +198,7 @@ public class ArchiveConsistency extends DynamicArchiveTestBase {
// -XX:SharedArchiveFile=non-exist-base.jsa:top.jsa
// -XX:SharedArchiveFile=base.jsa:non-exist-top.jsa
// -XX:SharedArchiveFile=non-exist-base.jsa:non-exist-top.jsa
startTest("7. Non-exist base archive");
startTest("8. Non-exist base archive");
String nonExistBase = "non-exist-base.jsa";
File nonExistBaseFile = new File(nonExistBase);
nonExistBaseFile.delete();
@ -190,7 +206,7 @@ public class ArchiveConsistency extends DynamicArchiveTestBase {
appJar, mainClass, isAuto ? 0 : 1,
"Specified shared archive not found (" + nonExistBase + ")");
startTest("8. Non-exist top archive");
startTest("9. Non-exist top archive");
String nonExistTop = "non-exist-top.jsa";
File nonExistTopFile = new File(nonExistTop);
nonExistTopFile.delete();
@ -198,7 +214,7 @@ public class ArchiveConsistency extends DynamicArchiveTestBase {
appJar, mainClass, isAuto ? 0 : 1,
"Specified shared archive not found (" + nonExistTop + ")");
startTest("9. nost-exist-base and non-exist-top");
startTest("10. nost-exist-base and non-exist-top");
runTwo(nonExistBase, nonExistTop,
appJar, mainClass, isAuto ? 0 : 1,
"Specified shared archive not found (" + nonExistBase + ")");
@ -209,7 +225,7 @@ public class ArchiveConsistency extends DynamicArchiveTestBase {
if (!isUseSharedSpacesDisabled()) {
new File(baseArchiveName).delete();
startTest("10. -XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=" + topArchiveName);
startTest("11. -XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=" + topArchiveName);
run(topArchiveName,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
@ -219,7 +235,7 @@ public class ArchiveConsistency extends DynamicArchiveTestBase {
output.shouldContain("warning: -XX:+AutoCreateSharedArchive is unsupported when base CDS archive is not loaded");
});
startTest("11. -XX:SharedArchiveFile=" + topArchiveName + " -XX:ArchiveClassesAtExit=" + getNewArchiveName("top3"));
startTest("12. -XX:SharedArchiveFile=" + topArchiveName + " -XX:ArchiveClassesAtExit=" + getNewArchiveName("top3"));
run(topArchiveName,
"-Xshare:auto",
"-XX:ArchiveClassesAtExit=" + getNewArchiveName("top3"),

@ -0,0 +1,185 @@
/*
* Copyright (c) 2022, 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
* @bug 8279366
* @summary Test app class paths checking with the longest common path taken into account.
* @requires vm.cds
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
* @build jdk.test.whitebox.WhiteBox
* @compile ../test-classes/Hello.java
* @compile ../test-classes/HelloMore.java
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. CommonAppClasspath
*/
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import jdk.test.lib.cds.CDSTestUtils;
public class CommonAppClasspath extends DynamicArchiveTestBase {
private static final Path USER_DIR = Paths.get(CDSTestUtils.getOutputDir());
private static final String failedMessage = "shared class paths mismatch";
private static final String successMessage1 = "Hello source: shared objects file";
private static final String successMessage2 = "HelloMore source: shared objects file";
private static void runtimeTest(String topArchiveName, String classPath,
String mainClass, int expectedExitValue,
String ... checkMessages) throws Exception {
CDSTestUtils.Result result = run(topArchiveName,
"-Xlog:class+load",
"-Xlog:cds+dynamic=debug,cds=debug",
"-cp", classPath, mainClass);
if (expectedExitValue == 0) {
result.assertNormalExit( output -> {
for (String s : checkMessages) {
output.shouldContain(s);
}
});
} else {
result.assertAbnormalExit( output -> {
for (String s : checkMessages) {
output.shouldContain(s);
}
});
}
}
public static void main(String[] args) throws Exception {
runTest(CommonAppClasspath::testDefaultBase);
}
static void testDefaultBase() throws Exception {
String topArchiveName = getNewArchiveName("top");
doTest(topArchiveName);
}
private static void doTest(String topArchiveName) throws Exception {
String mainClass = "HelloMore";
String appJar = JarBuilder.getOrCreateHelloJar();
String appJar2 = JarBuilder.build("AppendClasspath_HelloMore", mainClass);
// dump an archive with only appJar in the original location
String jars = appJar;
dump(topArchiveName,
"-Xlog:cds",
"-Xlog:cds+dynamic=debug",
"-cp", jars, "Hello")
.assertNormalExit(output -> {
output.shouldContain("Written dynamic archive 0x");
});
// copy hello.jar to tmp dir
Path destPath = CDSTestUtils.copyFile(appJar, System.getProperty("java.io.tmpdir"));
// Run with appJar relocated to tmp dir - should PASS
jars = destPath.toString();
runtimeTest(topArchiveName, jars, "Hello", 0, successMessage1);
// dump an archive with both jars in the original location
jars = appJar + File.pathSeparator + appJar2;
dump(topArchiveName,
"-Xlog:cds",
"-Xlog:cds+dynamic=debug",
"-cp", jars, mainClass)
.assertNormalExit(output -> {
output.shouldContain("Written dynamic archive 0x");
});
// copy hello.jar to USER_DIR/deploy
String newDir = USER_DIR.toString() + File.separator + "deploy";
destPath = CDSTestUtils.copyFile(appJar, newDir);
// copy AppendClasspath_HelloMore.jar to USER_DIR/deploy
Path destPath2 = CDSTestUtils.copyFile(appJar2, newDir);
// Run with both jars relocated to USER_DIR/dpeloy - should PASS
jars = destPath.toString() + File.pathSeparator + destPath2.toString();
runtimeTest(topArchiveName, jars, mainClass, 0, successMessage1, successMessage2);
// Run with relocation of only the second jar - should FAIL
jars = appJar + File.pathSeparator + destPath2.toString();
runtimeTest(topArchiveName, jars, mainClass, 1, failedMessage);
// Run with relocation of only the first jar - should FAIL
jars = destPath.toString() + File.pathSeparator + appJar2;
runtimeTest(topArchiveName, jars, mainClass, 1, failedMessage);
// Dump CDS archive with the first jar relocated.
jars = destPath.toString() + File.pathSeparator + appJar2;
dump(topArchiveName,
"-Xlog:cds",
"-Xlog:cds+dynamic=debug",
"-cp", jars, mainClass)
.assertNormalExit(output -> {
output.shouldContain("Written dynamic archive 0x");
});
// Run with first jar relocated - should PASS
jars = destPath.toString() + File.pathSeparator + appJar2;
runtimeTest(topArchiveName, jars, mainClass, 0, successMessage1, successMessage2);
// Run with both jars relocated - should FAIL
jars = destPath.toString() + File.pathSeparator + destPath2.toString();
runtimeTest(topArchiveName, jars, mainClass, 1, failedMessage);
// Copy hello.jar to USER_DIR/a
destPath = CDSTestUtils.copyFile(appJar, USER_DIR.toString() + File.separator + "a");
// copy AppendClasspath_HelloMore.jar to USER_DIR/aa
destPath2 = CDSTestUtils.copyFile(appJar2, USER_DIR.toString() + File.separator + "aa");
// Dump CDS archive with the both jar files relocated
// appJar to USER_DIR/a
// appJar2 to USER_DIR/aa
jars = destPath.toString() + File.pathSeparator + destPath2.toString();
dump(topArchiveName,
"-Xlog:cds",
"-Xlog:cds+dynamic=debug",
"-cp", jars, mainClass)
.assertNormalExit(output -> {
output.shouldContain("Written dynamic archive 0x");
});
// Copy hello.jar to USER_DIR/x/a
Path runPath = CDSTestUtils.copyFile(appJar, USER_DIR.toString() + File.separator + "x" + File.separator + "a");
// copy AppendClasspath_HelloMore.jar to USER_DIR/x/aa
Path runPath2= CDSTestUtils.copyFile(appJar2, USER_DIR.toString() + File.separator + "x" + File.separator + "aa");
// Run with both jars relocated to USER_DIR/x/a and USER_DIR/x/aa dirs - should PASS
jars = runPath.toString() + File.pathSeparator + runPath2.toString();
runtimeTest(topArchiveName, jars, mainClass, 0, successMessage1, successMessage2);
// copy AppendClasspath_HelloMore.jar to USER_DIR/x/a
runPath2= CDSTestUtils.copyFile(appJar2, USER_DIR.toString() + File.separator + "x" + File.separator + "a");
// Run with both jars relocated to USER_DIR/x/a dir - should FAIL
jars = runPath.toString() + File.pathSeparator + runPath2.toString();
runtimeTest(topArchiveName, jars, mainClass, 1, failedMessage);
}
}

@ -53,6 +53,7 @@ public class CDSArchiveUtils {
private static int offsetCrc; // offset of GenericCDSFileMapHeader::_crc
private static int offsetVersion; // offset of GenericCDSFileMapHeader::_version
private static int offsetHeaderSize; // offset of GenericCDSFileMapHeader::_header_size
private static int offsetCommonAppClasspathPrefixSize;// offset of GenericCDSFileMapHeader::_common_app_classpath_size
private static int offsetBaseArchiveNameOffset;// offset of GenericCDSFileMapHeader::_base_archive_name_offset
private static int offsetBaseArchiveNameSize; // offset of GenericCDSFileMapHeader::_base_archive_name_size
private static int offsetJvmIdent; // offset of FileMapHeader::_jvm_ident
@ -93,6 +94,7 @@ public class CDSArchiveUtils {
offsetCrc = wb.getCDSOffsetForName("GenericCDSFileMapHeader::_crc");
offsetVersion = wb.getCDSOffsetForName("GenericCDSFileMapHeader::_version");
offsetHeaderSize = wb.getCDSOffsetForName("GenericCDSFileMapHeader::_header_size");
offsetCommonAppClasspathPrefixSize = wb.getCDSOffsetForName("GenericCDSFileMapHeader::_common_app_classpath_prefix_size");
offsetBaseArchiveNameOffset = wb.getCDSOffsetForName("GenericCDSFileMapHeader::_base_archive_name_offset");
offsetBaseArchiveNameSize = wb.getCDSOffsetForName("GenericCDSFileMapHeader::_base_archive_name_size");
offsetJvmIdent = wb.getCDSOffsetForName("FileMapHeader::_jvm_ident");
@ -131,6 +133,7 @@ public class CDSArchiveUtils {
public static int offsetCrc() { return offsetCrc; }
public static int offsetVersion() { return offsetVersion; }
public static int offsetHeaderSize() { return offsetHeaderSize; }
public static int offsetCommonAppClasspathPrefixSize() { return offsetCommonAppClasspathPrefixSize; }
public static int offsetBaseArchiveNameOffset() { return offsetBaseArchiveNameOffset; }
public static int offsetBaseArchiveNameSize() { return offsetBaseArchiveNameSize; }
public static int offsetJvmIdent() { return offsetJvmIdent; }
@ -158,6 +161,10 @@ public class CDSArchiveUtils {
return alignUpWithAlignment(size);
}
public static int commonAppClasspathPrefixSize(File jsaFile) throws Exception {
return (int)readInt(jsaFile, offsetCommonAppClasspathPrefixSize, 4);
}
public static int baseArchiveNameOffset(File jsaFile) throws Exception {
return (int)readInt(jsaFile, offsetBaseArchiveNameOffset, 4);
}

@ -28,7 +28,11 @@ import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.CopyOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@ -761,4 +765,20 @@ public class CDSTestUtils {
System.out.println(" ]");
return new ProcessBuilder(args);
}
public static Path copyFile(String srcFile, String destDir) throws Exception {
int idx = srcFile.lastIndexOf(File.separator);
String jarName = srcFile.substring(idx + 1);
Path srcPath = Paths.get(jarName);
Path newPath = Paths.get(destDir);
Path newDir;
if (!Files.exists(newPath)) {
newDir = Files.createDirectories(newPath);
} else {
newDir = newPath;
}
Path destPath = newDir.resolve(jarName);
Files.copy(srcPath, destPath, REPLACE_EXISTING, COPY_ATTRIBUTES);
return destPath;
}
}