diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index b3bfe79bb42..f8ba385bd62 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -42,6 +42,7 @@ #include "jvm.h" #include "logging/log.hpp" #include "logging/logTag.hpp" +#include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "oops/constantPool.hpp" #include "runtime/atomic.hpp" @@ -97,6 +98,12 @@ ClassListParser::~ClassListParser() { _instance = nullptr; } +int ClassListParser::parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) { + UnregisteredClasses::initialize(CHECK_0); + ClassListParser parser(classlist_path, parse_mode); + return parser.parse(THREAD); // returns the number of classes loaded. +} + int ClassListParser::parse(TRAPS) { int class_count = 0; @@ -390,6 +397,19 @@ bool ClassListParser::parse_uint_option(const char* option_name, int* value) { return false; } +objArrayOop ClassListParser::get_specified_interfaces(TRAPS) { + 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; + } +} + void ClassListParser::print_specified_interfaces() { const int n = _interfaces->length(); jio_fprintf(defaultStream::error_stream(), "Currently specified interfaces[%d] = {\n", n); @@ -474,7 +494,17 @@ 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* k = UnregisteredClasses::load_class(class_name, source_path, CHECK_NULL); + 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); + 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, + k->java_super()->external_name()); + } if (k->local_interfaces()->length() != _interfaces->length()) { print_specified_interfaces(); print_actual_interfaces(k); @@ -682,46 +712,3 @@ InstanceKlass* ClassListParser::lookup_class_by_id(int id) { assert(*klass_ptr != nullptr, "must be"); return *klass_ptr; } - - -InstanceKlass* ClassListParser::lookup_super_for_current_class(Symbol* super_name) { - if (!is_loading_from_source()) { - return nullptr; - } - - InstanceKlass* k = lookup_class_by_id(super()); - if (super_name != k->name()) { - error("The specified super class %s (id %d) does not match actual super class %s", - k->name()->as_klass_external_name(), super(), - super_name->as_klass_external_name()); - } - return k; -} - -InstanceKlass* ClassListParser::lookup_interface_for_current_class(Symbol* interface_name) { - if (!is_loading_from_source()) { - return nullptr; - } - - const int n = _interfaces->length(); - if (n == 0) { - error("Class %s implements the interface %s, but no interface has been specified in the input line", - _class_name, interface_name->as_klass_external_name()); - ShouldNotReachHere(); - } - - int i; - for (i=0; iat(i)); - if (interface_name == k->name()) { - return k; - } - } - - // interface_name is not specified by the "interfaces:" keyword. - print_specified_interfaces(); - error("The interface %s implemented by class %s does not match any of the specified interface IDs", - interface_name->as_klass_external_name(), _class_name); - ShouldNotReachHere(); - return nullptr; -} diff --git a/src/hotspot/share/cds/classListParser.hpp b/src/hotspot/share/cds/classListParser.hpp index 74a2ff10515..4234cd436ee 100644 --- a/src/hotspot/share/cds/classListParser.hpp +++ b/src/hotspot/share/cds/classListParser.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -134,12 +134,10 @@ class ClassListParser : public StackObj { ClassListParser(const char* file, ParseMode _parse_mode); ~ClassListParser(); + objArrayOop get_specified_interfaces(TRAPS); public: - static int parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) { - ClassListParser parser(classlist_path, parse_mode); - return parser.parse(THREAD); // returns the number of classes loaded. - } + static int parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS); static bool is_parsing_thread(); static ClassListParser* instance() { @@ -192,11 +190,6 @@ class ClassListParser : public StackObj { bool lambda_form_line() { return _lambda_form_line; } - // Look up the super or interface of the current class being loaded - // (in this->load_current_class()). - InstanceKlass* lookup_super_for_current_class(Symbol* super_name); - InstanceKlass* lookup_interface_for_current_class(Symbol* interface_name); - static void populate_cds_indy_info(const constantPoolHandle &pool, int cp_index, CDSIndyInfo* cii, TRAPS); }; #endif // SHARE_CDS_CLASSLISTPARSER_HPP diff --git a/src/hotspot/share/cds/unregisteredClasses.cpp b/src/hotspot/share/cds/unregisteredClasses.cpp index 06d006ea1bb..641f84c3dcd 100644 --- a/src/hotspot/share/cds/unregisteredClasses.cpp +++ b/src/hotspot/share/cds/unregisteredClasses.cpp @@ -29,7 +29,7 @@ #include "classfile/classLoaderExt.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/symbolTable.hpp" -#include "classfile/systemDictionaryShared.hpp" +#include "classfile/systemDictionary.hpp" #include "classfile/vmSymbols.hpp" #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" @@ -39,10 +39,22 @@ #include "runtime/javaCalls.hpp" #include "services/threadService.hpp" +InstanceKlass* UnregisteredClasses::_UnregisteredClassLoader_klass = nullptr; + +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); + } +} + // 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, TRAPS) { +InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path, + Handle super_class, objArrayHandle interfaces, TRAPS) { assert(name != nullptr, "invariant"); assert(DumpSharedSpaces, "this function is only used with -Xshare:dump"); @@ -50,19 +62,23 @@ InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path, T THREAD->get_thread_stat()->perf_timers_addr(), PerfClassTraceTime::CLASS_LOAD); + // Call CDS$UnregisteredClassLoader::load(String name, Class superClass, Class[] interfaces) + 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 url_classloader = get_url_classloader(path_symbol, CHECK_NULL); + Handle classloader = get_classloader(path_symbol, CHECK_NULL); Handle ext_class_name = java_lang_String::externalize_classname(name, CHECK_NULL); JavaValue result(T_OBJECT); - JavaCallArguments args(2); - args.set_receiver(url_classloader); + JavaCallArguments args(3); + args.set_receiver(classloader); args.push_oop(ext_class_name); - args.push_int(JNI_FALSE); + args.push_oop(super_class); + args.push_oop(interfaces); JavaCalls::call_virtual(&result, - vmClasses::URLClassLoader_klass(), - vmSymbols::loadClass_name(), - vmSymbols::string_boolean_class_signature(), + UnregisteredClassLoader_klass(), + methodName, + methodSignature, &args, CHECK_NULL); assert(result.get_type() == T_OBJECT, "just checking"); @@ -70,45 +86,35 @@ InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path, T return InstanceKlass::cast(java_lang_Class::as_Klass(obj)); } -class URLClassLoaderTable : public ResourceHashtable< +class UnregisteredClasses::ClassLoaderTable : public ResourceHashtable< Symbol*, OopHandle, 137, // prime number AnyObj::C_HEAP> {}; -static URLClassLoaderTable* _url_classloader_table = nullptr; +static UnregisteredClasses::ClassLoaderTable* _classloader_table = nullptr; -Handle UnregisteredClasses::create_url_classloader(Symbol* path, TRAPS) { +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); - JavaCalls::call_static(&result, - vmClasses::jdk_internal_loader_ClassLoaders_klass(), - vmSymbols::toFileURL_name(), - vmSymbols::toFileURL_signature(), - path_string, CHECK_NH); - assert(result.get_type() == T_OBJECT, "just checking"); - oop url_h = result.get_oop(); - objArrayHandle urls = oopFactory::new_objArray_handle(vmClasses::URL_klass(), 1, CHECK_NH); - urls->obj_at_put(0, url_h); - - Handle url_classloader = JavaCalls::construct_new_instance( - vmClasses::URLClassLoader_klass(), - vmSymbols::url_array_classloader_void_signature(), - urls, Handle(), CHECK_NH); - return url_classloader; + Handle classloader = JavaCalls::construct_new_instance( + UnregisteredClassLoader_klass(), + vmSymbols::string_void_signature(), + path_string, CHECK_NH); + return classloader; } -Handle UnregisteredClasses::get_url_classloader(Symbol* path, TRAPS) { - if (_url_classloader_table == nullptr) { - _url_classloader_table = new (mtClass)URLClassLoaderTable(); +Handle UnregisteredClasses::get_classloader(Symbol* path, TRAPS) { + if (_classloader_table == nullptr) { + _classloader_table = new (mtClass)ClassLoaderTable(); } - OopHandle* url_classloader_ptr = _url_classloader_table->get(path); - if (url_classloader_ptr != nullptr) { - return Handle(THREAD, (*url_classloader_ptr).resolve()); + OopHandle* classloader_ptr = _classloader_table->get(path); + if (classloader_ptr != nullptr) { + return Handle(THREAD, (*classloader_ptr).resolve()); } else { - Handle url_classloader = create_url_classloader(path, CHECK_NH); - _url_classloader_table->put(path, OopHandle(Universe::vm_global(), url_classloader())); + Handle classloader = create_classloader(path, CHECK_NH); + _classloader_table->put(path, OopHandle(Universe::vm_global(), classloader())); path->increment_refcount(); - return url_classloader; + return classloader; } } diff --git a/src/hotspot/share/cds/unregisteredClasses.hpp b/src/hotspot/share/cds/unregisteredClasses.hpp index a9f7dceaead..ea3a308c2de 100644 --- a/src/hotspot/share/cds/unregisteredClasses.hpp +++ b/src/hotspot/share/cds/unregisteredClasses.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -25,15 +25,30 @@ #ifndef SHARE_CDS_UNREGISTEREDCLASSES_HPP #define SHARE_CDS_UNREGISTEREDCLASSES_HPP +#include "memory/allStatic.hpp" #include "runtime/handles.hpp" +class InstanceKlass; +class Symbol; + class UnregisteredClasses: AllStatic { public: - static InstanceKlass* load_class(Symbol* h_name, const char* path, TRAPS); + static InstanceKlass* load_class(Symbol* h_name, const char* path, + Handle super_class, objArrayHandle interfaces, + TRAPS); + static void initialize(TRAPS); + static InstanceKlass* UnregisteredClassLoader_klass() { + return _UnregisteredClassLoader_klass; + } + + class ClassLoaderTable; private: - static Handle create_url_classloader(Symbol* path, TRAPS); - static Handle get_url_classloader(Symbol* path, TRAPS); + // 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); }; #endif // SHARE_CDS_UNREGISTEREDCLASSES_HPP diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 7022bfca7a5..de4efebb278 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -412,16 +412,6 @@ InstanceKlass* SystemDictionary::resolve_super_or_fail(Symbol* class_name, assert(super_name != nullptr, "null superclass for resolving"); assert(!Signature::is_array(super_name), "invalid superclass name"); -#if INCLUDE_CDS - if (DumpSharedSpaces) { - // Special processing for handling UNREGISTERED shared classes. - InstanceKlass* k = SystemDictionaryShared::lookup_super_for_unregistered_class(class_name, - super_name, is_superclass); - if (k) { - return k; - } - } -#endif // INCLUDE_CDS // If klass is already loaded, just return the superclass or superinterface. // Make sure there's a placeholder for the class_name before resolving. diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index 4744c38b87e..22a94133a9a 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -34,6 +34,7 @@ #include "cds/dumpTimeClassInfo.inline.hpp" #include "cds/metaspaceShared.hpp" #include "cds/runTimeClassInfo.hpp" +#include "cds/unregisteredClasses.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" @@ -331,6 +332,12 @@ 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 } @@ -449,45 +456,6 @@ bool SystemDictionaryShared::add_unregistered_class(Thread* current, InstanceKla return (klass == *v); } -// This function is called to lookup the super/interfaces of shared classes for -// unregistered loaders. E.g., SharedClass in the below example -// where "super:" (and optionally "interface:") have been specified. -// -// java/lang/Object id: 0 -// Interface id: 2 super: 0 source: cust.jar -// SharedClass id: 4 super: 0 interfaces: 2 source: cust.jar -InstanceKlass* SystemDictionaryShared::lookup_super_for_unregistered_class( - Symbol* class_name, Symbol* super_name, bool is_superclass) { - - assert(DumpSharedSpaces, "only when static dumping"); - - if (!ClassListParser::is_parsing_thread()) { - // Unregistered classes can be created only by ClassListParser::_parsing_thread. - - return nullptr; - } - - ClassListParser* parser = ClassListParser::instance(); - if (parser == nullptr) { - // We're still loading the well-known classes, before the ClassListParser is created. - return nullptr; - } - if (class_name->equals(parser->current_class_name())) { - // When this function is called, all the numbered super and interface types - // must have already been loaded. Hence this function is never recursively called. - if (is_superclass) { - return parser->lookup_super_for_current_class(super_name); - } else { - return parser->lookup_interface_for_current_class(super_name); - } - } else { - // The VM is not trying to resolve a super type of parser->current_class_name(). - // Instead, it's resolving an error class (because parser->current_class_name() has - // failed parsing or verification). Don't do anything here. - return nullptr; - } -} - void SystemDictionaryShared::set_shared_class_misc_info(InstanceKlass* k, ClassFileStream* cfs) { Arguments::assert_is_dumping_archive(); assert(!is_builtin(k), "must be unregistered class"); diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index bb2babff25a..9ecbec41d6a 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -144,7 +144,6 @@ /* support for CDS */ \ do_klass(ByteArrayInputStream_klass, java_io_ByteArrayInputStream ) \ do_klass(URL_klass, java_net_URL ) \ - do_klass(URLClassLoader_klass, java_net_URLClassLoader ) \ do_klass(Enum_klass, java_lang_Enum ) \ do_klass(Jar_Manifest_klass, java_util_jar_Manifest ) \ do_klass(jdk_internal_loader_BuiltinClassLoader_klass,jdk_internal_loader_BuiltinClassLoader ) \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index d4cc1d9c570..4cd9a1a6d0c 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -127,7 +127,6 @@ template(java_security_ProtectionDomain, "java/security/ProtectionDomain") \ template(java_security_SecureClassLoader, "java/security/SecureClassLoader") \ template(java_net_URL, "java/net/URL") \ - template(java_net_URLClassLoader, "java/net/URLClassLoader") \ template(java_util_jar_Manifest, "java/util/jar/Manifest") \ template(java_io_OutputStream, "java/io/OutputStream") \ template(java_io_Reader, "java/io/Reader") \ @@ -788,7 +787,6 @@ template(toFileURL_name, "toFileURL") \ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ template(url_void_signature, "(Ljava/net/URL;)V") \ - template(url_array_classloader_void_signature, "([Ljava/net/URL;Ljava/lang/ClassLoader;)V") \ \ /* Thread.dump_to_file jcmd */ \ template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ diff --git a/src/java.base/share/classes/jdk/internal/misc/CDS.java b/src/java.base/share/classes/jdk/internal/misc/CDS.java index c41adcf9c4c..a963a45a813 100644 --- a/src/java.base/share/classes/jdk/internal/misc/CDS.java +++ b/src/java.base/share/classes/jdk/internal/misc/CDS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -31,6 +31,10 @@ 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.Path; import java.util.Arrays; import java.util.ArrayList; import java.util.List; @@ -333,4 +337,112 @@ private static String dumpSharedArchive(boolean isStatic, String fileName) throw System.out.println("The process was attached by jcmd and dumped a " + (isStatic ? "static" : "dynamic") + " archive " + archiveFilePath); return archiveFilePath; } + + /** + * This class is used only by native JVM code at CDS dump time for loading + * "unregistered classes", which are archived classes that are intended to + * 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 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() + }; + } + + + /** + * Load the class of the given /name from the JAR file that was given to + * the constructor of the current UnregisteredClassLoader instance. This class must be + * a direct subclass of superClass. This class must be declared to implement + * the specified interfaces. + *

