8328313: Archived module graph should allow identical --module-path to be specified during dump time and run time

Reviewed-by: alanb, dholmes, iklam
This commit is contained in:
Calvin Cheung 2024-10-02 15:51:56 +00:00
parent 9fc1c68442
commit 0bdfe88e4c
19 changed files with 591 additions and 51 deletions

View File

@ -236,7 +236,7 @@ void CDSConfig::init_shared_archive_paths() {
}
void CDSConfig::check_internal_module_property(const char* key, const char* value) {
if (Arguments::is_internal_module_property(key)) {
if (Arguments::is_internal_module_property(key) && !Arguments::is_module_path_property(key)) {
stop_using_optimized_module_handling();
log_info(cds)("optimized module handling: disabled due to incompatible property: %s=%s", key, value);
}

View File

@ -781,12 +781,12 @@ bool FileMapInfo::check_paths(int shared_path_start_idx, int num_paths, Growable
assert(strlen(rp_array->at(i)) > (size_t)runtime_prefix_len, "sanity");
const char* runtime_path = rp_array->at(i) + runtime_prefix_len;
if (!os::same_files(dumptime_path, runtime_path)) {
return true;
return false;
}
i++;
j++;
}
return false;
return true;
}
bool FileMapInfo::validate_boot_class_paths() {
@ -810,7 +810,7 @@ bool FileMapInfo::validate_boot_class_paths() {
char* rp = skip_first_path_entry(runtime_boot_path);
assert(shared_path(0)->is_modules_image(), "first shared_path must be the modules image");
int dp_len = header()->app_class_paths_start_index() - 1; // ignore the first path to the module image
bool mismatch = false;
bool match = true;
bool relaxed_check = !header()->has_platform_or_app_classes();
if (dp_len == 0 && rp == nullptr) {
@ -823,7 +823,7 @@ bool FileMapInfo::validate_boot_class_paths() {
if (check_paths_existence(rp)) {
// If a path exists in the runtime boot paths, it is considered a mismatch
// since there's no boot path specified during dump time.
mismatch = true;
match = false;
}
}
} else if (dp_len > 0 && rp != nullptr) {
@ -840,16 +840,16 @@ 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, 0, 0);
match = 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
// shorter. We consider boot classpath mismatch in this case.
mismatch = true;
match = false;
}
}
if (mismatch) {
if (!match) {
// The paths are different
return classpath_failure("[BOOT classpath mismatch, actual =", runtime_boot_path);
}
@ -860,7 +860,7 @@ bool FileMapInfo::validate_app_class_paths(int shared_app_paths_len) {
const char *appcp = Arguments::get_appclasspath();
assert(appcp != nullptr, "null app classpath");
int rp_len = num_paths(appcp);
bool mismatch = false;
bool match = false;
if (rp_len < shared_app_paths_len) {
return classpath_failure("Run time APP classpath is shorter than the one at dump time: ", appcp);
}
@ -889,8 +889,8 @@ 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, 0, 0);
if (mismatch) {
match = check_paths(j, shared_app_paths_len, rp_array, 0, 0);
if (!match) {
// 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.
@ -901,10 +901,10 @@ bool FileMapInfo::validate_app_class_paths(int shared_app_paths_len) {
if (dumptime_prefix_len != 0 || runtime_prefix_len != 0) {
log_info(class, path)("LCP length for app classpath (dumptime: %u, runtime: %u)",
dumptime_prefix_len, runtime_prefix_len);
mismatch = check_paths(j, shared_app_paths_len, rp_array,
match = check_paths(j, shared_app_paths_len, rp_array,
dumptime_prefix_len, runtime_prefix_len);
}
if (mismatch) {
if (!match) {
return classpath_failure("[APP classpath mismatch, actual: -Djava.class.path=", appcp);
}
}
@ -926,15 +926,35 @@ void FileMapInfo::log_paths(const char* msg, int start_idx, int end_idx) {
}
}
void FileMapInfo::extract_module_paths(const char* runtime_path, GrowableArray<const char*>* module_paths) {
GrowableArray<const char*>* path_array = create_path_array(runtime_path);
int num_paths = path_array->length();
for (int i = 0; i < num_paths; i++) {
const char* name = path_array->at(i);
ClassLoaderExt::extract_jar_files_from_path(name, module_paths);
}
// module paths are stored in sorted order in the CDS archive.
module_paths->sort(ClassLoaderExt::compare_module_path_by_name);
}
bool FileMapInfo::check_module_paths() {
const char* rp = Arguments::get_property("jdk.module.path");
int num_paths = CDSConfig::num_archives(rp);
if (num_paths != header()->num_module_paths()) {
const char* runtime_path = Arguments::get_property("jdk.module.path");
int archived_num_module_paths = header()->num_module_paths();
if (runtime_path == nullptr && archived_num_module_paths == 0) {
return true;
}
if ((runtime_path == nullptr && archived_num_module_paths > 0) ||
(runtime_path != nullptr && archived_num_module_paths == 0)) {
return false;
}
ResourceMark rm;
GrowableArray<const char*>* rp_array = create_path_array(rp);
return check_paths(header()->app_module_paths_start_index(), num_paths, rp_array, 0, 0);
GrowableArray<const char*>* module_paths = new GrowableArray<const char*>(3);
extract_module_paths(runtime_path, module_paths);
int num_paths = module_paths->length();
if (num_paths != archived_num_module_paths) {
return false;
}
return check_paths(header()->app_module_paths_start_index(), num_paths, module_paths, 0, 0);
}
bool FileMapInfo::validate_shared_path_table() {
@ -944,6 +964,16 @@ bool FileMapInfo::validate_shared_path_table() {
// Load the shared path table info from the archive header
_shared_path_table = header()->shared_path_table();
bool matched_module_paths = true;
if (CDSConfig::is_dumping_dynamic_archive() || header()->has_full_module_graph()) {
matched_module_paths = check_module_paths();
}
if (header()->has_full_module_graph() && !matched_module_paths) {
CDSConfig::stop_using_optimized_module_handling();
log_info(cds)("optimized module handling: disabled because of mismatched module paths");
}
if (CDSConfig::is_dumping_dynamic_archive()) {
// Only support dynamic dumping with the usage of the default CDS archive
// or a simple base archive.
@ -959,7 +989,7 @@ bool FileMapInfo::validate_shared_path_table() {
"Dynamic archiving is disabled because base layer archive has appended boot classpath");
}
if (header()->num_module_paths() > 0) {
if (!check_module_paths()) {
if (!matched_module_paths) {
CDSConfig::disable_dumping_dynamic_archive();
log_warning(cds)(
"Dynamic archiving is disabled because base layer archive has a different module path");

View File

@ -271,6 +271,7 @@ public:
bool compressed_oops() const { return _compressed_oops; }
bool compressed_class_pointers() const { return _compressed_class_ptrs; }
HeapRootSegments heap_root_segments() const { return _heap_root_segments; }
bool has_full_module_graph() const { return _has_full_module_graph; }
size_t heap_oopmap_start_pos() const { return _heap_oopmap_start_pos; }
size_t heap_ptrmap_start_pos() const { return _heap_ptrmap_start_pos; }
size_t rw_ptrmap_start_pos() const { return _rw_ptrmap_start_pos; }
@ -554,6 +555,7 @@ public:
GrowableArray<const char*>* rp_array,
unsigned int dumptime_prefix_len,
unsigned int runtime_prefix_len) NOT_CDS_RETURN_(false);
void extract_module_paths(const char* runtime_path, GrowableArray<const char*>* module_paths);
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_region_impl() NOT_CDS_JAVA_HEAP_RETURN_(false);

View File

@ -33,6 +33,7 @@
#include "cds/heapShared.hpp"
#include "cds/metaspaceShared.hpp"
#include "classfile/classLoaderData.hpp"
#include "classfile/classLoaderExt.hpp"
#include "classfile/javaClasses.inline.hpp"
#include "classfile/modules.hpp"
#include "classfile/stringTable.hpp"
@ -55,6 +56,7 @@
#include "oops/oop.inline.hpp"
#include "oops/typeArrayOop.inline.hpp"
#include "prims/jvmtiExport.hpp"
#include "runtime/arguments.hpp"
#include "runtime/fieldDescriptor.inline.hpp"
#include "runtime/init.hpp"
#include "runtime/javaCalls.hpp"
@ -875,6 +877,17 @@ void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k
return; // nothing to do
}
if (k->name()->equals("jdk/internal/module/ArchivedModuleGraph") &&
!CDSConfig::is_using_optimized_module_handling() &&
// archive was created with --module-path
ClassLoaderExt::num_module_paths() > 0) {
// ArchivedModuleGraph was created with a --module-path that's different than the runtime --module-path.
// Thus, it might contain references to modules that do not exist at runtime. We cannot use it.
log_info(cds, heap)("Skip initializing ArchivedModuleGraph subgraph: is_using_optimized_module_handling=%s num_module_paths=%d",
BOOL_TO_STR(CDSConfig::is_using_optimized_module_handling()), ClassLoaderExt::num_module_paths());
return;
}
ExceptionMark em(THREAD);
const ArchivedKlassSubGraphInfoRecord* record =
resolve_or_init_classes_for_subgraph_of(k, /*do_init=*/true, THREAD);
@ -1123,6 +1136,13 @@ bool HeapShared::archive_reachable_objects_from(int level,
// these objects that are referenced (directly or indirectly) by static fields.
ResourceMark rm;
log_error(cds, heap)("Cannot archive object of class %s", orig_obj->klass()->external_name());
if (log_is_enabled(Trace, cds, heap)) {
WalkOopAndArchiveClosure* walker = WalkOopAndArchiveClosure::current();
if (walker != nullptr) {
LogStream ls(Log(cds, heap)::trace());
CDSHeapVerifier::trace_to_root(&ls, walker->referencing_obj());
}
}
MetaspaceShared::unrecoverable_writing_error();
}

View File

@ -301,6 +301,7 @@ void MetaspaceShared::post_initialize(TRAPS) {
}
ClassLoaderExt::init_paths_start_index(info->app_class_paths_start_index());
ClassLoaderExt::init_app_module_paths_start_index(info->app_module_paths_start_index());
ClassLoaderExt::init_num_module_paths(info->header()->num_module_paths());
}
}
}
@ -792,6 +793,9 @@ void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS
// Do this at the very end, when no Java code will be executed. Otherwise
// some new strings may be added to the intern table.
StringTable::allocate_shared_strings_array(CHECK);
} else {
log_info(cds)("Not dumping heap, reset CDSConfig::_is_using_optimized_module_handling");
CDSConfig::stop_using_optimized_module_handling();
}
#endif

View File

@ -582,6 +582,8 @@ void ClassLoader::setup_module_search_path(JavaThread* current, const char* path
new_entry = create_class_path_entry(current, path, &st,
false /*is_boot_append */, false /* from_class_path_attr */);
if (new_entry != nullptr) {
// ClassLoaderExt::process_module_table() filters out non-jar entries before calling this function.
assert(new_entry->is_jar_file(), "module path entry %s is not a jar file", new_entry->name());
add_to_module_path_entries(path, new_entry);
}
}

View File

@ -55,6 +55,7 @@
jshort ClassLoaderExt::_app_class_paths_start_index = ClassLoaderExt::max_classpath_index;
jshort ClassLoaderExt::_app_module_paths_start_index = ClassLoaderExt::max_classpath_index;
jshort ClassLoaderExt::_max_used_path_index = 0;
int ClassLoaderExt::_num_module_paths = 0;
bool ClassLoaderExt::_has_app_classes = false;
bool ClassLoaderExt::_has_platform_classes = false;
bool ClassLoaderExt::_has_non_jar_in_classpath = false;
@ -89,21 +90,25 @@ void ClassLoaderExt::setup_app_search_path(JavaThread* current) {
os::free(app_class_path);
}
int ClassLoaderExt::compare_module_path_by_name(const char** p1, const char** p2) {
return strcmp(*p1, *p2);
}
void ClassLoaderExt::process_module_table(JavaThread* current, ModuleEntryTable* met) {
ResourceMark rm(current);
GrowableArray<char*>* module_paths = new GrowableArray<char*>(5);
GrowableArray<const char*>* module_paths = new GrowableArray<const char*>(5);
class ModulePathsGatherer : public ModuleClosure {
JavaThread* _current;
GrowableArray<char*>* _module_paths;
GrowableArray<const char*>* _module_paths;
public:
ModulePathsGatherer(JavaThread* current, GrowableArray<char*>* module_paths) :
ModulePathsGatherer(JavaThread* current, GrowableArray<const char*>* module_paths) :
_current(current), _module_paths(module_paths) {}
void do_module(ModuleEntry* m) {
char* uri = m->location()->as_C_string();
if (strncmp(uri, "file:", 5) == 0) {
char* path = ClassLoader::uri_to_path(uri);
_module_paths->append(path);
extract_jar_files_from_path(path, _module_paths);
}
}
};
@ -114,6 +119,10 @@ void ClassLoaderExt::process_module_table(JavaThread* current, ModuleEntryTable*
met->modules_do(&gatherer);
}
// Sort the module paths before storing into CDS archive for simpler
// checking at runtime.
module_paths->sort(compare_module_path_by_name);
for (int i = 0; i < module_paths->length(); i++) {
ClassLoader::setup_module_search_path(current, module_paths->at(i));
}
@ -129,6 +138,38 @@ void ClassLoaderExt::setup_module_paths(JavaThread* current) {
process_module_table(current, met);
}
bool ClassLoaderExt::has_jar_suffix(const char* filename) {
// In jdk.internal.module.ModulePath.readModule(), it checks for the ".jar" suffix.
// Performing the same check here.
const char* dot = strrchr(filename, '.');
if (dot != nullptr && strcmp(dot + 1, "jar") == 0) {
return true;
}
return false;
}
void ClassLoaderExt::extract_jar_files_from_path(const char* path, GrowableArray<const char*>* module_paths) {
DIR* dirp = os::opendir(path);
if (dirp == nullptr && errno == ENOTDIR && has_jar_suffix(path)) {
module_paths->append(path);
} else {
if (dirp != nullptr) {
struct dirent* dentry;
while ((dentry = os::readdir(dirp)) != nullptr) {
const char* file_name = dentry->d_name;
if (has_jar_suffix(file_name)) {
size_t full_name_len = strlen(path) + strlen(file_name) + strlen(os::file_separator()) + 1;
char* full_name = NEW_RESOURCE_ARRAY(char, full_name_len);
int n = os::snprintf(full_name, full_name_len, "%s%s%s", path, os::file_separator(), file_name);
assert((size_t)n == full_name_len - 1, "Unexpected number of characters in string");
module_paths->append(full_name);
}
}
os::closedir(dirp);
}
}
}
char* ClassLoaderExt::read_manifest(JavaThread* current, ClassPathEntry* entry,
jint *manifest_size, bool clean_text) {
const char* name = "META-INF/MANIFEST.MF";

View File

@ -53,12 +53,15 @@ private:
static jshort _app_module_paths_start_index;
// the largest path index being used during CDS dump time
static jshort _max_used_path_index;
// number of module paths
static int _num_module_paths;
static bool _has_app_classes;
static bool _has_platform_classes;
static bool _has_non_jar_in_classpath;
static char* read_manifest(JavaThread* current, ClassPathEntry* entry, jint *manifest_size, bool clean_text);
static bool has_jar_suffix(const char* filename);
public:
static void process_jar_manifest(JavaThread* current, ClassPathEntry* entry);
@ -68,6 +71,8 @@ public:
static void setup_search_paths(JavaThread* current);
static void setup_module_paths(JavaThread* current);
static void extract_jar_files_from_path(const char* path, GrowableArray<const char*>* module_paths);
static int compare_module_path_by_name(const char** p1, const char** p2);
static char* read_manifest(JavaThread* current, ClassPathEntry* entry, jint *manifest_size) {
// Remove all the new-line continuations (which wrap long lines at 72 characters, see
@ -87,6 +92,8 @@ public:
static jshort max_used_path_index() { return _max_used_path_index; }
static int num_module_paths() { return _num_module_paths; }
static void set_max_used_path_index(jshort used_index) {
_max_used_path_index = used_index;
}
@ -99,6 +106,10 @@ public:
_app_module_paths_start_index = module_start;
}
static void init_num_module_paths(int num_module_paths) {
_num_module_paths = num_module_paths;
}
static bool is_boot_classpath(int classpath_index) {
return classpath_index < _app_class_paths_start_index;
}

View File

@ -336,6 +336,11 @@ bool Arguments::is_internal_module_property(const char* property) {
return false;
}
// Return true if the key matches the --module-path property name ("jdk.module.path").
bool Arguments::is_module_path_property(const char* key) {
return (strcmp(key, MODULE_PROPERTY_PREFIX PATH) == 0);
}
// Process java launcher properties.
void Arguments::process_sun_java_launcher_properties(JavaVMInitArgs* args) {
// See if sun.java.launcher or sun.java.launcher.is_altjvm is defined.

View File

@ -461,6 +461,7 @@ class Arguments : AllStatic {
static int PropertyList_readable_count(SystemProperty* pl);
static bool is_internal_module_property(const char* option);
static bool is_module_path_property(const char* key);
// Miscellaneous System property value getter and setters.
static void set_dll_dir(const char *value) { _sun_boot_library_path->set_value(value); }

View File

@ -1084,5 +1084,8 @@ public class BuiltinClassLoader
private void resetArchivedStates() {
ucp = null;
resourceCache = null;
if (!moduleToReader.isEmpty()) {
moduleToReader.clear();
}
}
}

View File

@ -210,13 +210,6 @@ public class ClassLoaders {
protected Package defineOrCheckPackage(String pn, Manifest man, URL url) {
return super.defineOrCheckPackage(pn, man, url);
}
/**
* Called by the VM, during -Xshare:dump
*/
private void resetArchivedStates() {
setClassPath(null);
}
}
/**

View File

@ -33,6 +33,7 @@ import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
@ -139,7 +140,6 @@ public final class ModuleBootstrap {
*/
private static boolean canUseArchivedBootLayer() {
return getProperty("jdk.module.upgrade.path") == null &&
getProperty("jdk.module.path") == null &&
getProperty("jdk.module.patch.0") == null && // --patch-module
getProperty("jdk.module.addmods.0") == null && // --add-modules
getProperty("jdk.module.limitmods") == null && // --limit-modules
@ -203,7 +203,8 @@ public final class ModuleBootstrap {
SystemModules systemModules = null;
ModuleFinder systemModuleFinder;
boolean haveModulePath = (appModulePath != null || upgradeModulePath != null);
boolean haveUpgradeModulePath = (upgradeModulePath != null);
boolean haveModulePath = (appModulePath != null || haveUpgradeModulePath);
boolean needResolution = true;
boolean mayContainSplitPackages = true;
boolean mayContainIncubatorModules = true;
@ -463,7 +464,10 @@ public final class ModuleBootstrap {
// Step 8: CDS dump phase
if (CDS.isDumpingStaticArchive() && !haveModulePath && addModules.isEmpty()) {
if (CDS.isDumpingStaticArchive()
&& !haveUpgradeModulePath
&& addModules.isEmpty()
&& allJrtOrModularJar(cf)) {
assert !isPatched;
// Archive module graph and maybe boot layer
@ -510,6 +514,29 @@ public final class ModuleBootstrap {
}
}
/**
* Returns true if all modules in the configuration are in the run-time image or
* modular JAR files.
*/
private static boolean allJrtOrModularJar(Configuration cf) {
return !cf.modules().stream()
.map(m -> m.reference().location().orElseThrow())
.anyMatch(uri -> !uri.getScheme().equalsIgnoreCase("jrt")
&& !isJarFile(uri));
}
/**
* Returns true if the given URI locates a jar file on the file system.
*/
private static boolean isJarFile(URI uri) {
if ("file".equalsIgnoreCase(uri.getScheme())) {
Path path = Path.of(uri);
return path.toString().endsWith(".jar") && Files.isRegularFile(path);
} else {
return false;
}
}
/**
* Returns true if the configuration contains modules with overlapping packages.
*/

View File

@ -91,8 +91,19 @@ class ModuleReferences {
ModulePatcher patcher,
Path file) {
URI uri = file.toUri();
Supplier<ModuleReader> supplier = () -> new JarModuleReader(file, uri);
HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a);
String fileString = file.toString();
Supplier<ModuleReader> supplier = new Supplier<>() {
@Override
public ModuleReader get() {
return new JarModuleReader(fileString, uri);
}
};
HashSupplier hasher = new HashSupplier() {
@Override
public byte[] generate(String algorithm) {
return ModuleHashes.computeHash(supplier, algorithm);
}
};
return newModule(attrs, uri, supplier, patcher, hasher);
}
@ -222,9 +233,9 @@ class ModuleReferences {
private final JarFile jf;
private final URI uri;
static JarFile newJarFile(Path path) {
static JarFile newJarFile(String path) {
try {
return new JarFile(new File(path.toString()),
return new JarFile(new File(path),
true, // verify
ZipFile.OPEN_READ,
JarFile.runtimeVersion());
@ -233,7 +244,7 @@ class ModuleReferences {
}
}
JarModuleReader(Path path, URI uri) {
JarModuleReader(String path, URI uri) {
this.jf = newJarFile(path);
this.uri = uri;
}

View File

@ -438,6 +438,8 @@ hotspot_appcds_dynamic = \
-runtime/cds/appcds/complexURI \
-runtime/cds/appcds/customLoader \
-runtime/cds/appcds/dynamicArchive \
-runtime/cds/appcds/jigsaw/modulepath/ModulePathAndFMG.java \
-runtime/cds/appcds/jigsaw/modulepath/OptimizeModuleHandlingTest.java \
-runtime/cds/appcds/loaderConstraints/DynamicLoaderConstraintsTest.java \
-runtime/cds/appcds/javaldr/ArrayTest.java \
-runtime/cds/appcds/javaldr/ExceptionDuringDumpAtObjectsInitPhase.java \

View File

@ -213,8 +213,10 @@ public class MainModuleOnly extends DynamicArchiveTestBase {
"-cp", destJar.toString(),
"--module-path", MODS_DIR.toString(),
"-m", TEST_MODULE1 + "/" + MAIN_CLASS)
.assertAbnormalExit(output -> {
output.shouldMatch("Error: non-empty directory.*com.simple");
// After JDK-8328313, non-empty module path directory won't be included
// in the shared paths table.
.assertNormalExit(output -> {
output.shouldNotMatch("Error: non-empty directory.*com.simple");
});
// test module path with very long length

View File

@ -194,8 +194,10 @@ public class MainModuleOnly {
output = TestCommon.createArchive(destJar.toString(), appClasses,
"--module-path", MODS_DIR.toString(),
"-m", mainModule);
output.shouldHaveExitValue(1)
.shouldMatch("Error: non-empty directory.*com.simple");
// After JDK-8328313, non-empty module path directory won't be included
// in the shared paths table.
output.shouldHaveExitValue(0)
.shouldNotMatch("Error: non-empty directory.*com.simple");
// test module path with very long length
//

View File

@ -0,0 +1,385 @@
/*
* Copyright (c) 2024, 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 8328313
* @requires vm.cds & !vm.graal.enabled & vm.cds.write.archived.java.heap
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
* @run driver ModulePathAndFMG
* @summary test module path changes for full module graph handling.
*
*/
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.cds.CDSTestUtils;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
public class ModulePathAndFMG {
private static final String JAVA_HOME = System.getProperty("java.home");
private static final Path USER_DIR = Paths.get(CDSTestUtils.getOutputDir());
private static final String TEST_SRC = System.getProperty("test.src");
private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
private static final Path MODS_DIR = Paths.get("mody");
private static final Path JMOD_DIR = Paths.get("jmod_dir");
// the module name of the test module
private static final String MAIN_MODULE = "com.bars";
private static final String TEST_MODULE = "com.foos";
private static final String DUP_MODULE = "com.foos3";
// the module main class
private static final String MAIN_CLASS = "com.bars.Main";
private static final String TEST_CLASS = "com.foos.Test";
private static String PATH_LIBS = "modylibs";
private static String DUP_LIBS = "duplibs";
private static Path libsDir = null;
private static Path dupDir = null;
private static Path jmodDir = null;
private static Path mainJar = null;
private static Path testJar = null;
private static Path dupJar = null;
private static Path badJar = null;
private static String CLASS_FOUND_MESSAGE = "com.foos.Test found";
private static String CLASS_NOT_FOUND_MESSAGE = "java.lang.ClassNotFoundException: com.foos.Test";
private static String FIND_EXCEPTION_MESSAGE = "java.lang.module.FindException: Module com.foos not found, required by com.bars";
private static String MODULE_NOT_RECOGNIZED = "Module format not recognized:.*modylibs.*com.bars.JAR";
private static String OPTIMIZE_ENABLED = "] optimized module handling: enabled";
private static String OPTIMIZE_DISABLED = "] optimized module handling: disabled";
private static String FMG_ENABLED = "] full module graph: enabled";
private static String FMG_DISABLED = "] full module graph: disabled";
private static String MAIN_FROM_JAR = "class,load.*com.bars.Main.*[.]jar";
private static String MAIN_FROM_CDS = "class,load.*com.bars.Main.*shared objects file";
private static String MAIN_FROM_MODULE = "class,load.*com.bars.Main.*mody/com.bars";
private static String TEST_FROM_JAR = "class,load.*com.foos.Test.*[.]jar";
private static String TEST_FROM_CDS = "class,load.*com.foos.Test.*shared objects file";
private static String MAP_FAILED = "Unable to use shared archive";
private static String PATH_SEPARATOR = File.pathSeparator;
private static String appClasses[] = {MAIN_CLASS, TEST_CLASS};
private static String prefix[] = {"-Djava.class.path=", "-Xlog:cds,class+load,class+path=info"};
public static void buildTestModule() throws Exception {
// javac -d mods/$TESTMODULE src/$TESTMODULE/**
JarBuilder.compileModule(SRC_DIR.resolve(TEST_MODULE),
MODS_DIR.resolve(TEST_MODULE),
null);
// javac -d mods/$TESTMODULE --module-path MOD_DIR src/$TESTMODULE/**
JarBuilder.compileModule(SRC_DIR.resolve(MAIN_MODULE),
MODS_DIR.resolve(MAIN_MODULE),
MODS_DIR.toString());
libsDir = Files.createTempDirectory(USER_DIR, PATH_LIBS);
mainJar = libsDir.resolve(MAIN_MODULE + ".jar");
testJar = libsDir.resolve(TEST_MODULE + ".jar");
// modylibs contains both modules com.foos.jar, com.bars.jar
// build com.foos.jar
String classes = MODS_DIR.resolve(TEST_MODULE).toString();
JarBuilder.createModularJar(testJar.toString(), classes, TEST_CLASS);
// build com.bars.jar
classes = MODS_DIR.resolve(MAIN_MODULE).toString();
JarBuilder.createModularJar(mainJar.toString(), classes, MAIN_CLASS);
dupDir = Files.createTempDirectory(USER_DIR, DUP_LIBS);
dupJar = dupDir.resolve(DUP_MODULE + ".jar");
Files.copy(testJar, dupJar, StandardCopyOption.REPLACE_EXISTING);
badJar = libsDir.resolve(MAIN_MODULE + ".JAR");
Files.copy(mainJar, badJar, StandardCopyOption.REPLACE_EXISTING);
}
public static void buildJmod() throws Exception {
Path jmod = Paths.get(JAVA_HOME, "bin", "jmod");
jmodDir = Files.createDirectory(Paths.get(USER_DIR.toString() + File.separator + JMOD_DIR.toString()));
OutputAnalyzer output = ProcessTools.executeProcess(jmod.toString(),
"create",
"--class-path", Paths.get(USER_DIR.toString(), MODS_DIR.toString(), TEST_MODULE).toString(),
"--module-version", "1.0",
"--main-class", TEST_CLASS,
jmodDir.toString() + File.separator + TEST_MODULE + ".jmod");
output.shouldHaveExitValue(0);
}
public static void main(String... args) throws Exception {
runWithModulePath();
runWithExplodedModule();
runWithJmodAndBadJar();
}
private static void tty(String... args) {
for (String s : args) {
System.out.print(s + " ");
}
System.out.print("\n");
}
public static void runWithModulePath() throws Exception {
// compile the modules and create the modular jar files
buildTestModule();
// create an archive with the classes in the modules built in the
// previous step
OutputAnalyzer output = TestCommon.createArchive(
null, appClasses,
"--module-path",
libsDir.toString(),
"-m", MAIN_MODULE);
TestCommon.checkDump(output);
tty("1. run with CDS on, with module path same as dump time");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
libsDir.toString(), // --module-path
MAIN_MODULE) // -m
.assertNormalExit(out -> {
out.shouldNotContain(OPTIMIZE_DISABLED)
.shouldContain(OPTIMIZE_ENABLED)
.shouldNotContain(FMG_DISABLED)
.shouldContain(FMG_ENABLED)
.shouldMatch(MAIN_FROM_CDS) // archived Main class is for module only
.shouldContain(CLASS_FOUND_MESSAGE);
});
tty("2. run with CDS on, with jar on path");
TestCommon.run("-Xlog:cds",
"-Xlog:class+load",
"-cp", mainJar.toString() + PATH_SEPARATOR + testJar.toString(),
MAIN_CLASS)
.assertNormalExit(out -> {
out.shouldContain(CLASS_FOUND_MESSAGE)
.shouldMatch(MAIN_FROM_JAR)
.shouldMatch(TEST_FROM_JAR)
.shouldContain(OPTIMIZE_DISABLED)
.shouldNotContain(OPTIMIZE_ENABLED)
.shouldContain(FMG_DISABLED)
.shouldNotContain(FMG_ENABLED);
});
tty("3. run with CDS on, with --module-path, with jar should fail");
TestCommon.run("-Xlog:cds",
"-Xlog:class+load",
"-p", libsDir.toString(),
"-cp", mainJar.toString(),
MAIN_CLASS)
.assertNormalExit(out -> {
out.shouldContain(CLASS_NOT_FOUND_MESSAGE)
.shouldMatch(MAIN_FROM_JAR)
.shouldNotContain(FMG_ENABLED)
.shouldNotContain(OPTIMIZE_ENABLED);
});
final String modularJarPath = mainJar.toString() + PATH_SEPARATOR + testJar.toString();
tty("4. run with CDS on, with modular jars specified --module-path, should pass");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
modularJarPath, // --module-path
MAIN_MODULE) // -m
.assertNormalExit(out -> {
out.shouldNotContain(OPTIMIZE_DISABLED)
.shouldContain(OPTIMIZE_ENABLED)
.shouldNotContain(FMG_DISABLED)
.shouldContain(FMG_ENABLED)
.shouldMatch(MAIN_FROM_CDS); // archived Main class is for module only
});
final String extraModulePath = libsDir.toString() + PATH_SEPARATOR + dupDir.toString();
// create an archive with an extra module which is not referenced
output = TestCommon.createArchive(
null, appClasses,
"--module-path",
extraModulePath,
"-m", MAIN_MODULE);
TestCommon.checkDump(output);
tty("5. run with CDS on, without the extra module specified in dump time, should pass");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
libsDir.toString(), // --module-path
MAIN_MODULE) // -m
.assertNormalExit(out -> {
out.shouldNotContain(OPTIMIZE_DISABLED)
.shouldContain(OPTIMIZE_ENABLED)
.shouldNotContain(FMG_DISABLED)
.shouldContain(FMG_ENABLED)
.shouldMatch(MAIN_FROM_CDS) // archived Main class is for module only
.shouldContain(CLASS_FOUND_MESSAGE);
});
tty("6. run with CDS on, with the extra module specified in dump time");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
extraModulePath, // --module-path
MAIN_MODULE) // -m
.assertNormalExit(out -> {
out.shouldNotContain(OPTIMIZE_ENABLED)
.shouldContain(OPTIMIZE_DISABLED)
.shouldNotContain(FMG_ENABLED)
.shouldContain(FMG_DISABLED)
.shouldMatch(MAIN_FROM_CDS) // archived Main class is for module only
.shouldContain(CLASS_FOUND_MESSAGE);
});
final String extraJarPath = modularJarPath + PATH_SEPARATOR + dupJar.toString();
// create an archive by specifying modular jars in the --module-path with an extra module which is not referenced
output = TestCommon.createArchive(
null, appClasses,
"--module-path",
extraJarPath,
"-m", MAIN_MODULE);
TestCommon.checkDump(output);
tty("7. run with CDS on, without the extra module specified in dump time, should pass");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
modularJarPath, // --module-path
MAIN_MODULE) // -m
.assertNormalExit(out -> {
out.shouldNotContain(OPTIMIZE_DISABLED)
.shouldContain(OPTIMIZE_ENABLED)
.shouldNotContain(FMG_DISABLED)
.shouldContain(FMG_ENABLED)
.shouldMatch(MAIN_FROM_CDS) // archived Main class is for module only
.shouldContain(CLASS_FOUND_MESSAGE);
});
tty("8. run with CDS on, with the extra module specified in dump time");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
extraJarPath, // --module-path
MAIN_MODULE) // -m
.assertNormalExit(out -> {
out.shouldNotContain(OPTIMIZE_ENABLED)
.shouldContain(OPTIMIZE_DISABLED)
.shouldNotContain(FMG_ENABLED)
.shouldContain(FMG_DISABLED)
.shouldMatch(MAIN_FROM_CDS) // archived Main class is for module only
.shouldContain(CLASS_FOUND_MESSAGE);
});
tty("9. same as test case 8 but with paths instead of modular jars in the --module-path");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
extraModulePath, // --module-path
MAIN_MODULE) // -m
.assertNormalExit(out -> {
out.shouldNotContain(OPTIMIZE_ENABLED)
.shouldContain(OPTIMIZE_DISABLED)
.shouldNotContain(FMG_ENABLED)
.shouldContain(FMG_DISABLED)
.shouldMatch(MAIN_FROM_CDS) // archived Main class is for module only
.shouldContain(CLASS_FOUND_MESSAGE);
});
}
public static void runWithExplodedModule() throws Exception {
// create an archive with an exploded module in the module path.
OutputAnalyzer output = TestCommon.createArchive(
null, appClasses,
"--module-path",
MODS_DIR.toString(),
"-m", MAIN_MODULE + "/" + MAIN_CLASS);
TestCommon.checkDump(output);
tty("10. run with CDS on, with exploded module in the module path");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
MODS_DIR.toString(), // --module-path
MAIN_MODULE + "/" + MAIN_CLASS) // -m
.assertNormalExit(out -> {
out.shouldContain(FMG_DISABLED)
.shouldMatch(MAIN_FROM_MODULE) // Main class loaded from the exploded module
.shouldContain(CLASS_FOUND_MESSAGE);
});
}
public static void runWithJmodAndBadJar() throws Exception {
buildJmod();
final String modularJarPath = mainJar.toString() + PATH_SEPARATOR + testJar.toString();
// create an archive with --module-path com.bars.jar:com.foos.jar
OutputAnalyzer output = TestCommon.createArchive(
null, appClasses,
"--module-path",
modularJarPath,
"-m", MAIN_MODULE);
TestCommon.checkDump(output);
String runModulePath = mainJar.toString() + PATH_SEPARATOR +
jmodDir.toString() + TEST_MODULE + ".jmod";
tty("11. run with CDS on, with module path com.bars.jar:com.foos.jmod");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
runModulePath, // --module-path
MAIN_MODULE) // -m
.assertAbnormalExit(out -> {
out.shouldContain(OPTIMIZE_DISABLED)
.shouldNotContain(OPTIMIZE_ENABLED)
.shouldContain(FMG_DISABLED)
.shouldNotContain(FMG_ENABLED)
.shouldContain(FIND_EXCEPTION_MESSAGE);
});
runModulePath += PATH_SEPARATOR + testJar.toString();
tty("12. run with CDS on, with module path com.bars.jar:com.foos.jmod:com.foos.jar");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
runModulePath, // --module-path
MAIN_MODULE) // -m
.assertNormalExit(out -> {
out.shouldNotContain(OPTIMIZE_DISABLED)
.shouldContain(OPTIMIZE_ENABLED)
.shouldNotContain(FMG_DISABLED)
.shouldContain(FMG_ENABLED)
.shouldMatch(TEST_FROM_CDS)
.shouldMatch(MAIN_FROM_CDS)
.shouldContain(CLASS_FOUND_MESSAGE);
});
runModulePath = badJar.toString() + PATH_SEPARATOR + testJar.toString();
tty("13. run with CDS on, with module path com.bars.JAR:com.foos.jar");
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
runModulePath, // --module-path
TEST_MODULE) // -m
.assertAbnormalExit(out -> {
out.shouldContain(OPTIMIZE_DISABLED)
.shouldNotContain(OPTIMIZE_ENABLED)
.shouldContain(FMG_DISABLED)
.shouldNotContain(FMG_ENABLED)
.shouldMatch(MODULE_NOT_RECOGNIZED);
});
}
}

View File

@ -24,7 +24,7 @@
/**
* @test
* @requires vm.cds & !vm.graal.enabled
* @requires vm.cds & !vm.graal.enabled & vm.cds.write.archived.java.heap
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
* @run driver OptimizeModuleHandlingTest
* @summary test module path changes for optimization of
@ -64,8 +64,8 @@ public class OptimizeModuleHandlingTest {
private static String CLASS_FOUND_MESSAGE = "com.foos.Test found";
private static String CLASS_NOT_FOUND_MESSAGE = "java.lang.ClassNotFoundException: com.foos.Test";
private static String OPTIMIZE_ENABLED = "optimized module handling: enabled";
private static String OPTIMIZE_DISABLED = "optimized module handling: disabled";
private static String OPTIMIZE_ENABLED = "] optimized module handling: enabled";
private static String OPTIMIZE_DISABLED = "] optimized module handling: disabled";
private static String MAIN_FROM_JAR = "class,load.*com.bars.Main.*[.]jar";
private static String MAIN_FROM_CDS = "class,load.*com.bars.Main.*shared objects file";
private static String TEST_FROM_JAR = "class,load.*com.foos.Test.*[.]jar";
@ -154,14 +154,14 @@ public class OptimizeModuleHandlingTest {
// Following 5 - 10 test with CDS on
tty("5. run with CDS on, with module path");
String prefix[] = {"-Djava.class.path=", "-Xlog:cds", "-Xlog:class+load"};
String prefix[] = {"-Djava.class.path=", "-Xlog:cds,class+load,class+path=info"};
TestCommon.runWithModules(prefix,
null, // --upgrade-module-path
libsDir.toString(), // --module-path
MAIN_MODULE) // -m
.assertNormalExit(out -> {
out.shouldNotContain(OPTIMIZE_ENABLED)
.shouldContain(OPTIMIZE_DISABLED)
out.shouldNotContain(OPTIMIZE_DISABLED)
.shouldContain(OPTIMIZE_ENABLED)
.shouldMatch(MAIN_FROM_CDS) // // archived Main class is for module only
.shouldContain(CLASS_FOUND_MESSAGE);
});
@ -174,8 +174,8 @@ public class OptimizeModuleHandlingTest {
out.shouldContain(CLASS_FOUND_MESSAGE)
.shouldMatch(MAIN_FROM_CDS)
.shouldMatch(TEST_FROM_CDS)
.shouldContain(OPTIMIZE_DISABLED)
.shouldNotContain(OPTIMIZE_ENABLED);
.shouldContain(OPTIMIZE_ENABLED)
.shouldNotContain(OPTIMIZE_DISABLED);
});
tty("7. run with CDS on, with jar on path");
TestCommon.run("-Xlog:cds",
@ -231,7 +231,6 @@ public class OptimizeModuleHandlingTest {
MAIN_CLASS)
.assertAbnormalExit(out -> {
out.shouldNotContain(CLASS_FOUND_MESSAGE)
.shouldContain(OPTIMIZE_DISABLED) // mapping info
.shouldContain("shared class paths mismatch");
});
}