8348240: Remove SystemDictionaryShared::lookup_super_for_unregistered_class()

Reviewed-by: ccheung, coleenp
This commit is contained in:
Ioi Lam 2025-01-23 22:02:48 +00:00
parent 48ece07214
commit 7f16a0875c
10 changed files with 231 additions and 153 deletions

View file

@ -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.inline.hpp"
#include "runtime/atomic.hpp"
@ -111,6 +112,12 @@ ClassListParser::~ClassListParser() {
_instance = nullptr;
}
void ClassListParser::parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) {
UnregisteredClasses::initialize(CHECK);
ClassListParser parser(classlist_path, parse_mode);
parser.parse(THREAD);
}
void ClassListParser::parse(TRAPS) {
for (; !_input_stream.done(); _input_stream.next()) {
_line = _input_stream.current_line();
@ -387,6 +394,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);
@ -514,7 +534,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);
@ -734,49 +764,6 @@ InstanceKlass* ClassListParser::lookup_class_by_id(int id) {
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; i<n; i++) {
InstanceKlass* k = lookup_class_by_id(_interfaces->at(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;
}
InstanceKlass* ClassListParser::find_builtin_class_helper(JavaThread* current, Symbol* class_name_symbol, oop class_loader_oop) {
Handle class_loader(current, class_loader_oop);
return SystemDictionary::find_instance_klass(current, class_name_symbol, class_loader);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, 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
@ -137,12 +137,10 @@ 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);
public:
static void parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) {
ClassListParser parser(classlist_path, parse_mode);
parser.parse(THREAD);
}
static void parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS);
static bool is_parsing_thread();
static ClassListParser* instance() {
@ -201,12 +199,6 @@ public:
}
bool is_loading_from_source();
// 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

View file

@ -28,7 +28,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"
@ -38,10 +38,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(CDSConfig::is_dumping_static_archive(), "this function is only used with -Xshare:dump");
@ -49,19 +61,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);
Symbol* findClass = SymbolTable::new_symbol("findClass");
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_oop(super_class);
args.push_oop(interfaces);
JavaCalls::call_virtual(&result,
vmClasses::URLClassLoader_klass(),
findClass,
vmSymbols::string_class_signature(),
UnregisteredClassLoader_klass(),
methodName,
methodSignature,
&args,
CHECK_NULL);
assert(result.get_type() == T_OBJECT, "just checking");
@ -69,45 +85,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;
}
}

View file

@ -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

View file

@ -423,16 +423,6 @@ InstanceKlass* SystemDictionary::resolve_with_circularity_detection(Symbol* clas
assert(next_name != nullptr, "null superclass for resolving");
assert(!Signature::is_array(next_name), "invalid superclass name");
#if INCLUDE_CDS
if (CDSConfig::is_dumping_static_archive()) {
// Special processing for handling UNREGISTERED shared classes.
InstanceKlass* k = SystemDictionaryShared::lookup_super_for_unregistered_class(class_name,
next_name, is_superclass);
if (k) {
return k;
}
}
#endif // INCLUDE_CDS
// If class_name is already loaded, just return the superclass or superinterface.
// Make sure there's a placeholder for the class_name before resolving.

View file

@ -35,6 +35,7 @@
#include "cds/heapShared.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"
@ -352,6 +353,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
}
@ -470,45 +477,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(CDSConfig::is_dumping_static_archive(), "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) {
assert(CDSConfig::is_dumping_archive(), "sanity");
assert(!is_builtin(k), "must be unregistered class");

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2024, 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
@ -138,7 +138,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 ) \

View file

@ -124,7 +124,6 @@ class SerializeClosure;
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_ByteArrayInputStream, "java/io/ByteArrayInputStream") \
template(java_io_Serializable, "java/io/Serializable") \
@ -739,7 +738,6 @@ class SerializeClosure;
template(runtimeSetup, "runtimeSetup") \
template(toFileURL_name, "toFileURL") \
template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \
template(url_array_classloader_void_signature, "([Ljava/net/URL;Ljava/lang/ClassLoader;)V") \
\
/* jcmd Thread.dump_to_file */ \
template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2024, 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.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.Path;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
@ -337,4 +341,112 @@ public class CDS {
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 <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;
}
}
/**
* 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;
}
}
}
throw new ClassNotFoundException(name);
}
}
}

View file

@ -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 class ClassListFormatE extends ClassListFormatBase {
//----------------------------------------------------------------------
// 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 @@ public class ClassListFormatE extends ClassListFormatBase {
"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 @@ public class ClassListFormatE extends ClassListFormatBase {
"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");
}
}