| // natClassLoader.cc - Implementation of java.lang.ClassLoader native methods. |
| |
| /* Copyright (C) 1999, 2000, 2001 Free Software Foundation |
| |
| This file is part of libgcj. |
| |
| This software is copyrighted work licensed under the terms of the |
| Libgcj License. Please consult the file "LIBGCJ_LICENSE" for |
| details. */ |
| |
| /* Author: Kresten Krab Thorup <krab@gnu.org> */ |
| |
| #include <config.h> |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <gcj/cni.h> |
| #include <jvm.h> |
| |
| #include <java-threads.h> |
| #include <java-interp.h> |
| |
| #include <java/lang/Character.h> |
| #include <java/lang/Thread.h> |
| #include <java/lang/ClassLoader.h> |
| #include <gnu/gcj/runtime/VMClassLoader.h> |
| #include <java/lang/InternalError.h> |
| #include <java/lang/IllegalAccessError.h> |
| #include <java/lang/LinkageError.h> |
| #include <java/lang/ClassFormatError.h> |
| #include <java/lang/NoClassDefFoundError.h> |
| #include <java/lang/ClassNotFoundException.h> |
| #include <java/lang/ClassCircularityError.h> |
| #include <java/lang/IncompatibleClassChangeError.h> |
| #include <java/lang/VirtualMachineError.h> |
| #include <java/lang/VMClassLoader.h> |
| #include <java/lang/reflect/Modifier.h> |
| #include <java/lang/Runtime.h> |
| #include <java/lang/StringBuffer.h> |
| #include <java/io/Serializable.h> |
| #include <java/lang/Cloneable.h> |
| |
| // FIXME: remove these. |
| #define CloneableClass java::lang::Cloneable::class$ |
| #define ObjectClass java::lang::Object::class$ |
| #define ClassClass java::lang::Class::class$ |
| #define VMClassLoaderClass gnu::gcj::runtime::VMClassLoader::class$ |
| #define ClassLoaderClass java::lang::ClassLoader::class$ |
| #define SerializableClass java::io::Serializable::class$ |
| |
| /////////// java.lang.ClassLoader native methods //////////// |
| |
| java::lang::Class * |
| java::lang::ClassLoader::defineClass0 (jstring name, |
| jbyteArray data, |
| jint offset, |
| jint length, |
| java::security::ProtectionDomain *pd) |
| { |
| #ifdef INTERPRETER |
| jclass klass; |
| klass = (jclass) JvAllocObject (&ClassClass, sizeof (_Jv_InterpClass)); |
| _Jv_InitNewClassFields (klass); |
| |
| // synchronize on the class, so that it is not |
| // attempted initialized until we're done loading. |
| _Jv_MonitorEnter (klass); |
| |
| // record which is the defining loader |
| klass->loader = this; |
| |
| // register that we are the initiating loader... |
| if (name != 0) |
| { |
| _Jv_Utf8Const * name2 = _Jv_makeUtf8Const (name); |
| |
| if (! _Jv_VerifyClassName (name2)) |
| throw new java::lang::ClassFormatError |
| (JvNewStringLatin1 ("erroneous class name")); |
| |
| klass->name = name2; |
| } |
| |
| try |
| { |
| _Jv_DefineClass (klass, data, offset, length); |
| } |
| catch (java::lang::Throwable *ex) |
| { |
| klass->state = JV_STATE_ERROR; |
| klass->notifyAll (); |
| |
| _Jv_UnregisterClass (klass); |
| |
| _Jv_MonitorExit (klass); |
| |
| // FIXME: Here we may want to test that EX does |
| // indeed represent a valid exception. That is, |
| // anything but ClassNotFoundException, |
| // or some kind of Error. |
| |
| // FIXME: Rewrite this as a cleanup instead of |
| // as a catch handler. |
| |
| throw ex; |
| } |
| |
| klass->protectionDomain = pd; |
| |
| // if everything proceeded sucessfully, we're loaded. |
| JvAssert (klass->state == JV_STATE_LOADED); |
| |
| // if an exception is generated, this is initially missed. |
| // however, we come back here in handleException0 below... |
| _Jv_MonitorExit (klass); |
| |
| return klass; |
| |
| #else // INTERPRETER |
| |
| return 0; |
| #endif |
| } |
| |
| void |
| _Jv_WaitForState (jclass klass, int state) |
| { |
| if (klass->state >= state) |
| return; |
| |
| _Jv_MonitorEnter (klass) ; |
| |
| if (state == JV_STATE_LINKED) |
| { |
| // Must call _Jv_PrepareCompiledClass while holding the class |
| // mutex. |
| _Jv_PrepareCompiledClass (klass); |
| _Jv_MonitorExit (klass); |
| return; |
| } |
| |
| java::lang::Thread *self = java::lang::Thread::currentThread(); |
| |
| // this is similar to the strategy for class initialization. |
| // if we already hold the lock, just leave. |
| while (klass->state <= state |
| && klass->thread |
| && klass->thread != self) |
| klass->wait (); |
| |
| _Jv_MonitorExit (klass); |
| |
| if (klass->state == JV_STATE_ERROR) |
| throw new java::lang::LinkageError; |
| } |
| |
| // Finish linking a class. Only called from ClassLoader::resolveClass. |
| void |
| java::lang::ClassLoader::linkClass0 (java::lang::Class *klass) |
| { |
| if (klass->state >= JV_STATE_LINKED) |
| return; |
| |
| #ifdef INTERPRETER |
| if (_Jv_IsInterpretedClass (klass)) |
| _Jv_PrepareClass (klass); |
| #endif |
| |
| _Jv_PrepareCompiledClass (klass); |
| } |
| |
| void |
| java::lang::ClassLoader::markClassErrorState0 (java::lang::Class *klass) |
| { |
| klass->state = JV_STATE_ERROR; |
| klass->notifyAll (); |
| } |
| |
| jclass |
| java::lang::VMClassLoader::getPrimitiveClass (jchar type) |
| { |
| char sig[2]; |
| sig[0] = (char) type; |
| sig[1] = '\0'; |
| return _Jv_FindClassFromSignature (sig, NULL); |
| } |
| |
| // This is the findClass() implementation for the System classloader. It is |
| // the only native method in VMClassLoader, so we define it here. |
| jclass |
| gnu::gcj::runtime::VMClassLoader::findClass (jstring name) |
| { |
| _Jv_Utf8Const *name_u = _Jv_makeUtf8Const (name); |
| jclass klass = _Jv_FindClassInCache (name_u, 0); |
| |
| if (! klass) |
| { |
| // Turn `gnu.pkg.quux' into `lib-gnu-pkg-quux'. Then search for |
| // a module named (eg, on Linux) `lib-gnu-pkg-quux.so', followed |
| // by `lib-gnu-pkg.so' and `lib-gnu.so'. If loading one of |
| // these causes the class to appear in the cache, then use it. |
| java::lang::StringBuffer *sb = new java::lang::StringBuffer (JvNewStringLatin1("lib-")); |
| jstring so_base_name = (sb->append (name)->toString ())->replace ('.', '-'); |
| |
| while (! klass && so_base_name && so_base_name->length() > 0) |
| { |
| using namespace ::java::lang; |
| Runtime *rt = Runtime::getRuntime(); |
| jboolean loaded = rt->loadLibraryInternal (so_base_name); |
| |
| jint nd = so_base_name->lastIndexOf ('-'); |
| if (nd == -1) |
| so_base_name = NULL; |
| else |
| so_base_name = so_base_name->substring (0, nd); |
| |
| if (loaded) |
| klass = _Jv_FindClassInCache (name_u, 0); |
| } |
| } |
| |
| // Now try loading using the interpreter. |
| if (! klass) |
| { |
| klass = java::net::URLClassLoader::findClass (name); |
| } |
| |
| return klass; |
| } |
| |
| jclass |
| java::lang::ClassLoader::findLoadedClass (jstring name) |
| { |
| return _Jv_FindClassInCache (_Jv_makeUtf8Const (name), this); |
| } |
| |
| /** This function does class-preparation for compiled classes. |
| NOTE: It contains replicated functionality from |
| _Jv_ResolvePoolEntry, and this is intentional, since that function |
| lives in resolve.cc which is entirely conditionally compiled. |
| */ |
| void |
| _Jv_PrepareCompiledClass (jclass klass) |
| { |
| if (klass->state >= JV_STATE_LINKED) |
| return; |
| |
| // Short-circuit, so that mutually dependent classes are ok. |
| klass->state = JV_STATE_LINKED; |
| |
| _Jv_Constants *pool = &klass->constants; |
| for (int index = 1; index < pool->size; ++index) |
| { |
| if (pool->tags[index] == JV_CONSTANT_Class) |
| { |
| _Jv_Utf8Const *name = pool->data[index].utf8; |
| |
| jclass found; |
| if (name->data[0] == '[') |
| found = _Jv_FindClassFromSignature (&name->data[0], |
| klass->loader); |
| else |
| found = _Jv_FindClass (name, klass->loader); |
| |
| if (! found) |
| { |
| jstring str = _Jv_NewStringUTF (name->data); |
| throw new java::lang::ClassNotFoundException (str); |
| } |
| |
| pool->data[index].clazz = found; |
| pool->tags[index] |= JV_CONSTANT_ResolvedFlag; |
| } |
| else if (pool->tags[index] == JV_CONSTANT_String) |
| { |
| jstring str; |
| str = _Jv_NewStringUtf8Const (pool->data[index].utf8); |
| pool->data[index].o = str; |
| pool->tags[index] |= JV_CONSTANT_ResolvedFlag; |
| } |
| } |
| |
| #ifdef INTERPRETER |
| // FIXME: although the comment up top says that this function is |
| // only called for compiled classes, it is actually called for every |
| // class. |
| if (! _Jv_IsInterpretedClass (klass)) |
| { |
| #endif /* INTERPRETER */ |
| jfieldID f = JvGetFirstStaticField (klass); |
| for (int n = JvNumStaticFields (klass); n > 0; --n) |
| { |
| int mod = f->getModifiers (); |
| // If we have a static String field with a non-null initial |
| // value, we know it points to a Utf8Const. |
| if (f->getClass () == &StringClass |
| && java::lang::reflect::Modifier::isStatic (mod)) |
| { |
| jstring *strp = (jstring *) f->u.addr; |
| if (*strp) |
| *strp = _Jv_NewStringUtf8Const ((_Jv_Utf8Const *) *strp); |
| } |
| f = f->getNextField (); |
| } |
| #ifdef INTERPRETER |
| } |
| #endif /* INTERPRETER */ |
| |
| if (klass->vtable == NULL) |
| _Jv_MakeVTable(klass); |
| |
| if (klass->otable != NULL && klass->otable->state == 0) |
| _Jv_LinkOffsetTable(klass); |
| |
| klass->notifyAll (); |
| } |
| |
| |
| // |
| // A single class can have many "initiating" class loaders, |
| // and a single "defining" class loader. The Defining |
| // class loader is what is returned from Class.getClassLoader() |
| // and is used when loading dependent classes during resolution. |
| // The set of initiating class loaders are used to ensure |
| // safety of linking, and is maintained in the hash table |
| // "initiated_classes". A defining classloader is by definition also |
| // initiating, so we only store classes in this table, if they have more |
| // than one class loader associated. |
| // |
| |
| |
| // Size of local hash table. |
| #define HASH_LEN 1013 |
| |
| // Hash function for Utf8Consts. |
| #define HASH_UTF(Utf) (((Utf)->hash) % HASH_LEN) |
| |
| struct _Jv_LoaderInfo { |
| _Jv_LoaderInfo *next; |
| java::lang::Class *klass; |
| java::lang::ClassLoader *loader; |
| }; |
| |
| static _Jv_LoaderInfo *initiated_classes[HASH_LEN]; |
| static jclass loaded_classes[HASH_LEN]; |
| |
| // This is the root of a linked list of classes |
| |
| |
| |
| jclass |
| _Jv_FindClassInCache (_Jv_Utf8Const *name, java::lang::ClassLoader *loader) |
| { |
| _Jv_MonitorEnter (&ClassClass); |
| jint hash = HASH_UTF (name); |
| |
| // first, if LOADER is a defining loader, then it is also initiating |
| jclass klass; |
| for (klass = loaded_classes[hash]; klass; klass = klass->next) |
| { |
| if (loader == klass->loader && _Jv_equalUtf8Consts (name, klass->name)) |
| break; |
| } |
| |
| // otherwise, it may be that the class in question was defined |
| // by some other loader, but that the loading was initiated by |
| // the loader in question. |
| if (!klass) |
| { |
| _Jv_LoaderInfo *info; |
| for (info = initiated_classes[hash]; info; info = info->next) |
| { |
| if (loader == info->loader |
| && _Jv_equalUtf8Consts (name, info->klass->name)) |
| { |
| klass = info->klass; |
| break; |
| } |
| } |
| } |
| |
| _Jv_MonitorExit (&ClassClass); |
| |
| return klass; |
| } |
| |
| void |
| _Jv_UnregisterClass (jclass the_class) |
| { |
| _Jv_MonitorEnter (&ClassClass); |
| jint hash = HASH_UTF(the_class->name); |
| |
| jclass *klass = &(loaded_classes[hash]); |
| for ( ; *klass; klass = &((*klass)->next)) |
| { |
| if (*klass == the_class) |
| { |
| *klass = (*klass)->next; |
| break; |
| } |
| } |
| |
| _Jv_LoaderInfo **info = &(initiated_classes[hash]); |
| for ( ; ; info = &((*info)->next)) |
| { |
| while (*info && (*info)->klass == the_class) |
| { |
| *info = (*info)->next; |
| } |
| |
| if (*info == NULL) |
| break; |
| } |
| |
| _Jv_MonitorExit (&ClassClass); |
| } |
| |
| void |
| _Jv_RegisterInitiatingLoader (jclass klass, java::lang::ClassLoader *loader) |
| { |
| // non-gc alloc! |
| _Jv_LoaderInfo *info = (_Jv_LoaderInfo *) _Jv_Malloc (sizeof(_Jv_LoaderInfo)); |
| jint hash = HASH_UTF(klass->name); |
| |
| _Jv_MonitorEnter (&ClassClass); |
| info->loader = loader; |
| info->klass = klass; |
| info->next = initiated_classes[hash]; |
| initiated_classes[hash] = info; |
| _Jv_MonitorExit (&ClassClass); |
| } |
| |
| // This function is called many times during startup, before main() is |
| // run. At that point in time we know for certain we are running |
| // single-threaded, so we don't need to lock when adding classes to the |
| // class chain. At all other times, the caller should synchronize on |
| // Class::class$. |
| void |
| _Jv_RegisterClasses (jclass *classes) |
| { |
| for (; *classes; ++classes) |
| { |
| jclass klass = *classes; |
| |
| (*_Jv_RegisterClassHook) (klass); |
| |
| // registering a compiled class causes |
| // it to be immediately "prepared". |
| if (klass->state == JV_STATE_NOTHING) |
| klass->state = JV_STATE_COMPILED; |
| } |
| } |
| |
| void |
| _Jv_RegisterClassHookDefault (jclass klass) |
| { |
| jint hash = HASH_UTF (klass->name); |
| |
| jclass check_class = loaded_classes[hash]; |
| |
| // If the class is already registered, don't re-register it. |
| while (check_class != NULL) |
| { |
| if (check_class == klass) |
| { |
| // If you get this, it means you have the same class in two |
| // different libraries. |
| #define TEXT "Duplicate class registration: " |
| // We size-limit MESSAGE so that you can't trash the stack. |
| char message[200]; |
| strcpy (message, TEXT); |
| strncpy (message + sizeof (TEXT) - 1, klass->name->data, |
| sizeof (message) - sizeof (TEXT)); |
| message[sizeof (message) - 1] = '\0'; |
| if (! gcj::runtimeInitialized) |
| JvFail (message); |
| else |
| { |
| java::lang::String *str = JvNewStringLatin1 (message); |
| throw new java::lang::VirtualMachineError (str); |
| } |
| } |
| |
| check_class = check_class->next; |
| } |
| |
| klass->next = loaded_classes[hash]; |
| loaded_classes[hash] = klass; |
| } |
| |
| // A pointer to a function that actually registers a class. |
| // Normally _Jv_RegisterClassHookDefault, but could be some other function |
| // that registers the class in e.g. a ClassLoader-local table. |
| // Should synchronize on Class:class$ while setting/restore this variable. |
| |
| void (*_Jv_RegisterClassHook) (jclass cl) = _Jv_RegisterClassHookDefault; |
| |
| void |
| _Jv_RegisterClass (jclass klass) |
| { |
| jclass classes[2]; |
| classes[0] = klass; |
| classes[1] = NULL; |
| _Jv_RegisterClasses (classes); |
| } |
| |
| jclass |
| _Jv_FindClass (_Jv_Utf8Const *name, java::lang::ClassLoader *loader) |
| { |
| jclass klass = _Jv_FindClassInCache (name, loader); |
| |
| if (! klass) |
| { |
| jstring sname = _Jv_NewStringUTF (name->data); |
| |
| if (loader) |
| { |
| // Load using a user-defined loader, jvmspec 5.3.2 |
| klass = loader->loadClass(sname, false); |
| |
| // If "loader" delegated the loadClass operation to another |
| // loader, explicitly register that it is also an initiating |
| // loader of the given class. |
| if (klass && (klass->getClassLoader () != loader)) |
| _Jv_RegisterInitiatingLoader (klass, loader); |
| } |
| else |
| { |
| java::lang::ClassLoader *sys |
| = java::lang::ClassLoader::getSystemClassLoader (); |
| |
| // Load using the bootstrap loader jvmspec 5.3.1. |
| klass = sys->loadClass (sname, false); |
| |
| // Register that we're an initiating loader. |
| if (klass) |
| _Jv_RegisterInitiatingLoader (klass, 0); |
| } |
| } |
| else |
| { |
| // we need classes to be in the hash while |
| // we're loading, so that they can refer to themselves. |
| _Jv_WaitForState (klass, JV_STATE_LOADED); |
| } |
| |
| return klass; |
| } |
| |
| void |
| _Jv_InitNewClassFields (jclass ret) |
| { |
| ret->next = NULL; |
| ret->name = NULL; |
| ret->accflags = 0; |
| ret->superclass = NULL; |
| ret->constants.size = 0; |
| ret->constants.tags = NULL; |
| ret->constants.data = NULL; |
| ret->methods = NULL; |
| ret->method_count = 0; |
| ret->vtable_method_count = 0; |
| ret->fields = NULL; |
| ret->size_in_bytes = 0; |
| ret->field_count = 0; |
| ret->static_field_count = 0; |
| ret->vtable = NULL; |
| ret->interfaces = NULL; |
| ret->loader = NULL; |
| ret->interface_count = 0; |
| ret->state = JV_STATE_NOTHING; |
| ret->thread = NULL; |
| ret->depth = 0; |
| ret->ancestors = NULL; |
| ret->idt = NULL; |
| ret->arrayclass = NULL; |
| } |
| |
| jclass |
| _Jv_NewClass (_Jv_Utf8Const *name, jclass superclass, |
| java::lang::ClassLoader *loader) |
| { |
| jclass ret = (jclass) JvAllocObject (&ClassClass); |
| _Jv_InitNewClassFields (ret); |
| ret->name = name; |
| ret->superclass = superclass; |
| ret->loader = loader; |
| |
| _Jv_RegisterClass (ret); |
| |
| return ret; |
| } |
| |
| static _Jv_IDispatchTable *array_idt = NULL; |
| static jshort array_depth = 0; |
| static jclass *array_ancestors = NULL; |
| |
| // Create a class representing an array of ELEMENT and store a pointer to it |
| // in element->arrayclass. LOADER is the ClassLoader which _initiated_ the |
| // instantiation of this array. ARRAY_VTABLE is the vtable to use for the new |
| // array class. This parameter is optional. |
| void |
| _Jv_NewArrayClass (jclass element, java::lang::ClassLoader *loader, |
| _Jv_VTable *array_vtable) |
| { |
| JvSynchronize sync (element); |
| |
| _Jv_Utf8Const *array_name; |
| int len; |
| |
| if (element->arrayclass) |
| return; |
| |
| if (element->isPrimitive()) |
| { |
| if (element == JvPrimClass (void)) |
| throw new java::lang::ClassNotFoundException (); |
| len = 3; |
| } |
| else |
| len = element->name->length + 5; |
| |
| { |
| char signature[len]; |
| int index = 0; |
| signature[index++] = '['; |
| // Compute name of array class. |
| if (element->isPrimitive()) |
| { |
| signature[index++] = (char) element->method_count; |
| } |
| else |
| { |
| size_t length = element->name->length; |
| const char *const name = element->name->data; |
| if (name[0] != '[') |
| signature[index++] = 'L'; |
| memcpy (&signature[index], name, length); |
| index += length; |
| if (name[0] != '[') |
| signature[index++] = ';'; |
| } |
| array_name = _Jv_makeUtf8Const (signature, index); |
| } |
| |
| // Create new array class. |
| jclass array_class = _Jv_NewClass (array_name, &ObjectClass, |
| element->loader); |
| |
| // Note that `vtable_method_count' doesn't include the initial |
| // gc_descr slot. |
| JvAssert (ObjectClass.vtable_method_count == NUM_OBJECT_METHODS); |
| int dm_count = ObjectClass.vtable_method_count; |
| |
| // Create a new vtable by copying Object's vtable. |
| _Jv_VTable *vtable; |
| if (array_vtable) |
| vtable = array_vtable; |
| else |
| vtable = _Jv_VTable::new_vtable (dm_count); |
| vtable->clas = array_class; |
| vtable->gc_descr = ObjectClass.vtable->gc_descr; |
| for (int i = 0; i < dm_count; ++i) |
| vtable->set_method (i, ObjectClass.vtable->get_method (i)); |
| |
| array_class->vtable = vtable; |
| array_class->vtable_method_count = ObjectClass.vtable_method_count; |
| |
| // Stash the pointer to the element type. |
| array_class->methods = (_Jv_Method *) element; |
| |
| // Register our interfaces. |
| static jclass interfaces[] = { &CloneableClass, &SerializableClass }; |
| array_class->interfaces = interfaces; |
| array_class->interface_count = sizeof interfaces / sizeof interfaces[0]; |
| |
| // Since all array classes have the same interface dispatch table, we can |
| // cache one and reuse it. It is not necessary to synchronize this. |
| if (!array_idt) |
| { |
| _Jv_PrepareConstantTimeTables (array_class); |
| array_idt = array_class->idt; |
| array_depth = array_class->depth; |
| array_ancestors = array_class->ancestors; |
| } |
| else |
| { |
| array_class->idt = array_idt; |
| array_class->depth = array_depth; |
| array_class->ancestors = array_ancestors; |
| } |
| |
| using namespace java::lang::reflect; |
| { |
| // Array classes are "abstract final"... |
| _Jv_ushort accflags = Modifier::FINAL | Modifier::ABSTRACT; |
| // ... and inherit accessibility from element type, per vmspec 5.3.3.2 |
| accflags |= (element->accflags & Modifier::PUBLIC); |
| accflags |= (element->accflags & Modifier::PROTECTED); |
| accflags |= (element->accflags & Modifier::PRIVATE); |
| array_class->accflags = accflags; |
| } |
| |
| // An array class has no visible instance fields. "length" is invisible to |
| // reflection. |
| |
| // say this class is initialized and ready to go! |
| array_class->state = JV_STATE_DONE; |
| |
| // vmspec, section 5.3.3 describes this |
| if (element->loader != loader) |
| _Jv_RegisterInitiatingLoader (array_class, loader); |
| |
| element->arrayclass = array_class; |
| } |