Skip to content

Commit

Permalink
8343427: Class file load hook crashes on archived classes from multi-…
Browse files Browse the repository at this point in the history
…release JARs
  • Loading branch information
calvinccheung committed Nov 20, 2024
1 parent 9e92a9e commit d9d7037
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 1 deletion.
54 changes: 53 additions & 1 deletion src/hotspot/share/cds/filemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -2678,11 +2681,60 @@ 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);

// InputStream ClassLoader.getResourceAsStream(String name)
JavaCalls::call_virtual(&result,
class_loader,
vmClasses::ClassLoader_klass(),
vmSymbols::getResourceAsStream_name(),
vmSymbols::getResourceAsStream_signature(),
ext_class_name,
CHECK_NULL);
assert(result.get_type() == T_OBJECT, "just checking");
oop obj = result.get_oop();
assert(obj != nullptr, "ClassLoader::getResourceAsStream should not return null");

Handle input_stream = Handle(THREAD, obj);
JavaValue res(T_OBJECT);
// byte[] InputStream.readAllBytes()
JavaCalls::call_virtual(&res,
input_stream,
vmClasses::InputStream_klass(),
vmSymbols::readAllBytes(),
vmSymbols::void_byte_array_signature(),
CHECK_NULL);
// The result should be a [B
oop res_oop = res.get_oop();
assert(res_oop->is_typeArray(), "just checking");
assert(TypeArrayKlass::cast(res_oop->klass())->element_type() == T_BYTE, "just checking");

// copy from byte[] to a buffer
typeArrayOop ba = typeArrayOop(res_oop);
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
4 changes: 4 additions & 0 deletions src/hotspot/share/cds/filemap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,10 @@ class FileMapInfo : public CHeapObj<mtInternal> {
#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) {
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/classfile/vmClassMacros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
do_klass(CodeSource_klass, java_security_CodeSource ) \
do_klass(ConcurrentHashMap_klass, java_util_concurrent_ConcurrentHashMap ) \
do_klass(ArrayList_klass, java_util_ArrayList ) \
do_klass(InputStream_klass, java_io_InputStream ) \
\
do_klass(StackTraceElement_klass, java_lang_StackTraceElement ) \
\
Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/share/classfile/vmSymbols.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class SerializeClosure;
template(java_net_URLClassLoader, "java/net/URLClassLoader") \
template(java_util_jar_Manifest, "java/util/jar/Manifest") \
template(java_io_ByteArrayInputStream, "java/io/ByteArrayInputStream") \
template(java_io_InputStream, "java/io/InputStream") \
template(java_io_Serializable, "java/io/Serializable") \
template(java_nio_Buffer, "java/nio/Buffer") \
template(java_util_Arrays, "java/util/Arrays") \
Expand Down Expand Up @@ -723,6 +724,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(getResourceAsStream_name, "getResourceAsStream") \
template(getResourceAsStream_signature, "(Ljava/lang/String;)Ljava/io/InputStream;") \
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") \
Expand All @@ -732,6 +735,7 @@ class SerializeClosure;
template(jdk_internal_misc_CDS, "jdk/internal/misc/CDS") \
template(java_util_concurrent_ConcurrentHashMap, "java/util/concurrent/ConcurrentHashMap") \
template(java_util_ArrayList, "java/util/ArrayList") \
template(readAllBytes, "readAllBytes") \
template(runtimeSetup, "runtimeSetup") \
template(toFileURL_name, "toFileURL") \
template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \
Expand Down
177 changes: 177 additions & 0 deletions test/hotspot/jtreg/runtime/cds/appcds/jvmti/CFLH/MultiReleaseJars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* 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\");",
" }",
"}",
};
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
*/
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);

baseDir.mkdirs();
vDir.mkdirs();

File fileFoo = TestCommon.getOutputSourceFile("Foo.java");
writeFile(fileFoo, getFoo());
JarBuilder.compile(vDir.getAbsolutePath(), fileFoo.getAbsolutePath(), "--release", BASE_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(), ".");

}

static void checkExecOutput(OutputAnalyzer output, String expectedOutput) throws Exception {
try {
TestCommon.checkExec(output, expectedOutput);
} catch (java.lang.RuntimeException re) {
String cause = re.getMessage();
if (!expectedOutput.equals(cause)) {
throw re;
}
}
}

public static void main(String... args) throws Exception {
// create multi-version.jar which contains Main.class, Foo.class and Bar.class.
// Foo.class has only one version: base version 9.
// Bar.class has two versions: base version 9 and default 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")
// Foo is loaded from jar since it was modified by CFLH
.shouldContain("HELLO from Foo") // 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
}
}

0 comments on commit d9d7037

Please sign in to comment.