8259839: SystemDictionary exports too much implementation
Reviewed-by: iklam, dholmes
This commit is contained in:
parent
189b65b2ca
commit
a421bfad28
@ -966,7 +966,7 @@ void ClassFileParser::parse_interfaces(const ClassFileStream* const stream,
|
||||
guarantee_property(unresolved_klass->char_at(0) != JVM_SIGNATURE_ARRAY,
|
||||
"Bad interface name in class file %s", CHECK);
|
||||
|
||||
// Call resolve_super so classcircularity is checked
|
||||
// Call resolve_super so class circularity is checked
|
||||
interf = SystemDictionary::resolve_super_or_fail(
|
||||
_class_name,
|
||||
unresolved_klass,
|
||||
@ -6206,7 +6206,7 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st
|
||||
CHECK);
|
||||
}
|
||||
// We check super class after class file is parsed and format is checked
|
||||
if (_super_class_index > 0 && NULL ==_super_klass) {
|
||||
if (_super_class_index > 0 && NULL == _super_klass) {
|
||||
Symbol* const super_class_name = cp->klass_name_at(_super_class_index);
|
||||
if (_access_flags.is_interface()) {
|
||||
// Before attempting to resolve the superclass, check for class format
|
||||
|
@ -88,7 +88,6 @@
|
||||
#include "jfr/jfr.hpp"
|
||||
#endif
|
||||
|
||||
PlaceholderTable* SystemDictionary::_placeholders = NULL;
|
||||
LoaderConstraintTable* SystemDictionary::_loader_constraints = NULL;
|
||||
ResolutionErrorTable* SystemDictionary::_resolution_errors = NULL;
|
||||
SymbolPropertyTable* SystemDictionary::_invoke_method_table = NULL;
|
||||
@ -98,9 +97,17 @@ OopHandle SystemDictionary::_java_system_loader;
|
||||
OopHandle SystemDictionary::_java_platform_loader;
|
||||
|
||||
// Default ProtectionDomainCacheSize value
|
||||
|
||||
const int defaultProtectionDomainCacheSize = 1009;
|
||||
|
||||
const int _loader_constraint_size = 107; // number of entries in constraint table
|
||||
const int _resolution_error_size = 107; // number of entries in resolution error table
|
||||
const int _invoke_method_size = 139; // number of entries in invoke method table
|
||||
|
||||
// Hashtable holding placeholders for classes being loaded.
|
||||
const int _placeholder_table_size = 1009;
|
||||
PlaceholderTable* _placeholders = NULL;
|
||||
static PlaceholderTable* placeholders() { return _placeholders; }
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Java-level SystemLoader and PlatformLoader
|
||||
oop SystemDictionary::java_system_loader() {
|
||||
@ -211,55 +218,55 @@ Symbol* SystemDictionary::class_name_symbol(const char* name, Symbol* exception,
|
||||
return SymbolTable::new_symbol(name);
|
||||
}
|
||||
|
||||
// Forwards to resolve_or_null
|
||||
|
||||
Klass* SystemDictionary::resolve_or_fail(Symbol* class_name, Handle class_loader, Handle protection_domain, bool throw_error, TRAPS) {
|
||||
Klass* klass = resolve_or_null(class_name, class_loader, protection_domain, THREAD);
|
||||
if (HAS_PENDING_EXCEPTION || klass == NULL) {
|
||||
// can return a null klass
|
||||
klass = handle_resolution_exception(class_name, throw_error, klass, THREAD);
|
||||
}
|
||||
return klass;
|
||||
#ifdef ASSERT
|
||||
// Used to verify that class loading succeeded in adding k to the dictionary.
|
||||
void verify_dictionary_entry(Symbol* class_name, InstanceKlass* k) {
|
||||
MutexLocker mu(SystemDictionary_lock);
|
||||
ClassLoaderData* loader_data = k->class_loader_data();
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
assert(class_name == k->name(), "Must be the same");
|
||||
unsigned int name_hash = dictionary->compute_hash(class_name);
|
||||
InstanceKlass* kk = dictionary->find_class(name_hash, class_name);
|
||||
assert(kk == k, "should be present in dictionary");
|
||||
}
|
||||
#endif
|
||||
|
||||
Klass* SystemDictionary::handle_resolution_exception(Symbol* class_name,
|
||||
bool throw_error,
|
||||
Klass* klass, TRAPS) {
|
||||
static void handle_resolution_exception(Symbol* class_name, bool throw_error, TRAPS) {
|
||||
if (HAS_PENDING_EXCEPTION) {
|
||||
// If we have a pending exception we forward it to the caller, unless throw_error is true,
|
||||
// in which case we have to check whether the pending exception is a ClassNotFoundException,
|
||||
// and if so convert it to a NoClassDefFoundError
|
||||
// And chain the original ClassNotFoundException
|
||||
// and convert it to a NoClassDefFoundError and chain the original ClassNotFoundException.
|
||||
if (throw_error && PENDING_EXCEPTION->is_a(SystemDictionary::ClassNotFoundException_klass())) {
|
||||
ResourceMark rm(THREAD);
|
||||
assert(klass == NULL, "Should not have result with exception pending");
|
||||
Handle e(THREAD, PENDING_EXCEPTION);
|
||||
CLEAR_PENDING_EXCEPTION;
|
||||
THROW_MSG_CAUSE_NULL(vmSymbols::java_lang_NoClassDefFoundError(), class_name->as_C_string(), e);
|
||||
THROW_MSG_CAUSE(vmSymbols::java_lang_NoClassDefFoundError(), class_name->as_C_string(), e);
|
||||
} else {
|
||||
return NULL;
|
||||
return; // the caller will throw the incoming exception
|
||||
}
|
||||
}
|
||||
// Class not found, throw appropriate error or exception depending on value of throw_error
|
||||
if (klass == NULL) {
|
||||
ResourceMark rm(THREAD);
|
||||
if (throw_error) {
|
||||
THROW_MSG_NULL(vmSymbols::java_lang_NoClassDefFoundError(), class_name->as_C_string());
|
||||
} else {
|
||||
THROW_MSG_NULL(vmSymbols::java_lang_ClassNotFoundException(), class_name->as_C_string());
|
||||
}
|
||||
// If the class is not found, ie, caller has checked that klass is NULL, throw the appropriate
|
||||
// error or exception depending on the value of throw_error.
|
||||
ResourceMark rm(THREAD);
|
||||
if (throw_error) {
|
||||
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), class_name->as_C_string());
|
||||
} else {
|
||||
THROW_MSG(vmSymbols::java_lang_ClassNotFoundException(), class_name->as_C_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Forwards to resolve_or_null
|
||||
|
||||
Klass* SystemDictionary::resolve_or_fail(Symbol* class_name, Handle class_loader, Handle protection_domain,
|
||||
bool throw_error, TRAPS) {
|
||||
Klass* klass = resolve_or_null(class_name, class_loader, protection_domain, THREAD);
|
||||
// Check for pending exception or null klass, and throw exception
|
||||
if (HAS_PENDING_EXCEPTION || klass == NULL) {
|
||||
handle_resolution_exception(class_name, throw_error, CHECK_NULL);
|
||||
}
|
||||
return klass;
|
||||
}
|
||||
|
||||
|
||||
Klass* SystemDictionary::resolve_or_fail(Symbol* class_name,
|
||||
bool throw_error, TRAPS)
|
||||
{
|
||||
return resolve_or_fail(class_name, Handle(), Handle(), throw_error, THREAD);
|
||||
}
|
||||
|
||||
|
||||
// Forwards to resolve_array_class_or_null or resolve_instance_class_or_null
|
||||
|
||||
Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) {
|
||||
@ -287,10 +294,6 @@ InstanceKlass* SystemDictionary::resolve_instance_class_or_null_helper(Symbol* c
|
||||
}
|
||||
}
|
||||
|
||||
Klass* SystemDictionary::resolve_or_null(Symbol* class_name, TRAPS) {
|
||||
return resolve_or_null(class_name, Handle(), Handle(), THREAD);
|
||||
}
|
||||
|
||||
// Forwards to resolve_instance_class_or_null
|
||||
|
||||
Klass* SystemDictionary::resolve_array_class_or_null(Symbol* class_name,
|
||||
@ -323,9 +326,9 @@ Klass* SystemDictionary::resolve_array_class_or_null(Symbol* class_name,
|
||||
// Must be called for any super-class or super-interface resolution
|
||||
// during class definition to allow class circularity checking
|
||||
// super-interface callers:
|
||||
// parse_interfaces - for defineClass & jvmtiRedefineClasses
|
||||
// parse_interfaces - for defineClass
|
||||
// super-class callers:
|
||||
// ClassFileParser - for defineClass & jvmtiRedefineClasses
|
||||
// ClassFileParser - for defineClass
|
||||
// load_shared_class - while loading a class from shared archive
|
||||
// resolve_instance_class_or_null:
|
||||
// via: handle_parallel_super_load
|
||||
@ -352,14 +355,11 @@ Klass* SystemDictionary::resolve_array_class_or_null(Symbol* class_name,
|
||||
// 4.2 resolve_instance_class_or_null Base, finds placeholder for Base (super Super)
|
||||
// 4.3 calls resolve_super_or_fail Super in parallel on own thread T2
|
||||
// 4.4 finds T2, Super -> throws class circularity
|
||||
// Must be called, even if superclass is null, since this is
|
||||
// where the placeholder entry is created which claims this
|
||||
// thread is loading this class/classloader.
|
||||
// Be careful when modifying this code: once you have run
|
||||
// placeholders()->find_and_add(PlaceholderTable::LOAD_SUPER),
|
||||
// you need to find_and_remove it before returning.
|
||||
// So be careful to not exit with a CHECK_ macro betweeen these calls.
|
||||
InstanceKlass* SystemDictionary::resolve_super_or_fail(Symbol* child_name,
|
||||
// So be careful to not exit with a CHECK_ macro between these calls.
|
||||
InstanceKlass* SystemDictionary::resolve_super_or_fail(Symbol* class_name,
|
||||
Symbol* super_name,
|
||||
Handle class_loader,
|
||||
Handle protection_domain,
|
||||
@ -369,7 +369,7 @@ InstanceKlass* SystemDictionary::resolve_super_or_fail(Symbol* child_name,
|
||||
#if INCLUDE_CDS
|
||||
if (DumpSharedSpaces) {
|
||||
// Special processing for handling UNREGISTERED shared classes.
|
||||
InstanceKlass* k = SystemDictionaryShared::dump_time_resolve_super_or_fail(child_name,
|
||||
InstanceKlass* k = SystemDictionaryShared::dump_time_resolve_super_or_fail(class_name,
|
||||
super_name, class_loader, protection_domain, is_superclass, CHECK_NULL);
|
||||
if (k) {
|
||||
return k;
|
||||
@ -377,16 +377,16 @@ InstanceKlass* SystemDictionary::resolve_super_or_fail(Symbol* child_name,
|
||||
}
|
||||
#endif // INCLUDE_CDS
|
||||
|
||||
// Double-check, if child class is already loaded, just return super-class,interface
|
||||
// Double-check, if klass is already loaded, just return super-class,interface
|
||||
// Don't add a placedholder if already loaded, i.e. already in appropriate class loader
|
||||
// dictionary.
|
||||
// Make sure there's a placeholder for the *child* before resolving.
|
||||
// Make sure there's a placeholder for the *klass* before resolving.
|
||||
// Used as a claim that this thread is currently loading superclass/classloader
|
||||
// Used here for ClassCircularity checks and also for heap verification
|
||||
// (every InstanceKlass needs to be in its class loader dictionary or have a placeholder).
|
||||
// Must check ClassCircularity before checking if super class is already loaded.
|
||||
//
|
||||
// We might not already have a placeholder if this child_name was
|
||||
// We might not already have a placeholder if this class_name was
|
||||
// first seen via resolve_from_stream (jni_DefineClass or JVM_DefineClass);
|
||||
// the name of the class might not be known until the stream is actually
|
||||
// parsed.
|
||||
@ -394,39 +394,36 @@ InstanceKlass* SystemDictionary::resolve_super_or_fail(Symbol* child_name,
|
||||
|
||||
ClassLoaderData* loader_data = class_loader_data(class_loader);
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
unsigned int name_hash = dictionary->compute_hash(child_name);
|
||||
assert(placeholders()->compute_hash(child_name) == name_hash, "they're the same hashcode");
|
||||
unsigned int name_hash = dictionary->compute_hash(class_name);
|
||||
assert(placeholders()->compute_hash(class_name) == name_hash, "they're the same hashcode");
|
||||
// can't throw error holding a lock
|
||||
bool throw_circularity_error = false;
|
||||
{
|
||||
MutexLocker mu(THREAD, SystemDictionary_lock);
|
||||
InstanceKlass* childk = dictionary->find_class(name_hash, child_name);
|
||||
InstanceKlass* klassk = dictionary->find_class(name_hash, class_name);
|
||||
InstanceKlass* quicksuperk;
|
||||
// to support // loading: if child done loading, just return superclass
|
||||
// if super_name, & class_loader don't match:
|
||||
// if initial define, SD update will give LinkageError
|
||||
// if redefine: compare_class_versions will give HIERARCHY_CHANGED
|
||||
// so we don't throw an exception here.
|
||||
// see: nsk redefclass014 & java.lang.instrument Instrument032
|
||||
if ((childk != NULL ) && (is_superclass) &&
|
||||
((quicksuperk = childk->java_super()) != NULL) &&
|
||||
// To support parallel loading: if class is done loading, just return the superclass
|
||||
// if the super_name matches class->super()->name() and if the class loaders match.
|
||||
// Otherwise, a LinkageError will be thrown later.
|
||||
if (klassk != NULL && is_superclass &&
|
||||
((quicksuperk = klassk->java_super()) != NULL) &&
|
||||
((quicksuperk->name() == super_name) &&
|
||||
(quicksuperk->class_loader() == class_loader()))) {
|
||||
return quicksuperk;
|
||||
} else {
|
||||
PlaceholderEntry* probe = placeholders()->get_entry(name_hash, child_name, loader_data);
|
||||
PlaceholderEntry* probe = placeholders()->get_entry(name_hash, class_name, loader_data);
|
||||
if (probe && probe->check_seen_thread(THREAD, PlaceholderTable::LOAD_SUPER)) {
|
||||
throw_circularity_error = true;
|
||||
}
|
||||
}
|
||||
if (!throw_circularity_error) {
|
||||
// Be careful not to exit resolve_super
|
||||
PlaceholderEntry* newprobe = placeholders()->find_and_add(name_hash, child_name, loader_data, PlaceholderTable::LOAD_SUPER, super_name, THREAD);
|
||||
// Be careful not to exit resolve_super without removing this placeholder.
|
||||
PlaceholderEntry* newprobe = placeholders()->find_and_add(name_hash, class_name, loader_data, PlaceholderTable::LOAD_SUPER, super_name, THREAD);
|
||||
}
|
||||
}
|
||||
if (throw_circularity_error) {
|
||||
ResourceMark rm(THREAD);
|
||||
THROW_MSG_NULL(vmSymbols::java_lang_ClassCircularityError(), child_name->as_C_string());
|
||||
THROW_MSG_NULL(vmSymbols::java_lang_ClassCircularityError(), class_name->as_C_string());
|
||||
}
|
||||
|
||||
// java.lang.Object should have been found above
|
||||
@ -446,16 +443,13 @@ InstanceKlass* SystemDictionary::resolve_super_or_fail(Symbol* child_name,
|
||||
// the loader_data. parseClassFile adds the instanceKlass to loader_data.
|
||||
{
|
||||
MutexLocker mu(THREAD, SystemDictionary_lock);
|
||||
placeholders()->find_and_remove(name_hash, child_name, loader_data, PlaceholderTable::LOAD_SUPER, THREAD);
|
||||
placeholders()->find_and_remove(name_hash, class_name, loader_data, PlaceholderTable::LOAD_SUPER, THREAD);
|
||||
SystemDictionary_lock->notify_all();
|
||||
}
|
||||
|
||||
// Check for pending exception or null superk, and throw exception
|
||||
if (HAS_PENDING_EXCEPTION || superk == NULL) {
|
||||
// can null superk
|
||||
Klass* k = handle_resolution_exception(super_name, true, superk, THREAD);
|
||||
assert(k == NULL || k == superk, "must be");
|
||||
if (k == NULL) {
|
||||
superk = NULL;
|
||||
}
|
||||
handle_resolution_exception(super_name, true, CHECK_NULL);
|
||||
}
|
||||
|
||||
return superk;
|
||||
@ -758,28 +752,25 @@ InstanceKlass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
if (!class_has_been_loaded) {
|
||||
bool load_instance_added = false;
|
||||
|
||||
// add placeholder entry to record loading instance class
|
||||
// Five cases:
|
||||
// All cases need to prevent modifying bootclasssearchpath
|
||||
// in parallel with a classload of same classname
|
||||
// Redefineclasses uses existence of the placeholder for the duration
|
||||
// of the class load to prevent concurrent redefinition of not completely
|
||||
// defined classes.
|
||||
// case 1. traditional classloaders that rely on the classloader object lock
|
||||
// - no other need for LOAD_INSTANCE
|
||||
// case 2. traditional classloaders that break the classloader object lock
|
||||
// as a deadlock workaround. Detection of this case requires that
|
||||
// Add placeholder entry to record loading instance class
|
||||
// Three cases:
|
||||
// case 1. Bootstrap classloader
|
||||
// This classloader supports parallelism at the classloader level
|
||||
// but only allows a single thread to load a class/classloader pair.
|
||||
// The LOAD_INSTANCE placeholder is the mechanism for mutual exclusion.
|
||||
// case 2. parallelCapable user level classloaders
|
||||
// These class loaders don't lock the object until load_instance_class is
|
||||
// called after this placeholder is added.
|
||||
// Allow parallel classloading of a class/classloader pair where mutual
|
||||
// exclusion is provided by this lock in the class loader Java code.
|
||||
// case 3. traditional classloaders that rely on the classloader object lock
|
||||
// There should be no need for need for LOAD_INSTANCE, except:
|
||||
// case 4. traditional class loaders that break the classloader object lock
|
||||
// as a legacy deadlock workaround. Detection of this case requires that
|
||||
// this check is done while holding the classloader object lock,
|
||||
// and that lock is still held when calling classloader's loadClass.
|
||||
// For these classloaders, we ensure that the first requestor
|
||||
// completes the load and other requestors wait for completion.
|
||||
// case 3. Bootstrap classloader - don't own objectLocker
|
||||
// This classloader supports parallelism at the classloader level,
|
||||
// but only allows a single load of a class/classloader pair.
|
||||
// No performance benefit and no deadlock issues.
|
||||
// case 4. parallelCapable user level classloaders - without objectLocker
|
||||
// Allow parallel classloading of a class/classloader pair
|
||||
|
||||
{
|
||||
MutexLocker mu(THREAD, SystemDictionary_lock);
|
||||
if (class_loader.is_null() || !is_parallelCapable(class_loader)) {
|
||||
@ -790,15 +781,15 @@ InstanceKlass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
if (oldprobe->check_seen_thread(THREAD, PlaceholderTable::LOAD_INSTANCE)) {
|
||||
throw_circularity_error = true;
|
||||
} else {
|
||||
// case 1: traditional: should never see load_in_progress.
|
||||
// case 3: traditional: should never see load_in_progress.
|
||||
while (!class_has_been_loaded && oldprobe != NULL && oldprobe->instance_load_in_progress()) {
|
||||
|
||||
// case 3: bootstrap classloader: prevent futile classloading,
|
||||
// case 1: bootstrap classloader: prevent futile classloading,
|
||||
// wait on first requestor
|
||||
if (class_loader.is_null()) {
|
||||
SystemDictionary_lock->wait();
|
||||
} else {
|
||||
// case 2: traditional with broken classloader lock. wait on first
|
||||
// case 4: traditional with broken classloader lock. wait on first
|
||||
// requestor.
|
||||
double_lock_wait(THREAD, lockObject);
|
||||
}
|
||||
@ -815,10 +806,8 @@ InstanceKlass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
}
|
||||
}
|
||||
}
|
||||
// All cases: add LOAD_INSTANCE holding SystemDictionary_lock
|
||||
// case 4: parallelCapable: allow competing threads to try
|
||||
// LOAD_INSTANCE in parallel
|
||||
|
||||
// All cases: add LOAD_INSTANCE while holding the SystemDictionary_lock
|
||||
if (!throw_circularity_error && !class_has_been_loaded) {
|
||||
PlaceholderEntry* newprobe = placeholders()->find_and_add(name_hash, name, loader_data,
|
||||
PlaceholderTable::LOAD_INSTANCE, NULL, THREAD);
|
||||
@ -896,14 +885,9 @@ InstanceKlass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
if (class_load_start_event.should_commit()) {
|
||||
post_class_load_event(&class_load_start_event, loaded_class, loader_data);
|
||||
}
|
||||
#ifdef ASSERT
|
||||
{
|
||||
ClassLoaderData* loader_data = loaded_class->class_loader_data();
|
||||
MutexLocker mu(THREAD, SystemDictionary_lock);
|
||||
InstanceKlass* kk = find_class(name, loader_data);
|
||||
assert(kk == loaded_class, "should be present in dictionary");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Make sure we have the right class in the dictionary
|
||||
DEBUG_ONLY(verify_dictionary_entry(name, loaded_class));
|
||||
|
||||
// return if the protection domain in NULL
|
||||
if (protection_domain() == NULL) return loaded_class;
|
||||
@ -1118,33 +1102,23 @@ InstanceKlass* SystemDictionary::resolve_from_stream(Symbol* class_name,
|
||||
|
||||
// Add class just loaded
|
||||
// If a class loader supports parallel classloading, handle parallel define requests.
|
||||
// find_or_define_instance_class may return a different InstanceKlass
|
||||
// find_or_define_instance_class may return a different InstanceKlass,
|
||||
// in which case the old k would be deallocated
|
||||
if (is_parallelCapable(class_loader)) {
|
||||
InstanceKlass* defined_k = find_or_define_instance_class(h_name, class_loader, k, THREAD);
|
||||
if (!HAS_PENDING_EXCEPTION && defined_k != k) {
|
||||
// If a parallel capable class loader already defined this class, register 'k' for cleanup.
|
||||
assert(defined_k != NULL, "Should have a klass if there's no exception");
|
||||
loader_data->add_to_deallocate_list(k);
|
||||
k = defined_k;
|
||||
}
|
||||
k = find_or_define_instance_class(h_name, class_loader, k, CHECK_NULL);
|
||||
} else {
|
||||
define_instance_class(k, class_loader, THREAD);
|
||||
}
|
||||
|
||||
// If defining the class throws an exception register 'k' for cleanup.
|
||||
if (HAS_PENDING_EXCEPTION) {
|
||||
assert(k != NULL, "Must have an instance klass here!");
|
||||
loader_data->add_to_deallocate_list(k);
|
||||
return NULL;
|
||||
// If defining the class throws an exception register 'k' for cleanup.
|
||||
if (HAS_PENDING_EXCEPTION) {
|
||||
assert(k != NULL, "Must have an instance klass here!");
|
||||
loader_data->add_to_deallocate_list(k);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we have an entry in the SystemDictionary on success
|
||||
debug_only( {
|
||||
MutexLocker mu(THREAD, SystemDictionary_lock);
|
||||
|
||||
Klass* check = find_class(h_name, k->class_loader_data());
|
||||
assert(check == k, "should be present in the dictionary");
|
||||
} );
|
||||
DEBUG_ONLY(verify_dictionary_entry(h_name, k));
|
||||
|
||||
return k;
|
||||
}
|
||||
@ -1251,18 +1225,18 @@ bool SystemDictionary::is_shared_class_visible_impl(Symbol* class_name,
|
||||
return visible;
|
||||
}
|
||||
|
||||
bool SystemDictionary::check_shared_class_super_type(InstanceKlass* child, InstanceKlass* super_type,
|
||||
bool SystemDictionary::check_shared_class_super_type(InstanceKlass* klass, InstanceKlass* super_type,
|
||||
Handle class_loader, Handle protection_domain,
|
||||
bool is_superclass, TRAPS) {
|
||||
assert(super_type->is_shared(), "must be");
|
||||
|
||||
Klass *found = resolve_super_or_fail(child->name(), super_type->name(),
|
||||
Klass *found = resolve_super_or_fail(klass->name(), super_type->name(),
|
||||
class_loader, protection_domain, is_superclass, CHECK_0);
|
||||
if (found == super_type) {
|
||||
return true;
|
||||
} else {
|
||||
// The dynamically resolved super type is not the same as the one we used during dump time,
|
||||
// so we cannot use the child class.
|
||||
// so we cannot use the class.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1499,17 +1473,7 @@ InstanceKlass* SystemDictionary::load_instance_class(Symbol* class_name, Handle
|
||||
|
||||
// find_or_define_instance_class may return a different InstanceKlass
|
||||
if (k != NULL) {
|
||||
InstanceKlass* defined_k =
|
||||
find_or_define_instance_class(class_name, class_loader, k, THREAD);
|
||||
if (!HAS_PENDING_EXCEPTION && defined_k != k) {
|
||||
// If a parallel capable class loader already defined this class, register 'k' for cleanup.
|
||||
assert(defined_k != NULL, "Should have a klass if there's no exception");
|
||||
loader_data->add_to_deallocate_list(k);
|
||||
k = defined_k;
|
||||
} else if (HAS_PENDING_EXCEPTION) {
|
||||
loader_data->add_to_deallocate_list(k);
|
||||
return NULL;
|
||||
}
|
||||
k = find_or_define_instance_class(class_name, class_loader, k, CHECK_NULL);
|
||||
}
|
||||
return k;
|
||||
} else {
|
||||
@ -1578,8 +1542,8 @@ void SystemDictionary::define_instance_class(InstanceKlass* k, Handle class_load
|
||||
ClassLoaderData* loader_data = k->class_loader_data();
|
||||
assert(loader_data->class_loader() == class_loader(), "they must be the same");
|
||||
|
||||
// Bootstrap and other parallel classloaders don't acquire lock,
|
||||
// they use a placeholder token instead.
|
||||
// Bootstrap and other parallel classloaders don't acquire a lock,
|
||||
// they use placeholder token.
|
||||
// If a parallelCapable class loader calls define_instance_class instead of
|
||||
// find_or_define_instance_class to get here, we have a timing
|
||||
// hole with systemDictionary updates and check_constraints
|
||||
@ -1656,8 +1620,8 @@ void SystemDictionary::define_instance_class(InstanceKlass* k, Handle class_load
|
||||
// placeholders()->find_and_add(PlaceholderTable::DEFINE_CLASS),
|
||||
// you need to find_and_remove it before returning.
|
||||
// So be careful to not exit with a CHECK_ macro between these calls.
|
||||
InstanceKlass* SystemDictionary::find_or_define_instance_class(Symbol* class_name, Handle class_loader,
|
||||
InstanceKlass* k, TRAPS) {
|
||||
InstanceKlass* SystemDictionary::find_or_define_helper(Symbol* class_name, Handle class_loader,
|
||||
InstanceKlass* k, TRAPS) {
|
||||
|
||||
Symbol* name_h = k->name(); // passed in class_name may be null
|
||||
ClassLoaderData* loader_data = class_loader_data(class_loader);
|
||||
@ -1719,38 +1683,24 @@ InstanceKlass* SystemDictionary::find_or_define_instance_class(Symbol* class_nam
|
||||
SystemDictionary_lock->notify_all();
|
||||
}
|
||||
|
||||
return k;
|
||||
return HAS_PENDING_EXCEPTION ? NULL : k;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Lookup
|
||||
|
||||
// Basic find on classes in the midst of being loaded
|
||||
Symbol* SystemDictionary::find_placeholder(Symbol* class_name,
|
||||
ClassLoaderData* loader_data) {
|
||||
assert_locked_or_safepoint(SystemDictionary_lock);
|
||||
unsigned int name_hash = placeholders()->compute_hash(class_name);
|
||||
return placeholders()->find_entry(name_hash, class_name, loader_data);
|
||||
}
|
||||
|
||||
|
||||
// Used for assertions and verification only
|
||||
// Precalculating the hash and index is an optimization because there are many lookups
|
||||
// before adding the class.
|
||||
InstanceKlass* SystemDictionary::find_class(Symbol* class_name, ClassLoaderData* loader_data) {
|
||||
assert_locked_or_safepoint(SystemDictionary_lock);
|
||||
#ifndef ASSERT
|
||||
guarantee(VerifyBeforeGC ||
|
||||
VerifyDuringGC ||
|
||||
VerifyBeforeExit ||
|
||||
VerifyDuringStartup ||
|
||||
VerifyAfterGC, "too expensive");
|
||||
#endif
|
||||
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
unsigned int name_hash = dictionary->compute_hash(class_name);
|
||||
return dictionary->find_class(name_hash, class_name);
|
||||
// If a class loader supports parallel classloading handle parallel define requests.
|
||||
// find_or_define_instance_class may return a different InstanceKlass
|
||||
InstanceKlass* SystemDictionary::find_or_define_instance_class(Symbol* class_name, Handle class_loader,
|
||||
InstanceKlass* k, TRAPS) {
|
||||
InstanceKlass* defined_k = find_or_define_helper(class_name, class_loader, k, THREAD);
|
||||
// Clean up original InstanceKlass if duplicate or error
|
||||
if (!HAS_PENDING_EXCEPTION && defined_k != k) {
|
||||
// If a parallel capable class loader already defined this class, register 'k' for cleanup.
|
||||
assert(defined_k != NULL, "Should have a klass if there's no exception");
|
||||
k->class_loader_data()->add_to_deallocate_list(k);
|
||||
} else if (HAS_PENDING_EXCEPTION) {
|
||||
assert(defined_k == NULL, "Should not have a klass if there's an exception");
|
||||
k->class_loader_data()->add_to_deallocate_list(k);
|
||||
}
|
||||
return defined_k;
|
||||
}
|
||||
|
||||
|
||||
@ -1849,6 +1799,17 @@ void SystemDictionary::initialize(TRAPS) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
// Verify that this placeholder exists since this class is in the middle of loading.
|
||||
void verify_placeholder(Symbol* class_name, ClassLoaderData* loader_data) {
|
||||
// Only parallel capable class loaders use placeholder table for define class.
|
||||
assert_locked_or_safepoint(SystemDictionary_lock);
|
||||
unsigned int name_hash = placeholders()->compute_hash(class_name);
|
||||
Symbol* ph_check = placeholders()->find_entry(name_hash, class_name, loader_data);
|
||||
assert(ph_check != NULL, "This placeholder should exist");
|
||||
}
|
||||
#endif // ASSERT
|
||||
|
||||
// Constraints on class loaders. The details of the algorithm can be
|
||||
// found in the OOPSLA'98 paper "Dynamic Class Loading in the Java
|
||||
// Virtual Machine" by Sheng Liang and Gilad Bracha. The basic idea is
|
||||
@ -1877,8 +1838,6 @@ void SystemDictionary::check_constraints(unsigned int name_hash,
|
||||
// If different InstanceKlass - duplicate class definition,
|
||||
// else - ok, class loaded by a different thread in parallel.
|
||||
// We should only have found it if it was done loading and ok to use.
|
||||
// The dictionary only holds instance classes, placeholders
|
||||
// also hold array classes.
|
||||
|
||||
if ((defining == true) || (k != check)) {
|
||||
throwException = true;
|
||||
@ -1890,10 +1849,7 @@ void SystemDictionary::check_constraints(unsigned int name_hash,
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
Symbol* ph_check = find_placeholder(name, loader_data);
|
||||
assert(ph_check == NULL || ph_check == name, "invalid symbol");
|
||||
#endif
|
||||
DEBUG_ONLY(if (is_parallelCapable(class_loader)) verify_placeholder(name, loader_data));
|
||||
|
||||
if (throwException == false) {
|
||||
if (constraints()->check_or_update(k, class_loader, name) == false) {
|
||||
@ -1940,12 +1896,6 @@ void SystemDictionary::update_dictionary(unsigned int hash,
|
||||
if (sd_check == NULL) {
|
||||
dictionary->add_klass(hash, name, k);
|
||||
}
|
||||
#ifdef ASSERT
|
||||
sd_check = dictionary->find_class(hash, name);
|
||||
assert (sd_check != NULL, "should have entry in dictionary");
|
||||
// Note: there may be a placeholder entry: for circularity testing
|
||||
// or for parallel defines
|
||||
#endif
|
||||
SystemDictionary_lock->notify_all();
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,6 @@ class BootstrapInfo;
|
||||
class ClassFileStream;
|
||||
class ClassLoadInfo;
|
||||
class Dictionary;
|
||||
class PlaceholderTable;
|
||||
class LoaderConstraintTable;
|
||||
template <MEMFLAGS F> class HashtableBucket;
|
||||
class ResolutionErrorTable;
|
||||
@ -105,24 +104,23 @@ class SystemDictionary : public vmClasses {
|
||||
|
||||
static Klass* resolve_or_fail(Symbol* class_name, Handle class_loader, Handle protection_domain, bool throw_error, TRAPS);
|
||||
// Convenient call for null loader and protection domain.
|
||||
static Klass* resolve_or_fail(Symbol* class_name, bool throw_error, TRAPS);
|
||||
protected:
|
||||
// handle error translation for resolve_or_null results
|
||||
static Klass* handle_resolution_exception(Symbol* class_name, bool throw_error, Klass* klass, TRAPS);
|
||||
|
||||
public:
|
||||
static Klass* resolve_or_fail(Symbol* class_name, bool throw_error, TRAPS) {
|
||||
return resolve_or_fail(class_name, Handle(), Handle(), throw_error, THREAD);
|
||||
}
|
||||
|
||||
// Returns a class with a given class name and class loader.
|
||||
// Loads the class if needed. If not found NULL is returned.
|
||||
static Klass* resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS);
|
||||
// Version with null loader and protection domain
|
||||
static Klass* resolve_or_null(Symbol* class_name, TRAPS);
|
||||
static Klass* resolve_or_null(Symbol* class_name, TRAPS) {
|
||||
return resolve_or_null(class_name, Handle(), Handle(), THREAD);
|
||||
}
|
||||
|
||||
// Resolve a superclass or superinterface. Called from ClassFileParser,
|
||||
// parse_interfaces, resolve_instance_class_or_null, load_shared_class
|
||||
// "child_name" is the class whose super class or interface is being resolved.
|
||||
static InstanceKlass* resolve_super_or_fail(Symbol* child_name,
|
||||
Symbol* class_name,
|
||||
// "class_name" is the class whose super class or interface is being resolved.
|
||||
static InstanceKlass* resolve_super_or_fail(Symbol* class_name,
|
||||
Symbol* super_name,
|
||||
Handle class_loader,
|
||||
Handle protection_domain,
|
||||
bool is_superclass,
|
||||
@ -151,9 +149,9 @@ public:
|
||||
// Do not make any queries to class loaders; consult only the cache.
|
||||
// If not found NULL is returned.
|
||||
static Klass* find_instance_or_array_klass(Symbol* class_name,
|
||||
Handle class_loader,
|
||||
Handle protection_domain,
|
||||
TRAPS);
|
||||
Handle class_loader,
|
||||
Handle protection_domain,
|
||||
TRAPS);
|
||||
|
||||
// Lookup an instance or array class that has already been loaded
|
||||
// either into the given class loader, or else into another class
|
||||
@ -191,13 +189,9 @@ public:
|
||||
// loaders. Returns "true" iff something was unloaded.
|
||||
static bool do_unloading(GCTimer* gc_timer);
|
||||
|
||||
// System loader lock
|
||||
static oop system_loader_lock();
|
||||
|
||||
// Protection Domain Table
|
||||
static ProtectionDomainCacheTable* pd_cache_table() { return _pd_cache_table; }
|
||||
|
||||
public:
|
||||
// Printing
|
||||
static void print();
|
||||
static void print_on(outputStream* st);
|
||||
@ -226,19 +220,8 @@ public:
|
||||
|
||||
// Register a new class loader
|
||||
static ClassLoaderData* register_loader(Handle class_loader, bool create_mirror_cld = false);
|
||||
protected:
|
||||
// Mirrors for primitive classes (created eagerly)
|
||||
static oop check_mirror(oop m) {
|
||||
assert(m != NULL, "mirror not initialized");
|
||||
return m;
|
||||
}
|
||||
|
||||
public:
|
||||
// Note: java_lang_Class::primitive_type is the inverse of java_mirror
|
||||
|
||||
// Check class loader constraints
|
||||
static bool add_loader_constraint(Symbol* name, Klass* klass_being_linked, Handle loader1,
|
||||
Handle loader2, TRAPS);
|
||||
static Symbol* check_signature_loaders(Symbol* signature, Klass* klass_being_linked,
|
||||
Handle loader1, Handle loader2, bool is_method, TRAPS);
|
||||
|
||||
@ -312,21 +295,9 @@ public:
|
||||
|
||||
static ProtectionDomainCacheEntry* cache_get(Handle protection_domain);
|
||||
|
||||
protected:
|
||||
|
||||
enum Constants {
|
||||
_loader_constraint_size = 107, // number of entries in constraint table
|
||||
_resolution_error_size = 107, // number of entries in resolution error table
|
||||
_invoke_method_size = 139, // number of entries in invoke method table
|
||||
_placeholder_table_size = 1009 // number of entries in hash table for placeholders
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
// Static tables owned by the SystemDictionary
|
||||
|
||||
// Hashtable holding placeholders for classes being loaded.
|
||||
static PlaceholderTable* _placeholders;
|
||||
|
||||
// Constraints on class loaders
|
||||
static LoaderConstraintTable* _loader_constraints;
|
||||
|
||||
@ -340,35 +311,55 @@ public:
|
||||
static ProtectionDomainCacheTable* _pd_cache_table;
|
||||
|
||||
protected:
|
||||
static InstanceKlass* _well_known_klasses[];
|
||||
|
||||
private:
|
||||
// table of box klasses (int_klass, etc.)
|
||||
static InstanceKlass* _box_klasses[T_VOID+1];
|
||||
|
||||
static OopHandle _java_system_loader;
|
||||
static OopHandle _java_platform_loader;
|
||||
|
||||
static void validate_protection_domain(InstanceKlass* klass,
|
||||
Handle class_loader,
|
||||
Handle protection_domain, TRAPS);
|
||||
|
||||
friend class VM_PopulateDumpSharedSpace;
|
||||
friend class TraversePlaceholdersClosure;
|
||||
static PlaceholderTable* placeholders() { return _placeholders; }
|
||||
static LoaderConstraintTable* constraints() { return _loader_constraints; }
|
||||
static ResolutionErrorTable* resolution_errors() { return _resolution_errors; }
|
||||
static SymbolPropertyTable* invoke_method_table() { return _invoke_method_table; }
|
||||
static void post_class_load_event(EventClassLoad* event, const InstanceKlass* k, const ClassLoaderData* init_cld);
|
||||
|
||||
private:
|
||||
// Basic loading operations
|
||||
static InstanceKlass* resolve_instance_class_or_null_helper(Symbol* name,
|
||||
Handle class_loader,
|
||||
Handle protection_domain,
|
||||
TRAPS);
|
||||
static InstanceKlass* resolve_instance_class_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS);
|
||||
static Klass* resolve_array_class_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS);
|
||||
static InstanceKlass* handle_parallel_super_load(Symbol* class_name, Symbol* supername, Handle class_loader, Handle protection_domain, Handle lockObject, TRAPS);
|
||||
static InstanceKlass* resolve_instance_class_or_null(Symbol* class_name,
|
||||
Handle class_loader,
|
||||
Handle protection_domain, TRAPS);
|
||||
static Klass* resolve_array_class_or_null(Symbol* class_name,
|
||||
Handle class_loader,
|
||||
Handle protection_domain, TRAPS);
|
||||
static InstanceKlass* handle_parallel_super_load(Symbol* class_name,
|
||||
Symbol* supername,
|
||||
Handle class_loader,
|
||||
Handle protection_domain,
|
||||
Handle lockObject, TRAPS);
|
||||
// Wait on SystemDictionary_lock; unlocks lockObject before
|
||||
// waiting; relocks lockObject with correct recursion count
|
||||
// after waiting, but before reentering SystemDictionary_lock
|
||||
// to preserve lock order semantics.
|
||||
static void double_lock_wait(Thread* thread, Handle lockObject);
|
||||
static void define_instance_class(InstanceKlass* k, Handle class_loader, TRAPS);
|
||||
static InstanceKlass* find_or_define_instance_class(Symbol* class_name,
|
||||
Handle class_loader,
|
||||
InstanceKlass* k, TRAPS);
|
||||
static InstanceKlass* find_or_define_helper(Symbol* class_name,
|
||||
Handle class_loader,
|
||||
InstanceKlass* k, TRAPS);
|
||||
static InstanceKlass* load_instance_class(Symbol* class_name, Handle class_loader, TRAPS);
|
||||
static bool is_parallelDefine(Handle class_loader);
|
||||
static Handle compute_loader_lock_object(Thread* thread, Handle class_loader);
|
||||
static void check_loader_lock_contention(Thread* thread, Handle loader_lock);
|
||||
|
||||
static bool is_shared_class_visible(Symbol* class_name, InstanceKlass* ik,
|
||||
PackageEntry* pkg_entry,
|
||||
Handle class_loader, TRAPS);
|
||||
@ -376,11 +367,19 @@ protected:
|
||||
InstanceKlass* ik,
|
||||
PackageEntry* pkg_entry,
|
||||
Handle class_loader, TRAPS);
|
||||
static bool check_shared_class_super_type(InstanceKlass* child, InstanceKlass* super,
|
||||
static bool check_shared_class_super_type(InstanceKlass* klass, InstanceKlass* super,
|
||||
Handle class_loader, Handle protection_domain,
|
||||
bool is_superclass, TRAPS);
|
||||
static bool check_shared_class_super_types(InstanceKlass* ik, Handle class_loader,
|
||||
Handle protection_domain, TRAPS);
|
||||
// Second part of load_shared_class
|
||||
static void load_shared_class_misc(InstanceKlass* ik, ClassLoaderData* loader_data, TRAPS) NOT_CDS_RETURN;
|
||||
protected:
|
||||
// Used by SystemDictionaryShared
|
||||
|
||||
static bool add_loader_constraint(Symbol* name, Klass* klass_being_linked, Handle loader1,
|
||||
Handle loader2, TRAPS);
|
||||
static void post_class_load_event(EventClassLoad* event, const InstanceKlass* k, const ClassLoaderData* init_cld);
|
||||
static InstanceKlass* load_shared_lambda_proxy_class(InstanceKlass* ik,
|
||||
Handle class_loader,
|
||||
Handle protection_domain,
|
||||
@ -392,16 +391,13 @@ protected:
|
||||
const ClassFileStream *cfs,
|
||||
PackageEntry* pkg_entry,
|
||||
TRAPS);
|
||||
// Second part of load_shared_class
|
||||
static void load_shared_class_misc(InstanceKlass* ik, ClassLoaderData* loader_data, TRAPS) NOT_CDS_RETURN;
|
||||
static InstanceKlass* load_shared_boot_class(Symbol* class_name,
|
||||
PackageEntry* pkg_entry,
|
||||
TRAPS);
|
||||
static InstanceKlass* load_instance_class(Symbol* class_name, Handle class_loader, TRAPS);
|
||||
static Handle compute_loader_lock_object(Thread* thread, Handle class_loader);
|
||||
static bool is_parallelCapable(Handle class_loader);
|
||||
static bool is_parallelDefine(Handle class_loader);
|
||||
|
||||
static InstanceKlass* find_or_define_instance_class(Symbol* class_name,
|
||||
Handle class_loader,
|
||||
InstanceKlass* k, TRAPS);
|
||||
public:
|
||||
static bool is_system_class_loader(oop class_loader);
|
||||
static bool is_platform_class_loader(oop class_loader);
|
||||
@ -434,10 +430,6 @@ protected:
|
||||
static void update_dictionary(unsigned int hash,
|
||||
InstanceKlass* k, Handle loader);
|
||||
|
||||
private:
|
||||
static OopHandle _java_system_loader;
|
||||
static OopHandle _java_platform_loader;
|
||||
|
||||
public:
|
||||
static TableStatistics placeholders_statistics();
|
||||
static TableStatistics loader_constraints_statistics();
|
||||
|
@ -1046,7 +1046,7 @@ InstanceKlass* SystemDictionaryShared::find_or_load_shared_class(
|
||||
|
||||
k = load_shared_class_for_builtin_loader(name, class_loader, THREAD);
|
||||
if (k != NULL) {
|
||||
define_instance_class(k, class_loader, CHECK_NULL);
|
||||
k = find_or_define_instance_class(name, class_loader, k, CHECK_NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1219,14 +1219,14 @@ bool SystemDictionaryShared::add_unregistered_class(InstanceKlass* k, TRAPS) {
|
||||
}
|
||||
|
||||
// This function is called to resolve the super/interfaces of shared classes for
|
||||
// non-built-in loaders. E.g., ChildClass in the below example
|
||||
// non-built-in 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
|
||||
// ChildClass id: 4 super: 0 interfaces: 2 source: cust.jar
|
||||
// SharedClass id: 4 super: 0 interfaces: 2 source: cust.jar
|
||||
InstanceKlass* SystemDictionaryShared::dump_time_resolve_super_or_fail(
|
||||
Symbol* child_name, Symbol* class_name, Handle class_loader,
|
||||
Symbol* class_name, Symbol* super_name, Handle class_loader,
|
||||
Handle protection_domain, bool is_superclass, TRAPS) {
|
||||
|
||||
assert(DumpSharedSpaces, "only when dumping");
|
||||
@ -1236,13 +1236,13 @@ InstanceKlass* SystemDictionaryShared::dump_time_resolve_super_or_fail(
|
||||
// We're still loading the well-known classes, before the ClassListParser is created.
|
||||
return NULL;
|
||||
}
|
||||
if (child_name->equals(parser->current_class_name())) {
|
||||
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(class_name);
|
||||
return parser->lookup_super_for_current_class(super_name);
|
||||
} else {
|
||||
return parser->lookup_interface_for_current_class(class_name);
|
||||
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().
|
||||
|
@ -246,8 +246,8 @@ public:
|
||||
static bool is_sharing_possible(ClassLoaderData* loader_data);
|
||||
|
||||
static bool add_unregistered_class(InstanceKlass* k, TRAPS);
|
||||
static InstanceKlass* dump_time_resolve_super_or_fail(Symbol* child_name,
|
||||
Symbol* class_name,
|
||||
static InstanceKlass* dump_time_resolve_super_or_fail(Symbol* class_name,
|
||||
Symbol* super_name,
|
||||
Handle class_loader,
|
||||
Handle protection_domain,
|
||||
bool is_superclass,
|
||||
|
@ -726,8 +726,6 @@ void VM_PopulateDumpSharedSpace::doit() {
|
||||
// shared classes at runtime, where constraints were previously created.
|
||||
guarantee(SystemDictionary::constraints()->number_of_entries() == 0,
|
||||
"loader constraints are not saved");
|
||||
guarantee(SystemDictionary::placeholders()->number_of_entries() == 0,
|
||||
"placeholders are not saved");
|
||||
|
||||
// At this point, many classes have been loaded.
|
||||
// Gather systemDictionary classes in a global array and do everything to
|
||||
|
Loading…
Reference in New Issue
Block a user