8199807: AppCDS performs overly restrictive path matching check
8203377: Cleanup the usage of os::file_name_strcmp() in SharedPathsMiscInfo::check() Relax CDS/AppCDS path check for modules image. Reviewed-by: iklam, ccheung
This commit is contained in:
parent
281e575907
commit
422e112ffd
@ -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 <sys/ioctl.h>
|
||||
#include <netdb.h>
|
||||
|
||||
// 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) {
|
||||
|
@ -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 <poll.h>
|
||||
#include <netdb.h>
|
||||
|
||||
// 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) {
|
||||
|
@ -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 <poll.h>
|
||||
#include <netdb.h>
|
||||
|
||||
// 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) {
|
||||
|
@ -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 <netdb.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
// 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() {
|
||||
|
@ -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 char* os::dll_file_extension() { return ".dll"; }
|
||||
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) {
|
||||
|
@ -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 @@ bool SharedPathsMiscInfo::check() {
|
||||
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 @@ bool SharedPathsMiscInfo::check(jint type, const char* path) {
|
||||
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 @@ bool SharedPathsMiscInfo::check(jint type, const char* path) {
|
||||
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]) {
|
||||
|
@ -205,17 +205,23 @@ void FileMapInfo::FileMapHeader::populate(FileMapInfo* mapinfo, size_t alignment
|
||||
_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 @@ bool SharedClassPathEntry::validate(bool is_class_path) {
|
||||
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 @@ bool SharedClassPathEntry::validate(bool is_class_path) {
|
||||
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 @@ void FileMapInfo::allocate_shared_path_table() {
|
||||
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 @@ void FileMapInfo::allocate_shared_path_table() {
|
||||
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 @@ void FileMapInfo::allocate_shared_path_table() {
|
||||
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 @@ void FileMapInfo::update_shared_classpath(ClassPathEntry *cpe, SharedClassPathEn
|
||||
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 @@ void FileMapInfo::update_shared_classpath(ClassPathEntry *cpe, SharedClassPathEn
|
||||
char* p = (char*)(buf->data());
|
||||
memcpy(p, manifest, manifest_size);
|
||||
ent->set_manifest(buf);
|
||||
ent->set_is_signed(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<char>* _name;
|
||||
Array<u1>* _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 @@ public:
|
||||
void set_manifest(Array<u1>* manifest) {
|
||||
_manifest = manifest;
|
||||
}
|
||||
void set_is_signed(bool s) {
|
||||
_is_signed = s;
|
||||
}
|
||||
bool is_signed() {
|
||||
return _is_signed;
|
||||
}
|
||||
};
|
||||
|
||||
class FileMapInfo : public CHeapObj<mtInternal> {
|
||||
|
@ -167,7 +167,7 @@ class os: AllStatic {
|
||||
|
||||
// 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);
|
||||
|
@ -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 @@ public class BootClassPathMismatch {
|
||||
|
||||
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: <no bootclasspath specified>
|
||||
* 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 @@ public class BootClassPathMismatch {
|
||||
.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: <no bootclasspath specified>
|
||||
* 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 @@ public class BootClassPathMismatch {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
166
test/hotspot/jtreg/runtime/appcds/MoveJDKTest.java
Normal file
166
test/hotspot/jtreg/runtime/appcds/MoveJDKTest.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -172,7 +172,7 @@ public class MainModuleOnly {
|
||||
"--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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user