8315130: java.lang.IllegalAccessError when processing classlist to create CDS archive

Reviewed-by: iklam, ccheung
This commit is contained in:
Timofei Pushkin 2025-05-16 13:51:58 +00:00 committed by Ioi Lam
parent bca293d012
commit 46a12e781e
13 changed files with 578 additions and 186 deletions

View File

@ -395,17 +395,13 @@ bool ClassListParser::parse_uint_option(const char* option_name, int* value) {
return false;
}
objArrayOop ClassListParser::get_specified_interfaces(TRAPS) {
GrowableArray<InstanceKlass *> ClassListParser::get_specified_interfaces() {
const int n = _interfaces->length();
if (n == 0) {
return nullptr;
} else {
objArrayOop array = oopFactory::new_objArray(vmClasses::Class_klass(), n, CHECK_NULL);
for (int i = 0; i < n; i++) {
array->obj_at_put(i, lookup_class_by_id(_interfaces->at(i))->java_mirror());
}
return array;
GrowableArray<InstanceKlass *> specified_interfaces(n);
for (int i = 0; i < n; i++) {
specified_interfaces.append(lookup_class_by_id(_interfaces->at(i)));
}
return specified_interfaces;
}
void ClassListParser::print_specified_interfaces() {
@ -509,6 +505,25 @@ void ClassListParser::constant_pool_resolution_warning(const char* msg, ...) {
va_end(ap);
}
// If an unregistered class U is specified to have a registered supertype S1
// named SN but an unregistered class S2 also named SN has already been loaded
// S2 will be incorrectly used as the supertype of U instead of S1 due to
// limitations in the loading mechanism of unregistered classes.
void ClassListParser::check_supertype_obstruction(int specified_supertype_id, const InstanceKlass* specified_supertype, TRAPS) {
if (specified_supertype->is_shared_unregistered_class()) {
return; // Only registered supertypes can be obstructed
}
const InstanceKlass* obstructor = SystemDictionaryShared::get_unregistered_class(specified_supertype->name());
if (obstructor == nullptr) {
return; // No unregistered types with the same name have been loaded, i.e. no obstruction
}
// 'specified_supertype' is S1, 'obstructor' is S2 from the explanation above
ResourceMark rm;
THROW_MSG(vmSymbols::java_lang_UnsupportedOperationException(),
err_msg("%s (id %d) has super-type %s (id %d) obstructed by another class with the same name",
_class_name, _id, specified_supertype->external_name(), specified_supertype_id));
}
// This function is used for loading classes for customized class loaders
// during archive dumping.
InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS) {
@ -533,13 +548,18 @@ InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS
}
ResourceMark rm;
char * source_path = os::strdup_check_oom(ClassLoader::uri_to_path(_source));
InstanceKlass* specified_super = lookup_class_by_id(_super);
Handle super_class(THREAD, specified_super->java_mirror());
objArrayOop r = get_specified_interfaces(CHECK_NULL);
objArrayHandle interfaces(THREAD, r);
InstanceKlass* k = UnregisteredClasses::load_class(class_name, source_path,
super_class, interfaces, CHECK_NULL);
GrowableArray<InstanceKlass*> specified_interfaces = get_specified_interfaces();
// Obstruction must be checked before the class loading attempt because it may
// cause class loading errors (JVMS 5.3.5.3-5.3.5.4)
check_supertype_obstruction(_super, specified_super, CHECK_NULL);
for (int i = 0; i < _interfaces->length(); i++) {
check_supertype_obstruction(_interfaces->at(i), specified_interfaces.at(i), CHECK_NULL);
}
const char* source_path = ClassLoader::uri_to_path(_source);
InstanceKlass* k = UnregisteredClasses::load_class(class_name, source_path, CHECK_NULL);
if (k->java_super() != specified_super) {
error("The specified super class %s (id %d) does not match actual super class %s",
specified_super->external_name(), _super,
@ -551,6 +571,15 @@ InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS
error("The number of interfaces (%d) specified in class list does not match the class file (%d)",
_interfaces->length(), k->local_interfaces()->length());
}
for (int i = 0; i < _interfaces->length(); i++) {
InstanceKlass* specified_interface = specified_interfaces.at(i);
if (!k->local_interfaces()->contains(specified_interface)) {
print_specified_interfaces();
print_actual_interfaces(k);
error("Specified interface %s (id %d) is not directly implemented",
specified_interface->external_name(), _interfaces->at(i));
}
}
assert(k->is_shared_unregistered_class(), "must be");

View File

@ -137,7 +137,8 @@ private:
void print_diagnostic_info(outputStream* st, const char* msg, ...) ATTRIBUTE_PRINTF(3, 0);
void constant_pool_resolution_warning(const char* msg, ...) ATTRIBUTE_PRINTF(2, 0);
void error(const char* msg, ...) ATTRIBUTE_PRINTF(2, 0);
objArrayOop get_specified_interfaces(TRAPS);
GrowableArray<InstanceKlass*> get_specified_interfaces();
void check_supertype_obstruction(int specified_supertype_id, const InstanceKlass* specified_supertype, TRAPS);
public:
static void parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS);

View File

@ -24,37 +24,49 @@
#include "cds/cdsConfig.hpp"
#include "cds/unregisteredClasses.hpp"
#include "classfile/classFileStream.hpp"
#include "classfile/classLoader.inline.hpp"
#include "classfile/classLoaderExt.hpp"
#include "classfile/javaClasses.inline.hpp"
#include "classfile/symbolTable.hpp"
#include "classfile/systemDictionary.hpp"
#include "classfile/vmSymbols.hpp"
#include "memory/oopFactory.hpp"
#include "memory/resourceArea.hpp"
#include "oops/instanceKlass.hpp"
#include "oops/oopHandle.hpp"
#include "oops/oopHandle.inline.hpp"
#include "runtime/handles.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/javaCalls.hpp"
#include "services/threadService.hpp"
InstanceKlass* UnregisteredClasses::_UnregisteredClassLoader_klass = nullptr;
static InstanceKlass* _UnregisteredClassLoader_klass;
static InstanceKlass* _UnregisteredClassLoader_Source_klass;
static OopHandle _unregistered_class_loader;
void UnregisteredClasses::initialize(TRAPS) {
if (_UnregisteredClassLoader_klass == nullptr) {
// no need for synchronization as this function is called single-threaded.
Symbol* klass_name = SymbolTable::new_symbol("jdk/internal/misc/CDS$UnregisteredClassLoader");
Klass* k = SystemDictionary::resolve_or_fail(klass_name, true, CHECK);
_UnregisteredClassLoader_klass = InstanceKlass::cast(k);
if (_UnregisteredClassLoader_klass != nullptr) {
return;
}
Symbol* klass_name;
Klass* k;
// no need for synchronization as this function is called single-threaded.
klass_name = SymbolTable::new_symbol("jdk/internal/misc/CDS$UnregisteredClassLoader");
k = SystemDictionary::resolve_or_fail(klass_name, true, CHECK);
_UnregisteredClassLoader_klass = InstanceKlass::cast(k);
klass_name = SymbolTable::new_symbol("jdk/internal/misc/CDS$UnregisteredClassLoader$Source");
k = SystemDictionary::resolve_or_fail(klass_name, true, CHECK);
_UnregisteredClassLoader_Source_klass = InstanceKlass::cast(k);
precond(_unregistered_class_loader.is_empty());
HandleMark hm(THREAD);
const Handle cl = JavaCalls::construct_new_instance(_UnregisteredClassLoader_klass,
vmSymbols::void_method_signature(), CHECK);
_unregistered_class_loader = OopHandle(Universe::vm_global(), cl());
}
// Load the class of the given name from the location given by path. The path is specified by
// the "source:" in the class list file (see classListParser.cpp), and can be a directory or
// a JAR file.
InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path,
Handle super_class, objArrayHandle interfaces, TRAPS) {
InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path, TRAPS) {
assert(name != nullptr, "invariant");
assert(CDSConfig::is_dumping_static_archive(), "this function is only used with -Xshare:dump");
@ -62,59 +74,33 @@ InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path,
THREAD->get_thread_stat()->perf_timers_addr(),
PerfClassTraceTime::CLASS_LOAD);
// Call CDS$UnregisteredClassLoader::load(String name, Class<?> superClass, Class<?>[] interfaces)
assert(!_unregistered_class_loader.is_empty(), "not initialized");
Handle classloader(THREAD, _unregistered_class_loader.resolve());
// Call CDS$UnregisteredClassLoader::load(String name, String source)
Symbol* methodName = SymbolTable::new_symbol("load");
Symbol* methodSignature = SymbolTable::new_symbol("(Ljava/lang/String;Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/Class;");
Symbol* path_symbol = SymbolTable::new_symbol(path);
Handle classloader = get_classloader(path_symbol, CHECK_NULL);
Symbol* methodSignature = SymbolTable::new_symbol("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;");
Handle ext_class_name = java_lang_String::externalize_classname(name, CHECK_NULL);
Handle path_string = java_lang_String::create_from_str(path, CHECK_NULL);
JavaValue result(T_OBJECT);
JavaCallArguments args(3);
args.set_receiver(classloader);
args.push_oop(ext_class_name);
args.push_oop(super_class);
args.push_oop(interfaces);
JavaCalls::call_virtual(&result,
UnregisteredClassLoader_klass(),
classloader,
_UnregisteredClassLoader_klass,
methodName,
methodSignature,
&args,
ext_class_name,
path_string,
CHECK_NULL);
assert(result.get_type() == T_OBJECT, "just checking");
oop obj = result.get_oop();
return InstanceKlass::cast(java_lang_Class::as_Klass(obj));
return InstanceKlass::cast(java_lang_Class::as_Klass(result.get_oop()));
}
class UnregisteredClasses::ClassLoaderTable : public ResourceHashtable<
Symbol*, OopHandle,
137, // prime number
AnyObj::C_HEAP> {};
static UnregisteredClasses::ClassLoaderTable* _classloader_table = nullptr;
Handle UnregisteredClasses::create_classloader(Symbol* path, TRAPS) {
ResourceMark rm(THREAD);
JavaValue result(T_OBJECT);
Handle path_string = java_lang_String::create_from_str(path->as_C_string(), CHECK_NH);
Handle classloader = JavaCalls::construct_new_instance(
UnregisteredClassLoader_klass(),
vmSymbols::string_void_signature(),
path_string, CHECK_NH);
return classloader;
}
Handle UnregisteredClasses::get_classloader(Symbol* path, TRAPS) {
if (_classloader_table == nullptr) {
_classloader_table = new (mtClass)ClassLoaderTable();
}
OopHandle* classloader_ptr = _classloader_table->get(path);
if (classloader_ptr != nullptr) {
return Handle(THREAD, (*classloader_ptr).resolve());
} else {
Handle classloader = create_classloader(path, CHECK_NH);
_classloader_table->put(path, OopHandle(Universe::vm_global(), classloader()));
path->increment_refcount();
return classloader;
bool UnregisteredClasses::check_for_exclusion(const InstanceKlass* k) {
if (_UnregisteredClassLoader_klass == nullptr) {
return false; // Uninitialized
}
return k == _UnregisteredClassLoader_klass ||
k->implements_interface(_UnregisteredClassLoader_Source_klass);
}

View File

@ -33,22 +33,10 @@ class Symbol;
class UnregisteredClasses: AllStatic {
public:
static InstanceKlass* load_class(Symbol* h_name, const char* path,
Handle super_class, objArrayHandle interfaces,
TRAPS);
static InstanceKlass* load_class(Symbol* name, const char* path, TRAPS);
static void initialize(TRAPS);
static InstanceKlass* UnregisteredClassLoader_klass() {
return _UnregisteredClassLoader_klass;
}
class ClassLoaderTable;
private:
// Don't put this in vmClasses as it's used only with CDS dumping.
static InstanceKlass* _UnregisteredClassLoader_klass;
static Handle create_classloader(Symbol* path, TRAPS);
static Handle get_classloader(Symbol* path, TRAPS);
// Returns true if the class is loaded internally for dumping unregistered classes.
static bool check_for_exclusion(const InstanceKlass* k);
};
#endif // SHARE_CDS_UNREGISTEREDCLASSES_HPP

View File

@ -323,6 +323,12 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) {
}
}
if (UnregisteredClasses::check_for_exclusion(k)) {
ResourceMark rm;
log_info(cds)("Skipping %s: used only when dumping CDS archive", k->name()->as_C_string());
return true;
}
InstanceKlass* super = k->java_super();
if (super != nullptr && check_for_exclusion(super, nullptr)) {
ResourceMark rm;
@ -341,12 +347,6 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) {
}
}
if (k == UnregisteredClasses::UnregisteredClassLoader_klass()) {
ResourceMark rm;
log_info(cds)("Skipping %s: used only when dumping CDS archive", k->name()->as_C_string());
return true;
}
return false; // false == k should NOT be excluded
}
@ -465,6 +465,15 @@ bool SystemDictionaryShared::add_unregistered_class(Thread* current, InstanceKla
return (klass == *v);
}
InstanceKlass* SystemDictionaryShared::get_unregistered_class(Symbol* name) {
assert(CDSConfig::is_dumping_archive() || ClassListWriter::is_enabled(), "sanity");
if (_unregistered_classes_table == nullptr) {
return nullptr;
}
InstanceKlass** k = _unregistered_classes_table->get(name);
return k != nullptr ? *k : nullptr;
}
void SystemDictionaryShared::copy_unregistered_class_size_and_crc32(InstanceKlass* klass) {
precond(CDSConfig::is_dumping_final_static_archive());
precond(klass->is_shared());

View File

@ -249,6 +249,7 @@ public:
return (k->shared_classpath_index() != UNREGISTERED_INDEX);
}
static bool add_unregistered_class(Thread* current, InstanceKlass* k);
static InstanceKlass* get_unregistered_class(Symbol* name);
static void copy_unregistered_class_size_and_crc32(InstanceKlass* klass);
static void finish_exclusion_checks();

