8295102: Always load @lambda-form-invoker lines from default classlist

Reviewed-by: redestad, ccheung
This commit is contained in:
Ioi Lam 2022-10-13 00:01:06 +00:00
parent ac1941425b
commit 90fb9a085b
7 changed files with 242 additions and 39 deletions

@ -54,7 +54,9 @@
volatile Thread* ClassListParser::_parsing_thread = NULL;
ClassListParser* ClassListParser::_instance = NULL;
ClassListParser::ClassListParser(const char* file) : _id2klass_table(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE) {
ClassListParser::ClassListParser(const char* file, ParseMode parse_mode) : _id2klass_table(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE) {
log_info(cds)("Parsing %s%s", file,
(parse_mode == _parse_lambda_forms_invokers_only) ? " (lambda form invokers only)" : "");
_classlist_file = file;
_file = NULL;
// Use os::open() because neither fopen() nor os::fopen()
@ -73,6 +75,7 @@ ClassListParser::ClassListParser(const char* file) : _id2klass_table(INITIAL_TAB
_line_no = 0;
_interfaces = new (ResourceObj::C_HEAP, mtClass) GrowableArray<int>(10, mtClass);
_indy_items = new (ResourceObj::C_HEAP, mtClass) GrowableArray<const char*>(9, mtClass);
_parse_mode = parse_mode;
// _instance should only be accessed by the thread that created _instance.
assert(_instance == NULL, "must be singleton");
@ -104,6 +107,10 @@ int ClassListParser::parse(TRAPS) {
continue;
}
if (_parse_mode == _parse_lambda_forms_invokers_only) {
continue;
}
TempNewSymbol class_name_symbol = SymbolTable::new_symbol(_class_name);
if (_indy_items->length() > 0) {
// The current line is "@lambda-proxy class_name". Load the proxy class.

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -67,6 +67,13 @@ public:
};
class ClassListParser : public StackObj {
public:
enum ParseMode {
_parse_all,
_parse_lambda_forms_invokers_only,
};
private:
// Must be C_HEAP allocated -- we don't want nested resource allocations.
typedef ResizeableResourceHashtable<int, InstanceKlass*,
ResourceObj::C_HEAP, mtClassShared> ID2KlassTable;
@ -107,6 +114,7 @@ class ClassListParser : public StackObj {
bool _interfaces_specified;
const char* _source;
bool _lambda_form_line;
ParseMode _parse_mode;
bool parse_int_option(const char* option_name, int* value);
bool parse_uint_option(const char* option_name, int* value);
@ -124,10 +132,15 @@ class ClassListParser : public StackObj {
bool parse_one_line();
Klass* load_current_class(Symbol* class_name_symbol, TRAPS);
public:
ClassListParser(const char* file);
ClassListParser(const char* file, ParseMode _parse_mode);
~ClassListParser();
public:
static int parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) {
ClassListParser parser(classlist_path, parse_mode);
return parser.parse(THREAD); // returns the number of classes loaded.
}
static bool is_parsing_thread();
static ClassListParser* instance() {
assert(is_parsing_thread(), "call this only in the thread that created ClassListParsing::_instance");

@ -733,35 +733,39 @@ void MetaspaceShared::adjust_heap_sizes_for_dumping() {
}
#endif // INCLUDE_CDS_JAVA_HEAP && _LP64
void MetaspaceShared::get_default_classlist(char* default_classlist, const size_t buf_size) {
// Construct the path to the class list (in jre/lib)
// Walk up two directories from the location of the VM and
// optionally tack on "lib" (depending on platform)
os::jvm_path(default_classlist, (jint)(buf_size));
for (int i = 0; i < 3; i++) {
char *end = strrchr(default_classlist, *os::file_separator());
if (end != NULL) *end = '\0';
}
size_t classlist_path_len = strlen(default_classlist);
if (classlist_path_len >= 3) {
if (strcmp(default_classlist + classlist_path_len - 3, "lib") != 0) {
if (classlist_path_len < buf_size - 4) {
jio_snprintf(default_classlist + classlist_path_len,
buf_size - classlist_path_len,
"%slib", os::file_separator());
classlist_path_len += 4;
}
}
}
if (classlist_path_len < buf_size - 10) {
jio_snprintf(default_classlist + classlist_path_len,
buf_size - classlist_path_len,
"%sclasslist", os::file_separator());
}
}
void MetaspaceShared::preload_classes(TRAPS) {
char default_classlist[JVM_MAXPATHLEN];
const char* classlist_path;
get_default_classlist(default_classlist, sizeof(default_classlist));
if (SharedClassListFile == NULL) {
// Construct the path to the class list (in jre/lib)
// Walk up two directories from the location of the VM and
// optionally tack on "lib" (depending on platform)
os::jvm_path(default_classlist, sizeof(default_classlist));
for (int i = 0; i < 3; i++) {
char *end = strrchr(default_classlist, *os::file_separator());
if (end != NULL) *end = '\0';
}
int classlist_path_len = (int)strlen(default_classlist);
if (classlist_path_len >= 3) {
if (strcmp(default_classlist + classlist_path_len - 3, "lib") != 0) {
if (classlist_path_len < JVM_MAXPATHLEN - 4) {
jio_snprintf(default_classlist + classlist_path_len,
sizeof(default_classlist) - classlist_path_len,
"%slib", os::file_separator());
classlist_path_len += 4;
}
}
}
if (classlist_path_len < JVM_MAXPATHLEN - 10) {
jio_snprintf(default_classlist + classlist_path_len,
sizeof(default_classlist) - classlist_path_len,
"%sclasslist", os::file_separator());
}
classlist_path = default_classlist;
} else {
classlist_path = SharedClassListFile;
@ -769,9 +773,19 @@ void MetaspaceShared::preload_classes(TRAPS) {
log_info(cds)("Loading classes to share ...");
_has_error_classes = false;
int class_count = parse_classlist(classlist_path, CHECK);
int class_count = ClassListParser::parse_classlist(classlist_path,
ClassListParser::_parse_all, CHECK);
if (ExtraSharedClassListFile) {
class_count += parse_classlist(ExtraSharedClassListFile, CHECK);
class_count += ClassListParser::parse_classlist(ExtraSharedClassListFile,
ClassListParser::_parse_all, CHECK);
}
if (classlist_path != default_classlist) {
struct stat statbuf;
if (os::stat(default_classlist, &statbuf) == 0) {
// File exists, let's use it.
class_count += ClassListParser::parse_classlist(default_classlist,
ClassListParser::_parse_lambda_forms_invokers_only, CHECK);
}
}
// Exercise the manifest processing code to ensure classes used by CDS at runtime
@ -814,12 +828,6 @@ void MetaspaceShared::preload_and_dump_impl(TRAPS) {
VMThread::execute(&op);
}
int MetaspaceShared::parse_classlist(const char* classlist_path, TRAPS) {
ClassListParser parser(classlist_path);
return parser.parse(THREAD); // returns the number of classes loaded.
}
// Returns true if the class's status has changed.
bool MetaspaceShared::try_link_class(JavaThread* current, InstanceKlass* ik) {
ExceptionMark em(current);

@ -88,9 +88,6 @@ class MetaspaceShared : AllStatic {
private:
static void preload_and_dump_impl(TRAPS) NOT_CDS_RETURN;
static void preload_classes(TRAPS) NOT_CDS_RETURN;
static int parse_classlist(const char * classlist_path,
TRAPS) NOT_CDS_RETURN_(0);
public:
static Symbol* symbol_rs_base() {
@ -202,5 +199,6 @@ private:
ReservedSpace& class_space_rs);
static MapArchiveResult map_archive(FileMapInfo* mapinfo, char* mapped_base_address, ReservedSpace rs);
static void unmap_archive(FileMapInfo* mapinfo);
static void get_default_classlist(char* default_classlist, const size_t buf_size);
};
#endif // SHARE_CDS_METASPACESHARED_HPP

@ -423,6 +423,7 @@ hotspot_appcds_dynamic = \
-runtime/cds/appcds/jcmd/JCmdTestStaticDump.java \
-runtime/cds/appcds/jcmd/JCmdTestDynamicDump.java \
-runtime/cds/appcds/jcmd/JCmdTestFileSafety.java \
-runtime/cds/appcds/lambdaForm/DefaultClassListLFInvokers.java \
-runtime/cds/appcds/methodHandles \
-runtime/cds/appcds/sharedStrings \
-runtime/cds/appcds/ArchiveRelocationTest.java \

@ -35,6 +35,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
@ -745,4 +746,37 @@ public class TestCommon extends CDSTestUtils {
}
return sb.toString();
}
public static void filesMustMatch(Path a, Path b) throws IOException {
linesMustMatch(Files.readString(a).split("\n"),
Files.readString(b).split("\n"));
}
public static void linesMustMatch(String a[], String b[]) {
int limit = Math.min(a.length, b.length);
// Check the lines that are in both a[] and b[]
for (int i = 0; i < limit; i++) {
if (!a[i].equals(b[i])) {
System.out.println("a:" + i + " " + a[i]);
System.out.println("b:" + i + " " + b[i]);
throw new RuntimeException("Output mismatch on line " + i
+ ": a=" + a[i]
+ ", b=" + b[i]);
}
}
// Report the first line that is in one array but not in the other
if (a.length > b.length) {
throw new RuntimeException("Output mismatch on line " + limit
+ ": a=" + a[limit]
+ ", b=<none>");
}
if (a.length < b.length) {
throw new RuntimeException("Output mismatch on line " + limit
+ ": a=<none>"
+ ", b=" + b[limit]);
}
}
}

@ -0,0 +1,142 @@
/*
* 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 8295102
* @summary Always load the lambda-form-invoker lines from default classlist
* @requires vm.cds
* @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds
* @build DefaultClassListLFInvokers
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar
* DefaultClassListLFInvokersApp DefaultClassListLFInvokersApp$CompMethods
* @run driver DefaultClassListLFInvokers
*/
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Comparator;
import jdk.test.lib.cds.CDSOptions;
import jdk.test.lib.cds.CDSTestUtils;
import jdk.test.lib.helpers.ClassFileInstaller;
public class DefaultClassListLFInvokers {
static final String appClass = DefaultClassListLFInvokersApp.class.getName();
static final String appJar = ClassFileInstaller.getJarPath("app.jar");
static final String[] classlist = {
appClass,
// If we have at least one line of @lambda-form-invoker in the classlist, it triggers
// the regeneration of the 4 XXX$Holder during -Xshare:dump.
"@lambda-form-invoker [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L_V"
};
public static void main(String[] args) throws Exception {
File classListFile = CDSTestUtils.makeClassList(classlist);
CDSTestUtils.createArchiveAndCheck("-XX:SharedClassListFile=" + classListFile.getPath(),
"-cp", appJar);
// Make sure we still have all the LF invoker methods as when CDS is disabled,
// in which case the XXX$Holder classes are loaded from $JAVA_HOME/lib/modules
Path no_cds_logfile = run(Mode.no_cds);
Path custom_cds_logfile = run(Mode.custom_cds);
System.out.println("\n\n============================== Checking output: custom_cds vs no_cds");
TestCommon.filesMustMatch(custom_cds_logfile, no_cds_logfile);
// We should also have all the LF invoker methods as when the default CDS archive is used
// in which case the XXX$Holder classes are loaded from the default archive,
// e.g., $JAVA_HOME/lib/server/classes.jsa
Path default_cds_logfile = run(Mode.default_cds);
System.out.println("\n\n============================== Checking output: custom_cds vs default_cds");
TestCommon.filesMustMatch(custom_cds_logfile, default_cds_logfile);
}
enum Mode {
no_cds,
default_cds,
custom_cds
};
static Path run(Mode mode) throws Exception {
File f = new File("log_" + mode.name() + ".txt");
CDSOptions opts = (new CDSOptions())
.addSuffix("-showversion", "-cp", appJar, appClass, f.toString())
.setUseVersion(false);
switch (mode) {
case no_cds:
opts.setXShareMode("off");
break;
case custom_cds:
// We will use the archive created by the last CDSTestUtils.createArchiveAndCheck() call
opts.setUseSystemArchive(false);
opts.setXShareMode("auto");
break;
case default_cds:
default:
// We will use the default archive.
opts.setUseSystemArchive(true);
opts.setXShareMode("auto");
break;
}
CDSTestUtils.run(opts).assertNormalExit(DefaultClassListLFInvokersApp.FLAG);
return f.toPath();
}
}
class DefaultClassListLFInvokersApp {
public static final String FLAG = "Test Success!";
static class CompMethods implements Comparator<Method> {
public int compare(Method a, Method b) {
return a.toString().compareTo(b.toString());
}
}
static final CompMethods compMethods = new CompMethods();
public static void main(String[] args) throws Exception {
try (BufferedWriter w = new BufferedWriter(new FileWriter(args[0]))) {
test(w, "java.lang.invoke.Invokers$Holder");
test(w, "java.lang.invoke.DirectMethodHandle$Holder");
test(w, "java.lang.invoke.DelegatingMethodHandle$Holder");
test(w, "java.lang.invoke.LambdaForm$Holder");
System.out.println(FLAG);
}
}
static void test(BufferedWriter w, String className) throws Exception {
Class c = Class.forName(className);
Method[] methods = c.getDeclaredMethods();
w.write("Dumping all methods in " + c + "\n");
Arrays.sort(methods, 0, methods.length, compMethods);
for (Method m : methods) {
w.write(m + "\n");
}
w.write("Found " + methods.length + " methods\n\n\n");
}
}