mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8315130: java.lang.IllegalAccessError when processing classlist to create CDS archive
Reviewed-by: iklam, ccheung
This commit is contained in:
parent
bca293d012
commit
46a12e781e
@ -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");
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user