+ * 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; + } + } + + /** + * This method must be called from inside the load() method. The /name + * can be only: + *

    + *
  • the name parameter for load() + *
  • the name of the superClass parameter for load() + *
  • the name of one of the interfaces in interfaces parameter for load() + *
      + * + * For all other cases, a ClassNotFoundException 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 this.findClass(name) 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; + } + } + } + + throw new ClassNotFoundException(name); + } + } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java index b5386c54afb..2beb39d0b39 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -49,14 +49,15 @@ public static void main(String[] args) throws Throwable { //---------------------------------------------------------------------- // TESTGROUP E: super class and interfaces //---------------------------------------------------------------------- - dumpShouldFail( + dumpShouldPass( "TESTCASE E1: missing interfaces: keyword", appJar, classlist( "Hello", "java/lang/Object id: 1", "CustomLoadee2 id: 1 super: 1 source: " + customJarPath ), - "Class CustomLoadee2 implements the interface CustomInterface2_ia, but no interface has been specified in the input line"); + "java.lang.NoClassDefFoundError: CustomInterface2_ia", + "Cannot find CustomLoadee2"); dumpShouldFail( "TESTCASE E2: missing one interface", @@ -67,7 +68,7 @@ appJar, classlist( "CustomInterface2_ib id: 3 super: 1 source: " + customJarPath, "CustomLoadee2 id: 4 super: 1 interfaces: 2 source: " + customJarPath ), - "The interface CustomInterface2_ib implemented by class CustomLoadee2 does not match any of the specified interface IDs"); + "The number of interfaces (1) specified in class list does not match the class file (2)"); dumpShouldFail( "TESTCASE E3: specifying an interface that's not implemented by the class", @@ -101,5 +102,15 @@ appJar, classlist( "CustomLoadee2 id: 5 super: 4 interfaces: 2 3 source: " + customJarPath ), "The specified super class CustomLoadee (id 4) does not match actual super class java.lang.Object"); + + dumpShouldPass( + "TESTCASE E6: JAR file doesn't exist", + appJar, classlist( + "Hello", + "java/lang/Object id: 1", + "NoSuchClass id: 2 super: 1 source: no_such_file.jar" + ), + "Cannot find NoSuchClass", + "java.io.IOException: No such file: no_such_file.jar"); } }