8343427: Class file load hook crashes on archived classes from multi-release JARs
Reviewed-by: dholmes, iklam
This commit is contained in:
parent
f1b5a6e66e
commit
d752f19611
@ -53,15 +53,18 @@
|
|||||||
#include "memory/oopFactory.hpp"
|
#include "memory/oopFactory.hpp"
|
||||||
#include "memory/universe.hpp"
|
#include "memory/universe.hpp"
|
||||||
#include "nmt/memTracker.hpp"
|
#include "nmt/memTracker.hpp"
|
||||||
|
#include "oops/access.hpp"
|
||||||
#include "oops/compressedOops.hpp"
|
#include "oops/compressedOops.hpp"
|
||||||
#include "oops/compressedOops.inline.hpp"
|
#include "oops/compressedOops.inline.hpp"
|
||||||
#include "oops/compressedKlass.hpp"
|
#include "oops/compressedKlass.hpp"
|
||||||
#include "oops/objArrayOop.hpp"
|
#include "oops/objArrayOop.hpp"
|
||||||
#include "oops/oop.inline.hpp"
|
#include "oops/oop.inline.hpp"
|
||||||
|
#include "oops/typeArrayKlass.hpp"
|
||||||
#include "prims/jvmtiExport.hpp"
|
#include "prims/jvmtiExport.hpp"
|
||||||
#include "runtime/arguments.hpp"
|
#include "runtime/arguments.hpp"
|
||||||
#include "runtime/globals_extension.hpp"
|
#include "runtime/globals_extension.hpp"
|
||||||
#include "runtime/java.hpp"
|
#include "runtime/java.hpp"
|
||||||
|
#include "runtime/javaCalls.hpp"
|
||||||
#include "runtime/mutexLocker.hpp"
|
#include "runtime/mutexLocker.hpp"
|
||||||
#include "runtime/os.hpp"
|
#include "runtime/os.hpp"
|
||||||
#include "runtime/vm_version.hpp"
|
#include "runtime/vm_version.hpp"
|
||||||
@ -2678,11 +2681,44 @@ ClassFileStream* FileMapInfo::open_stream_for_jvmti(InstanceKlass* ik, Handle cl
|
|||||||
const char* const file_name = ClassLoader::file_name_for_class_name(class_name,
|
const char* const file_name = ClassLoader::file_name_for_class_name(class_name,
|
||||||
name->utf8_length());
|
name->utf8_length());
|
||||||
ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(class_loader());
|
ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(class_loader());
|
||||||
ClassFileStream* cfs = cpe->open_stream_for_loader(THREAD, file_name, loader_data);
|
ClassFileStream* cfs;
|
||||||
|
if (class_loader() != nullptr && !cpe->is_modules_image()) {
|
||||||
|
cfs = get_stream_from_class_loader(class_loader, cpe, file_name, CHECK_NULL);
|
||||||
|
} else {
|
||||||
|
cfs = cpe->open_stream_for_loader(THREAD, file_name, loader_data);
|
||||||
|
}
|
||||||
assert(cfs != nullptr, "must be able to read the classfile data of shared classes for built-in loaders.");
|
assert(cfs != nullptr, "must be able to read the classfile data of shared classes for built-in loaders.");
|
||||||
log_debug(cds, jvmti)("classfile data for %s [%d: %s] = %d bytes", class_name, path_index,
|
log_debug(cds, jvmti)("classfile data for %s [%d: %s] = %d bytes", class_name, path_index,
|
||||||
cfs->source(), cfs->length());
|
cfs->source(), cfs->length());
|
||||||
return cfs;
|
return cfs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClassFileStream* FileMapInfo::get_stream_from_class_loader(Handle class_loader,
|
||||||
|
ClassPathEntry* cpe,
|
||||||
|
const char* file_name,
|
||||||
|
TRAPS) {
|
||||||
|
JavaValue result(T_OBJECT);
|
||||||
|
TempNewSymbol class_name_sym = SymbolTable::new_symbol(file_name);
|
||||||
|
Handle ext_class_name = java_lang_String::externalize_classname(class_name_sym, CHECK_NULL);
|
||||||
|
|
||||||
|
// byte[] ClassLoader.getResourceAsByteArray(String name)
|
||||||
|
JavaCalls::call_virtual(&result,
|
||||||
|
class_loader,
|
||||||
|
vmClasses::ClassLoader_klass(),
|
||||||
|
vmSymbols::getResourceAsByteArray_name(),
|
||||||
|
vmSymbols::getResourceAsByteArray_signature(),
|
||||||
|
ext_class_name,
|
||||||
|
CHECK_NULL);
|
||||||
|
assert(result.get_type() == T_OBJECT, "just checking");
|
||||||
|
oop obj = result.get_oop();
|
||||||
|
assert(obj != nullptr, "ClassLoader.getResourceAsByteArray should not return null");
|
||||||
|
|
||||||
|
// copy from byte[] to a buffer
|
||||||
|
typeArrayOop ba = typeArrayOop(obj);
|
||||||
|
jint len = ba->length();
|
||||||
|
u1* buffer = NEW_RESOURCE_ARRAY(u1, len);
|
||||||
|
ArrayAccess<>::arraycopy_to_native<>(ba, typeArrayOopDesc::element_offset<jbyte>(0), buffer, len);
|
||||||
|
|
||||||
|
return new ClassFileStream(buffer, len, cpe->name());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -507,6 +507,10 @@ public:
|
|||||||
#if INCLUDE_JVMTI
|
#if INCLUDE_JVMTI
|
||||||
// Caller needs a ResourceMark because parts of the returned cfs are resource-allocated.
|
// Caller needs a ResourceMark because parts of the returned cfs are resource-allocated.
|
||||||
static ClassFileStream* open_stream_for_jvmti(InstanceKlass* ik, Handle class_loader, TRAPS);
|
static ClassFileStream* open_stream_for_jvmti(InstanceKlass* ik, Handle class_loader, TRAPS);
|
||||||
|
static ClassFileStream* get_stream_from_class_loader(Handle class_loader,
|
||||||
|
ClassPathEntry* cpe,
|
||||||
|
const char* file_name,
|
||||||
|
TRAPS);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static SharedClassPathEntry* shared_path(int index) {
|
static SharedClassPathEntry* shared_path(int index) {
|
||||||
|
@ -723,6 +723,8 @@ class SerializeClosure;
|
|||||||
template(dumpSharedArchive_signature, "(ZLjava/lang/String;)Ljava/lang/String;") \
|
template(dumpSharedArchive_signature, "(ZLjava/lang/String;)Ljava/lang/String;") \
|
||||||
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \
|
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \
|
||||||
template(generateLambdaFormHolderClasses_signature, "([Ljava/lang/String;)[Ljava/lang/Object;") \
|
template(generateLambdaFormHolderClasses_signature, "([Ljava/lang/String;)[Ljava/lang/Object;") \
|
||||||
|
template(getResourceAsByteArray_name, "getResourceAsByteArray") \
|
||||||
|
template(getResourceAsByteArray_signature, "(Ljava/lang/String;)[B") \
|
||||||
template(java_lang_Enum, "java/lang/Enum") \
|
template(java_lang_Enum, "java/lang/Enum") \
|
||||||
template(java_lang_invoke_Invokers_Holder, "java/lang/invoke/Invokers$Holder") \
|
template(java_lang_invoke_Invokers_Holder, "java/lang/invoke/Invokers$Holder") \
|
||||||
template(java_lang_invoke_DirectMethodHandle_Holder, "java/lang/invoke/DirectMethodHandle$Holder") \
|
template(java_lang_invoke_DirectMethodHandle_Holder, "java/lang/invoke/DirectMethodHandle$Holder") \
|
||||||
|
@ -1685,6 +1685,15 @@ public abstract class ClassLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by VM for reading class bytes.
|
||||||
|
*/
|
||||||
|
private byte[] getResourceAsByteArray(String name) throws IOException {
|
||||||
|
Objects.requireNonNull(name);
|
||||||
|
InputStream is = getResourceAsStream(name);
|
||||||
|
return is != null ? is.readAllBytes() : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open for reading, a resource of the specified name from the search path
|
* Open for reading, a resource of the specified name from the search path
|
||||||
* used to load classes. This method locates the resource through the
|
* used to load classes. This method locates the resource through the
|
||||||
|
@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
* @summary Test multi-release jar with CFLH
|
||||||
|
* @requires vm.cds
|
||||||
|
* @requires vm.jvmti
|
||||||
|
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
|
||||||
|
* @run main/othervm/native MultiReleaseJars
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import jdk.test.lib.cds.CDSTestUtils;
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
|
||||||
|
public class MultiReleaseJars {
|
||||||
|
|
||||||
|
static final int BASE_VERSION = 9;
|
||||||
|
static final String BASE_VERSION_STRING = Integer.toString(BASE_VERSION);
|
||||||
|
static final int MAJOR_VERSION = Runtime.version().major();
|
||||||
|
static final String MAJOR_VERSION_STRING = String.valueOf(MAJOR_VERSION);
|
||||||
|
|
||||||
|
static String getMain() {
|
||||||
|
String sts = """
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
System.out.println(Class.forName(\"Foo\"));
|
||||||
|
System.out.println(Class.forName(\"Bar\"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
return sts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getFoo() {
|
||||||
|
String sts = """
|
||||||
|
class Foo {
|
||||||
|
static {
|
||||||
|
System.out.println("Hello from Foo old version");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
return sts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getFooNewVersion() {
|
||||||
|
String sts = """
|
||||||
|
class Foo {
|
||||||
|
static {
|
||||||
|
System.out.println("Hello from Foo new version");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
return sts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getBar() {
|
||||||
|
String sts = """
|
||||||
|
class Bar {
|
||||||
|
static {
|
||||||
|
System.out.println("Hello from Bar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
return sts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeFile(File file, String... contents) throws Exception {
|
||||||
|
if (contents == null) {
|
||||||
|
throw new java.lang.RuntimeException("No input for writing to file" + file);
|
||||||
|
}
|
||||||
|
try (
|
||||||
|
FileOutputStream fos = new FileOutputStream(file);
|
||||||
|
PrintStream ps = new PrintStream(fos)
|
||||||
|
) {
|
||||||
|
for (String str : contents) {
|
||||||
|
ps.println(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* version.jar entries and files:
|
||||||
|
* META-INF/
|
||||||
|
* META-INF/MANIFEST.MF
|
||||||
|
* Bar.class
|
||||||
|
* Main.class
|
||||||
|
* META-INF/versions/9/
|
||||||
|
* META-INF/versions/9/Bar.class
|
||||||
|
* META-INF/versions/9/Foo.class
|
||||||
|
* META-INF/versions/24/
|
||||||
|
* META-INF/versions/24/Foo.class
|
||||||
|
*/
|
||||||
|
static void createClassFilesAndJar() throws Exception {
|
||||||
|
String tempDir = CDSTestUtils.getOutputDir();
|
||||||
|
File baseDir = new File(tempDir + File.separator + "base");
|
||||||
|
File vDir = new File(tempDir + File.separator + BASE_VERSION_STRING);
|
||||||
|
File vDir2 = new File(tempDir + File.separator + MAJOR_VERSION_STRING);
|
||||||
|
|
||||||
|
baseDir.mkdirs();
|
||||||
|
vDir.mkdirs();
|
||||||
|
|
||||||
|
File fileFoo = TestCommon.getOutputSourceFile("Foo.java");
|
||||||
|
writeFile(fileFoo, getFoo());
|
||||||
|
JarBuilder.compile(vDir.getAbsolutePath(), fileFoo.getAbsolutePath(), "--release", BASE_VERSION_STRING);
|
||||||
|
|
||||||
|
writeFile(fileFoo, getFooNewVersion());
|
||||||
|
JarBuilder.compile(vDir2.getAbsolutePath(), fileFoo.getAbsolutePath(), "--release", MAJOR_VERSION_STRING);
|
||||||
|
|
||||||
|
File fileMain = TestCommon.getOutputSourceFile("Main.java");
|
||||||
|
writeFile(fileMain, getMain());
|
||||||
|
JarBuilder.compile(baseDir.getAbsolutePath(), fileMain.getAbsolutePath());
|
||||||
|
File fileBar = TestCommon.getOutputSourceFile("Bar.java");
|
||||||
|
writeFile(fileBar, getBar());
|
||||||
|
JarBuilder.compile(baseDir.getAbsolutePath(), fileBar.getAbsolutePath());
|
||||||
|
JarBuilder.compile(vDir.getAbsolutePath(), fileBar.getAbsolutePath(), "--release", BASE_VERSION_STRING);
|
||||||
|
|
||||||
|
String[] meta = {
|
||||||
|
"Multi-Release: true",
|
||||||
|
"Main-Class: Main"
|
||||||
|
};
|
||||||
|
File metainf = new File(tempDir, "mf.txt");
|
||||||
|
writeFile(metainf, meta);
|
||||||
|
|
||||||
|
JarBuilder.build("multi-version", baseDir, metainf.getAbsolutePath(),
|
||||||
|
"--release", BASE_VERSION_STRING, "-C", vDir.getAbsolutePath(), ".",
|
||||||
|
"--release", MAJOR_VERSION_STRING, "-C", vDir2.getAbsolutePath(), ".");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String... args) throws Exception {
|
||||||
|
// create multi-version.jar which contains Main.class, Foo.class and Bar.class.
|
||||||
|
// Foo.class has two version: base version 9 and current major JDK version.
|
||||||
|
// Bar.class has two versions: base version 9 and default version.
|
||||||
|
// Since there is no default version for Foo, the class loader will get the
|
||||||
|
// highest version (current major JDK version in this case) which is the
|
||||||
|
// same or below the current JDK version.
|
||||||
|
createClassFilesAndJar();
|
||||||
|
|
||||||
|
String mainClass = "Main";
|
||||||
|
String appJar = TestCommon.getTestJar("multi-version.jar");
|
||||||
|
String appClasses[] = {"Foo", "Bar"};
|
||||||
|
|
||||||
|
OutputAnalyzer output = TestCommon.dump(appJar, appClasses);
|
||||||
|
output.shouldContain("Loading classes to share: done.")
|
||||||
|
.shouldHaveExitValue(0);
|
||||||
|
|
||||||
|
String agentCmdArg = "-agentlib:SimpleClassFileLoadHook=Foo,Hello,HELLO";
|
||||||
|
output = TestCommon.execAuto("-cp", appJar,
|
||||||
|
"-Xlog:cds=info,class+load",
|
||||||
|
agentCmdArg,
|
||||||
|
mainClass);
|
||||||
|
|
||||||
|
output.shouldMatch(".*Foo.source:.*multi-version.jar")
|
||||||
|
// New version of Foo is loaded from jar since it was modified by CFLH
|
||||||
|
.shouldContain("HELLO from Foo new version") // CFLH changed "Hello" to "HELLO"
|
||||||
|
.shouldContain("class Foo") // output from Main
|
||||||
|
// Bar is loaded from archive
|
||||||
|
.shouldContain("Bar source: shared objects file")
|
||||||
|
.shouldContain("Hello from Bar")
|
||||||
|
.shouldContain("class Bar"); // output from Main
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user