View File

@ -31,15 +31,15 @@ import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.InvalidPathException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import jdk.internal.access.SharedSecrets;
@ -399,105 +399,111 @@ public class CDS {
* be loaded by custom class loaders during runtime.
* See src/hotspot/share/cds/unregisteredClasses.cpp.
*/
private static class UnregisteredClassLoader extends URLClassLoader {
private String currentClassName;
private Class<?> currentSuperClass;
private Class<?>[] currentInterfaces;
/**
* Used only by native code. Construct an UnregisteredClassLoader for loading
* unregistered classes from the specified file. If the file doesn't exist,
* the exception will be caughted by native code which will print a warning message and continue.
*
* @param fileName path of the the JAR file to load unregistered classes from.
*/
private UnregisteredClassLoader(String fileName) throws InvalidPathException, IOException {
super(toURLArray(fileName), /*parent*/null);
currentClassName = null;
currentSuperClass = null;
currentInterfaces = null;
private static class UnregisteredClassLoader extends ClassLoader {
static {
registerAsParallelCapable();
}
private static URL[] toURLArray(String fileName) throws InvalidPathException, IOException {
if (!((new File(fileName)).exists())) {
throw new IOException("No such file: " + fileName);
}
return new URL[] {
// Use an intermediate File object to construct a URI/URL without
// authority component as URLClassPath can't handle URLs with a UNC
// server name in the authority component.
Path.of(fileName).toRealPath().toFile().toURI().toURL()
};
static interface Source {
public byte[] readClassFile(String className) throws IOException;
}
static class JarSource implements Source {
private final JarFile jar;
/**
* Load the class of the given <code>/name<code> from the JAR file that was given to
* the constructor of the current UnregisteredClassLoader instance. This class must be
* a direct subclass of <code>superClass</code>. This class must be declared to implement
* the specified <code>interfaces</code>.
* <p>
* This method must be called in a single threaded context. It will never be recursed (thus
* the asserts)
*
* @param name the name of the class to be loaded.
* @param superClass must not be null. The named class must have a super class.
* @param interfaces could be null if the named class does not implement any interfaces.
*/
private Class<?> load(String name, Class<?> superClass, Class<?>[] interfaces)
throws ClassNotFoundException
{
assert currentClassName == null;
assert currentSuperClass == null;
assert currentInterfaces == null;
try {
currentClassName = name;
currentSuperClass = superClass;
currentInterfaces = interfaces;
return findClass(name);
} finally {
currentClassName = null;
currentSuperClass = null;
currentInterfaces = null;
JarSource(File file) throws IOException {
jar = new JarFile(file);
}
}
/**
* This method must be called from inside the <code>load()</code> method. The <code>/name<code>
* can be only:
* <ul>
* <li> the <code>name</code> parameter for <code>load()</code>
* <li> the name of the <code>superClass</code> parameter for <code>load()</code>
* <li> the name of one of the interfaces in <code>interfaces</code> parameter for <code>load()</code>
* <ul>
*
* For all other cases, a <code>ClassNotFoundException</code> will be thrown.
*/
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
Objects.requireNonNull(currentClassName);
Objects.requireNonNull(currentSuperClass);
if (name.equals(currentClassName)) {
// Note: the following call will call back to <code>this.findClass(name)</code> to
// resolve the super types of the named class.
return super.findClass(name);
}
if (name.equals(currentSuperClass.getName())) {
return currentSuperClass;
}
if (currentInterfaces != null) {
for (Class<?> c : currentInterfaces) {
if (name.equals(c.getName())) {
return c;
}
@Override
public byte[] readClassFile(String className) throws IOException {
final var entryName = className.replace('.', '/').concat(".class");
final var entry = jar.getEntry(entryName);
if (entry == null) {
throw new IOException("No such entry: " + entryName + " in " + jar.getName());
}
try (final var in = jar.getInputStream(entry)) {
return in.readAllBytes();
}
}
}
throw new ClassNotFoundException(name);
static class DirSource implements Source {
private final String basePath;
DirSource(File dir) {
assert dir.isDirectory();
basePath = dir.toString();
}
@Override
public byte[] readClassFile(String className) throws IOException {
final var subPath = className.replace('.', File.separatorChar).concat(".class");
final var fullPath = Path.of(basePath, subPath);
return Files.readAllBytes(fullPath);
}
}
private final HashMap<String, Source> sources = new HashMap<>();
private Source resolveSource(String path) throws IOException {
Source source = sources.get(path);
if (source != null) {
return source;
}
final var file = new File(path);
if (!file.exists()) {
throw new IOException("No such file: " + path);
}
if (file.isFile()) {
source = new JarSource(file);
} else if (file.isDirectory()) {
source = new DirSource(file);
} else {
throw new IOException("Not a normal file: " + path);
}
sources.put(path, source);
return source;
}
/**
* Load the class of the given <code>name</code> from the given <code>source</code>.
* <p>
* All super classes and interfaces of the named class must have already been loaded:
* either defined by this class loader (unregistered ones) or loaded, possibly indirectly,
* by the system class loader (registered ones).
* <p>
* If the named class has a registered super class or interface named N there should be no
* unregistered class or interface named N loaded yet.
*
* @param name the name of the class to be loaded.
* @param source path to a directory or a JAR file from which the named class should be
* loaded.
*/
private Class<?> load(String name, String source) throws IOException {
final Source resolvedSource = resolveSource(source);
final byte[] bytes = resolvedSource.readClassFile(name);
// 'defineClass()' may cause loading of supertypes of this unregistered class by VM
// calling 'this.loadClass()'.
//
// For any supertype S named SN specified in the classlist the following is ensured by
// the CDS implementation:
// - if S is an unregistered class it must have already been defined by this class
// loader and thus will be found by 'this.findLoadedClass(SN)',
// - if S is not an unregistered class there should be no unregistered class named SN
// loaded yet so either S has previously been (indirectly) loaded by this class loader
// and thus it will be found when calling 'this.findLoadedClass(SN)' or it will be
// found when delegating to the system class loader, which must have already loaded S,
// by calling 'this.getParent().loadClass(SN, false)'.
// See the implementation of 'ClassLoader.loadClass()' for details.
//
// Therefore, we should resolve all supertypes to the expected ones as specified by the
// "super:" and "interfaces:" attributes in the classlist. This invariant is validated
// by the C++ function 'ClassListParser::load_class_from_source()'.
assert getParent() == getSystemClassLoader();
return defineClass(name, bytes, 0, bytes.length);
}
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2025, 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.
*/
import java.nio.file.Files;
import java.nio.file.Path;
import jdk.test.lib.process.OutputAnalyzer;
/**
* @test
* @bug 8315130
* @summary Tests archiving a hierarchy of package-private classes loaded from
* different sources.
*
* @requires vm.cds
* @requires vm.cds.custom.loaders
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
* @compile test-classes/DifferentSourcesApp.java test-classes/CustomLoadee5.java test-classes/CustomLoadee5Child.java
* @run main DifferentSourcesTest
*/
public class DifferentSourcesTest {
public static void main(String[] args) throws Exception {
// Setup:
// - CustomLoadee5 is package-private
// - CustomLoadee5Child extends CustomLoadee5
//
// This setup requires CustomLoadee5 and CustomLoadee5Child to be in the
// same run-time package. Since their package name is the same (empty)
// this boils down to "be loaded by the same class loader".
//
// DifferentSourcesApp adheres to this requirement.
//
// This test checks that CDS adheres to this requirement too when
// creating a static app archive, even if CustomLoadee5 and
// CustomLoadee5Child are in different sources.
OutputAnalyzer output;
// The main check: the archive is created without IllegalAccessError
JarBuilder.build("base", "CustomLoadee5");
JarBuilder.build("sub", "CustomLoadee5Child");
final String classlist[] = new String[] {
"java/lang/Object id: 0",
"CustomLoadee5 id: 1 super: 0 source: base.jar",
"CustomLoadee5Child id: 2 super: 1 source: sub.jar",
};
output = TestCommon.testDump(null, classlist);
output.shouldNotContain("java.lang.IllegalAccessError: class CustomLoadee5Child cannot access its superclass CustomLoadee5");
output.shouldNotContain("Cannot find CustomLoadee5Child");
// Sanity check: the archive is used as expected
output = TestCommon.execCommon("-Xlog:class+load", "DifferentSourcesApp");
TestCommon.checkExec(
output,
"CustomLoadee5 source: shared objects file",
"CustomLoadee5Child source: shared objects file"
);
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2025, 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.
*/
import jdk.test.lib.process.OutputAnalyzer;
/**
* @test id=unreg
* @summary Tests that if a super class is listed as unregistered it is archived
* as such even if a class with the same name has also been loaded from the
* classpath.
*
* @requires vm.cds
* @requires vm.cds.custom.loaders
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
* @compile test-classes/RegUnregSuperApp.java test-classes/CustomLoadee3.java test-classes/CustomLoadee3Child.java
* @run main RegUnregSuperTest unreg
*/
/**
* @test id=reg
* @summary If an unregistered class U is specified to have a registered
* supertype S1 named SN but an unregistered class S2 also named SN has already
* been loaded S2 will be incorrectly used as the supertype of U instead of S1
* due to limitations in the loading mechanism of unregistered classes. For this
* reason U should not be loaded at all and an appropriate warning should be
* printed.
*
* @requires vm.cds
* @requires vm.cds.custom.loaders
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
* @compile test-classes/RegUnregSuperApp.java test-classes/CustomLoadee3.java test-classes/CustomLoadee3Child.java
* @run main RegUnregSuperTest reg
*/
public class RegUnregSuperTest {
public static void main(String[] args) throws Exception {
final String variant = args[0];
final String appJar = JarBuilder.build(
"app", "RegUnregSuperApp", "DirectClassLoader", "CustomLoadee3", "CustomLoadee3Child"
);
OutputAnalyzer out;
final String classlist[] = new String[] {
"java/lang/Object id: 0",
"CustomLoadee3 id: 1",
"CustomLoadee3 id: 2 super: 0 source: " + appJar,
"CustomLoadee3Child id: 3 super: " + ("reg".equals(variant) ? "1" : "2") + " source: " + appJar
};
out = TestCommon.testDump(appJar, classlist, "-Xlog:cds+class=debug");
out.shouldContain("app CustomLoadee3"); // Not using \n as below because it'll be "app CustomLoadee3 aot-linked" with AOTClassLinking
out.shouldNotContain("app CustomLoadee3Child");
out.shouldContain("unreg CustomLoadee3\n"); // Accepts "unreg CustomLoadee3" but not "unreg CustomLoadee3Child"
if ("reg".equals(variant)) {
out.shouldNotContain("unreg CustomLoadee3Child");
out.shouldContain("CustomLoadee3Child (id 3) has super-type CustomLoadee3 (id 1) obstructed by another class with the same name");
} else {
out.shouldContain("unreg CustomLoadee3Child");
out.shouldNotContain("[warning]");
}
out = TestCommon.exec(appJar, "-Xlog:class+load", "RegUnregSuperApp", variant);
TestCommon.checkExec(
out,
"CustomLoadee3Child source: " + ("reg".equals(variant) ? "file:" : "shared objects file")
);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2025, 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.
*/
class CustomLoadee5 {
public String toString() {
return "this is CustomLoadee5";
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2025, 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.
*/
class CustomLoadee5Child extends CustomLoadee5 {
public String toString() {
return "this is CustomLoadee5Child";
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2025, 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.
*/
import java.nio.file.Path;
import java.nio.file.FileSystems;
import java.net.URL;
import java.net.URLClassLoader;
/**
* See ../DifferentSourcesTest.java for details.
*/
public class DifferentSourcesApp {
public static void main(String args[]) throws Exception {
Path base = FileSystems.getDefault().getPath("base.jar");
Path sub = FileSystems.getDefault().getPath("sub.jar");
URL[] urls = new URL[] { base.toUri().toURL(), sub.toUri().toURL() };
URLClassLoader cl = new URLClassLoader(urls, /* parent = */ null);
Class<?> cls = cl.loadClass("CustomLoadee5Child");
System.out.println(cls.getName());
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) 2025, 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.
*/
import java.nio.file.FileSystems;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Set;
class DirectClassLoader extends URLClassLoader {
private final Set<String> directlyLoadedNames;
public DirectClassLoader(URL url, String... directlyLoadedNames) {
super(new URL[] { url });
this.directlyLoadedNames = Set.of(directlyLoadedNames);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
if (directlyLoadedNames.contains(name)) {
c = findClass(name);
} else {
c = super.loadClass(name);
}
}
return c;
}
}
}
/**
* See ../RegUnregSuperTest.java for details.
*/
public class RegUnregSuperApp {
private static final URL APP_JAR;
static {
final URL appJar;
try {
appJar = FileSystems.getDefault().getPath("app.jar").toUri().toURL();
} catch (Exception e) {
throw new RuntimeException(e);
}
APP_JAR = appJar;
}
public static void main(String args[]) throws Exception {
switch (args[0]) {
case "reg" -> loadWithRegisteredSuper();
case "unreg" -> loadWithUnregisteredSuper();
default -> throw new IllegalArgumentException("Unknown variant: " + args[0]);
}
}
private static void loadWithRegisteredSuper() throws Exception {
// Load unregistered super
final var unregisteredBaseCl = new DirectClassLoader(APP_JAR, "CustomLoadee3");
Class<?> unregisteredBase = unregisteredBaseCl.loadClass("CustomLoadee3");
checkClassLoader(unregisteredBase, unregisteredBaseCl);
// Load unregistered child with REGISTERED super
final var registeredBaseCl = new DirectClassLoader(APP_JAR, "CustomLoadee3Child");
Class<?> unregisteredChild = registeredBaseCl.loadClass("CustomLoadee3Child");
checkClassLoader(unregisteredChild, registeredBaseCl);
checkClassLoader(unregisteredChild.getSuperclass(), ClassLoader.getSystemClassLoader());
}
private static void loadWithUnregisteredSuper() throws Exception {
// Load registered super
final var systemCl = ClassLoader.getSystemClassLoader();
Class<?> registeredBase = systemCl.loadClass("CustomLoadee3");
checkClassLoader(registeredBase, systemCl);
// Load unregistered child with UNREGISTERED super
final var unregisteredBaseCl = new DirectClassLoader(APP_JAR, "CustomLoadee3", "CustomLoadee3Child");
Class<?> unregisteredChild = unregisteredBaseCl.loadClass("CustomLoadee3Child");
checkClassLoader(unregisteredChild, unregisteredBaseCl);
checkClassLoader(unregisteredChild.getSuperclass(), unregisteredBaseCl);
}
private static void checkClassLoader(Class<?> c, ClassLoader cl) {
ClassLoader realCl = c.getClassLoader();
if (realCl != cl) {
throw new RuntimeException(c + " has wrong loader: expected " + cl + ", got " + realCl);
}
}
}