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/universe.hpp"
|
||||
#include "nmt/memTracker.hpp"
|
||||
#include "oops/access.hpp"
|
||||
#include "oops/compressedOops.hpp"
|
||||
#include "oops/compressedOops.inline.hpp"
|
||||
#include "oops/compressedKlass.hpp"
|
||||
#include "oops/objArrayOop.hpp"
|
||||
#include "oops/oop.inline.hpp"
|
||||
#include "oops/typeArrayKlass.hpp"
|
||||
#include "prims/jvmtiExport.hpp"
|
||||
#include "runtime/arguments.hpp"
|
||||
#include "runtime/globals_extension.hpp"
|
||||
#include "runtime/java.hpp"
|
||||
#include "runtime/javaCalls.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
#include "runtime/os.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,
|
||||
name->utf8_length());
|
||||
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.");
|
||||
log_debug(cds, jvmti)("classfile data for %s [%d: %s] = %d bytes", class_name, path_index,
|
||||
cfs->source(), cfs->length());
|
||||
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
|
||||
|
@ -507,6 +507,10 @@ public:
|
||||
#if INCLUDE_JVMTI
|
||||
// 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* get_stream_from_class_loader(Handle class_loader,
|
||||
ClassPathEntry* cpe,
|
||||
const char* file_name,
|
||||
TRAPS);
|
||||
#endif
|
||||
|
||||
static SharedClassPathEntry* shared_path(int index) {
|
||||
|
@ -723,6 +723,8 @@ class SerializeClosure;
|
||||
template(dumpSharedArchive_signature, "(ZLjava/lang/String;)Ljava/lang/String;") \
|
||||
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \
|
||||
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_invoke_Invokers_Holder, "java/lang/invoke/Invokers$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
|
||||
* 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