// jni.cc - JNI implementation, including the jump table. /* Copyright (C) 1998, 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. */ #include #include #include // Define this before including jni.h. #define __GCJ_JNI_IMPL__ #include #include #include #include #ifdef ENABLE_JVMPI #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace gcj; // This enum is used to select different template instantiations in // the invocation code. enum invocation_type { normal, nonvirtual, static_type, constructor }; // Forward declarations. extern struct JNINativeInterface _Jv_JNIFunctions; extern struct JNIInvokeInterface _Jv_JNI_InvokeFunctions; // Number of slots in the default frame. The VM must allow at least // 16. #define FRAME_SIZE 32 // Mark value indicating this is an overflow frame. #define MARK_NONE 0 // Mark value indicating this is a user frame. #define MARK_USER 1 // Mark value indicating this is a system frame. #define MARK_SYSTEM 2 // This structure is used to keep track of local references. struct _Jv_JNI_LocalFrame { // This is true if this frame object represents a pushed frame (eg // from PushLocalFrame). int marker : 2; // Number of elements in frame. int size : 30; // Next frame in chain. _Jv_JNI_LocalFrame *next; // The elements. These are allocated using the C "struct hack". jobject vec[0]; }; // This holds a reference count for all local references. static java::util::IdentityHashMap *local_ref_table; // This holds a reference count for all global references. static java::util::IdentityHashMap *global_ref_table; // The only VM. static JavaVM *the_vm; #ifdef ENABLE_JVMPI // The only JVMPI interface description. static JVMPI_Interface _Jv_JVMPI_Interface; static jint jvmpiEnableEvent (jint event_type, void *) { switch (event_type) { case JVMPI_EVENT_OBJECT_ALLOC: _Jv_JVMPI_Notify_OBJECT_ALLOC = _Jv_JVMPI_Interface.NotifyEvent; break; case JVMPI_EVENT_THREAD_START: _Jv_JVMPI_Notify_THREAD_START = _Jv_JVMPI_Interface.NotifyEvent; break; case JVMPI_EVENT_THREAD_END: _Jv_JVMPI_Notify_THREAD_END = _Jv_JVMPI_Interface.NotifyEvent; break; default: return JVMPI_NOT_AVAILABLE; } return JVMPI_SUCCESS; } static jint jvmpiDisableEvent (jint event_type, void *) { switch (event_type) { case JVMPI_EVENT_OBJECT_ALLOC: _Jv_JVMPI_Notify_OBJECT_ALLOC = NULL; break; default: return JVMPI_NOT_AVAILABLE; } return JVMPI_SUCCESS; } #endif void _Jv_JNI_Init (void) { local_ref_table = new java::util::IdentityHashMap; global_ref_table = new java::util::IdentityHashMap; #ifdef ENABLE_JVMPI _Jv_JVMPI_Interface.version = 1; _Jv_JVMPI_Interface.EnableEvent = &jvmpiEnableEvent; _Jv_JVMPI_Interface.DisableEvent = &jvmpiDisableEvent; _Jv_JVMPI_Interface.EnableGC = &_Jv_EnableGC; _Jv_JVMPI_Interface.DisableGC = &_Jv_DisableGC; _Jv_JVMPI_Interface.RunGC = &_Jv_RunGC; #endif } // Tell the GC that a certain pointer is live. static void mark_for_gc (jobject obj, java::util::IdentityHashMap *ref_table) { JvSynchronize sync (ref_table); using namespace java::lang; Integer *refcount = (Integer *) ref_table->get (obj); jint val = (refcount == NULL) ? 0 : refcount->intValue (); // FIXME: what about out of memory error? ref_table->put (obj, new Integer (val + 1)); } // Unmark a pointer. static void unmark_for_gc (jobject obj, java::util::IdentityHashMap *ref_table) { JvSynchronize sync (ref_table); using namespace java::lang; Integer *refcount = (Integer *) ref_table->get (obj); JvAssert (refcount); jint val = refcount->intValue () - 1; JvAssert (val >= 0); if (val == 0) ref_table->remove (obj); else // FIXME: what about out of memory error? ref_table->put (obj, new Integer (val)); } // "Unwrap" some random non-reference type. This exists to simplify // other template functions. template static T unwrap (T val) { return val; } // Unwrap a weak reference, if required. template static T * unwrap (T *obj) { using namespace gnu::gcj::runtime; // We can compare the class directly because JNIWeakRef is `final'. // Doing it this way is much faster. if (obj == NULL || obj->getClass () != &JNIWeakRef::class$) return obj; JNIWeakRef *wr = reinterpret_cast (obj); return reinterpret_cast (wr->get ()); } static jobject _Jv_JNI_NewGlobalRef (JNIEnv *, jobject obj) { // This seems weird but I think it is correct. obj = unwrap (obj); mark_for_gc (obj, global_ref_table); return obj; } static void _Jv_JNI_DeleteGlobalRef (JNIEnv *, jobject obj) { // This seems weird but I think it is correct. obj = unwrap (obj); unmark_for_gc (obj, global_ref_table); } static void _Jv_JNI_DeleteLocalRef (JNIEnv *env, jobject obj) { _Jv_JNI_LocalFrame *frame; // This seems weird but I think it is correct. obj = unwrap (obj); for (frame = env->locals; frame != NULL; frame = frame->next) { for (int i = 0; i < frame->size; ++i) { if (frame->vec[i] == obj) { frame->vec[i] = NULL; unmark_for_gc (obj, local_ref_table); return; } } // Don't go past a marked frame. JvAssert (frame->marker == MARK_NONE); } JvAssert (0); } static jint _Jv_JNI_EnsureLocalCapacity (JNIEnv *env, jint size) { // It is easier to just always allocate a new frame of the requested // size. This isn't the most efficient thing, but for now we don't // care. Note that _Jv_JNI_PushLocalFrame relies on this right now. _Jv_JNI_LocalFrame *frame; try { frame = (_Jv_JNI_LocalFrame *) _Jv_Malloc (sizeof (_Jv_JNI_LocalFrame) + size * sizeof (jobject)); } catch (jthrowable t) { env->ex = t; return JNI_ERR; } frame->marker = MARK_NONE; frame->size = size; memset (&frame->vec[0], 0, size * sizeof (jobject)); frame->next = env->locals; env->locals = frame; return 0; } static jint _Jv_JNI_PushLocalFrame (JNIEnv *env, jint size) { jint r = _Jv_JNI_EnsureLocalCapacity (env, size); if (r < 0) return r; // The new frame is on top. env->locals->marker = MARK_USER; return 0; } static jobject _Jv_JNI_NewLocalRef (JNIEnv *env, jobject obj) { // This seems weird but I think it is correct. obj = unwrap (obj); // Try to find an open slot somewhere in the topmost frame. _Jv_JNI_LocalFrame *frame = env->locals; bool done = false, set = false; for (; frame != NULL && ! done; frame = frame->next) { for (int i = 0; i < frame->size; ++i) { if (frame->vec[i] == NULL) { set = true; done = true; frame->vec[i] = obj; break; } } // If we found a slot, or if the frame we just searched is the // mark frame, then we are done. if (done || frame->marker != MARK_NONE) break; } if (! set) { // No slots, so we allocate a new frame. According to the spec // we could just die here. FIXME: return value. _Jv_JNI_EnsureLocalCapacity (env, 16); // We know the first element of the new frame will be ok. env->locals->vec[0] = obj; } mark_for_gc (obj, local_ref_table); return obj; } static jobject _Jv_JNI_PopLocalFrame (JNIEnv *env, jobject result, int stop) { _Jv_JNI_LocalFrame *rf = env->locals; bool done = false; while (rf != NULL && ! done) { for (int i = 0; i < rf->size; ++i) if (rf->vec[i] != NULL) unmark_for_gc (rf->vec[i], local_ref_table); // If the frame we just freed is the marker frame, we are done. done = (rf->marker == stop); _Jv_JNI_LocalFrame *n = rf->next; // When N==NULL, we've reached the stack-allocated frame, and we // must not free it. However, we must be sure to clear all its // elements, since we might conceivably reuse it. if (n == NULL) { memset (&rf->vec[0], 0, rf->size * sizeof (jobject)); break; } _Jv_Free (rf); rf = n; } // Update the local frame information. env->locals = rf; return result == NULL ? NULL : _Jv_JNI_NewLocalRef (env, result); } static jobject _Jv_JNI_PopLocalFrame (JNIEnv *env, jobject result) { return _Jv_JNI_PopLocalFrame (env, result, MARK_USER); } // Pop a `system' frame from the stack. This is `extern "C"' as it is // used by the compiler. extern "C" void _Jv_JNI_PopSystemFrame (JNIEnv *env) { _Jv_JNI_PopLocalFrame (env, NULL, MARK_SYSTEM); if (env->ex) { jthrowable t = env->ex; env->ex = NULL; throw t; } } // This function is used from other template functions. It wraps the // return value appropriately; we specialize it so that object returns // are turned into local references. template static T wrap_value (JNIEnv *, T value) { return value; } // This specialization is used for jobject, jclass, jstring, jarray, // etc. template static T * wrap_value (JNIEnv *env, T *value) { return (value == NULL ? value : (T *) _Jv_JNI_NewLocalRef (env, (jobject) value)); } static jint _Jv_JNI_GetVersion (JNIEnv *) { return JNI_VERSION_1_2; } static jclass _Jv_JNI_DefineClass (JNIEnv *env, jobject loader, const jbyte *buf, jsize bufLen) { try { loader = unwrap (loader); jbyteArray bytes = JvNewByteArray (bufLen); jbyte *elts = elements (bytes); memcpy (elts, buf, bufLen * sizeof (jbyte)); java::lang::ClassLoader *l = reinterpret_cast (loader); jclass result = l->defineClass (bytes, 0, bufLen); return (jclass) wrap_value (env, result); } catch (jthrowable t) { env->ex = t; return NULL; } } static jclass _Jv_JNI_FindClass (JNIEnv *env, const char *name) { // FIXME: assume that NAME isn't too long. int len = strlen (name); char s[len + 1]; for (int i = 0; i <= len; ++i) s[i] = (name[i] == '/') ? '.' : name[i]; jclass r = NULL; try { // This might throw an out of memory exception. jstring n = JvNewStringUTF (s); java::lang::ClassLoader *loader = NULL; if (env->klass != NULL) loader = env->klass->getClassLoader (); if (loader == NULL) { // FIXME: should use getBaseClassLoader, but we don't have that // yet. loader = java::lang::ClassLoader::getSystemClassLoader (); } r = loader->loadClass (n); } catch (jthrowable t) { env->ex = t; } return (jclass) wrap_value (env, r); } static jclass _Jv_JNI_GetSuperclass (JNIEnv *env, jclass clazz) { return (jclass) wrap_value (env, unwrap (clazz)->getSuperclass ()); } static jboolean _Jv_JNI_IsAssignableFrom(JNIEnv *, jclass clazz1, jclass clazz2) { return unwrap (clazz1)->isAssignableFrom (unwrap (clazz2)); } static jint _Jv_JNI_Throw (JNIEnv *env, jthrowable obj) { // We check in case the user did some funky cast. obj = unwrap (obj); JvAssert (obj != NULL && java::lang::Throwable::class$.isInstance (obj)); env->ex = obj; return 0; } static jint _Jv_JNI_ThrowNew (JNIEnv *env, jclass clazz, const char *message) { using namespace java::lang::reflect; clazz = unwrap (clazz); JvAssert (java::lang::Throwable::class$.isAssignableFrom (clazz)); int r = JNI_OK; try { JArray *argtypes = (JArray *) JvNewObjectArray (1, &java::lang::Class::class$, NULL); jclass *elts = elements (argtypes); elts[0] = &StringClass; Constructor *cons = clazz->getConstructor (argtypes); jobjectArray values = JvNewObjectArray (1, &StringClass, NULL); jobject *velts = elements (values); velts[0] = JvNewStringUTF (message); jobject obj = cons->newInstance (values); env->ex = reinterpret_cast (obj); } catch (jthrowable t) { env->ex = t; r = JNI_ERR; } return r; } static jthrowable _Jv_JNI_ExceptionOccurred (JNIEnv *env) { return (jthrowable) wrap_value (env, env->ex); } static void _Jv_JNI_ExceptionDescribe (JNIEnv *env) { if (env->ex != NULL) env->ex->printStackTrace(); } static void _Jv_JNI_ExceptionClear (JNIEnv *env) { env->ex = NULL; } static jboolean _Jv_JNI_ExceptionCheck (JNIEnv *env) { return env->ex != NULL; } static void _Jv_JNI_FatalError (JNIEnv *, const char *message) { JvFail (message); } static jboolean _Jv_JNI_IsSameObject (JNIEnv *, jobject obj1, jobject obj2) { return unwrap (obj1) == unwrap (obj2); } static jobject _Jv_JNI_AllocObject (JNIEnv *env, jclass clazz) { jobject obj = NULL; using namespace java::lang::reflect; try { clazz = unwrap (clazz); JvAssert (clazz && ! clazz->isArray ()); if (clazz->isInterface() || Modifier::isAbstract(clazz->getModifiers())) env->ex = new java::lang::InstantiationException (); else { // FIXME: will this work for String? obj = JvAllocObject (clazz); } } catch (jthrowable t) { env->ex = t; } return wrap_value (env, obj); } static jclass _Jv_JNI_GetObjectClass (JNIEnv *env, jobject obj) { obj = unwrap (obj); JvAssert (obj); return (jclass) wrap_value (env, obj->getClass()); } static jboolean _Jv_JNI_IsInstanceOf (JNIEnv *, jobject obj, jclass clazz) { return unwrap (clazz)->isInstance(unwrap (obj)); } // // This section concerns method invocation. // template static jmethodID _Jv_JNI_GetAnyMethodID (JNIEnv *env, jclass clazz, const char *name, const char *sig) { try { clazz = unwrap (clazz); _Jv_InitClass (clazz); _Jv_Utf8Const *name_u = _Jv_makeUtf8Const ((char *) name, -1); // FIXME: assume that SIG isn't too long. int len = strlen (sig); char s[len + 1]; for (int i = 0; i <= len; ++i) s[i] = (sig[i] == '/') ? '.' : sig[i]; _Jv_Utf8Const *sig_u = _Jv_makeUtf8Const ((char *) s, -1); JvAssert (! clazz->isPrimitive()); using namespace java::lang::reflect; while (clazz != NULL) { jint count = JvNumMethods (clazz); jmethodID meth = JvGetFirstMethod (clazz); for (jint i = 0; i < count; ++i) { if (((is_static && Modifier::isStatic (meth->accflags)) || (! is_static && ! Modifier::isStatic (meth->accflags))) && _Jv_equalUtf8Consts (meth->name, name_u) && _Jv_equalUtf8Consts (meth->signature, sig_u)) return meth; meth = meth->getNextMethod(); } clazz = clazz->getSuperclass (); } env->ex = new java::lang::NoSuchMethodError (); } catch (jthrowable t) { env->ex = t; } return NULL; } // This is a helper function which turns a va_list into an array of // `jvalue's. It needs signature information in order to do its work. // The array of values must already be allocated. static void array_from_valist (jvalue *values, JArray *arg_types, va_list vargs) { jclass *arg_elts = elements (arg_types); for (int i = 0; i < arg_types->length; ++i) { if (arg_elts[i] == JvPrimClass (byte)) values[i].b = (jbyte) va_arg (vargs, int); else if (arg_elts[i] == JvPrimClass (short)) values[i].s = (jshort) va_arg (vargs, int); else if (arg_elts[i] == JvPrimClass (int)) values[i].i = va_arg (vargs, jint); else if (arg_elts[i] == JvPrimClass (long)) values[i].j = va_arg (vargs, jlong); else if (arg_elts[i] == JvPrimClass (float)) values[i].f = va_arg (vargs, jfloat); else if (arg_elts[i] == JvPrimClass (double)) values[i].d = va_arg (vargs, jdouble); else if (arg_elts[i] == JvPrimClass (boolean)) values[i].z = (jboolean) va_arg (vargs, int); else if (arg_elts[i] == JvPrimClass (char)) values[i].c = (jchar) va_arg (vargs, int); else { // An object. values[i].l = unwrap (va_arg (vargs, jobject)); } } } // This can call any sort of method: virtual, "nonvirtual", static, or // constructor. template static T _Jv_JNI_CallAnyMethodV (JNIEnv *env, jobject obj, jclass klass, jmethodID id, va_list vargs) { obj = unwrap (obj); klass = unwrap (klass); if (style == normal) id = _Jv_LookupDeclaredMethod (obj->getClass (), id->name, id->signature); jclass decl_class = klass ? klass : obj->getClass (); JvAssert (decl_class != NULL); jclass return_type; JArray *arg_types; try { _Jv_GetTypesFromSignature (id, decl_class, &arg_types, &return_type); jvalue args[arg_types->length]; array_from_valist (args, arg_types, vargs); // For constructors we need to pass the Class we are instantiating. if (style == constructor) return_type = klass; jvalue result; jthrowable ex = _Jv_CallAnyMethodA (obj, return_type, id, style == constructor, arg_types, args, &result); if (ex != NULL) env->ex = ex; // We cheat a little here. FIXME. return wrap_value (env, * (T *) &result); } catch (jthrowable t) { env->ex = t; } return wrap_value (env, (T) 0); } template static T _Jv_JNI_CallAnyMethod (JNIEnv *env, jobject obj, jclass klass, jmethodID method, ...) { va_list args; T result; va_start (args, method); result = _Jv_JNI_CallAnyMethodV (env, obj, klass, method, args); va_end (args); return result; } template static T _Jv_JNI_CallAnyMethodA (JNIEnv *env, jobject obj, jclass klass, jmethodID id, jvalue *args) { obj = unwrap (obj); klass = unwrap (klass); if (style == normal) id = _Jv_LookupDeclaredMethod (obj->getClass (), id->name, id->signature); jclass decl_class = klass ? klass : obj->getClass (); JvAssert (decl_class != NULL); jclass return_type; JArray *arg_types; try { _Jv_GetTypesFromSignature (id, decl_class, &arg_types, &return_type); // For constructors we need to pass the Class we are instantiating. if (style == constructor) return_type = klass; // Unwrap arguments as required. Eww. jclass *type_elts = elements (arg_types); jvalue arg_copy[arg_types->length]; for (int i = 0; i < arg_types->length; ++i) { if (type_elts[i]->isPrimitive ()) arg_copy[i] = args[i]; else arg_copy[i].l = unwrap (args[i].l); } jvalue result; jthrowable ex = _Jv_CallAnyMethodA (obj, return_type, id, style == constructor, arg_types, arg_copy, &result); if (ex != NULL) env->ex = ex; // We cheat a little here. FIXME. return wrap_value (env, * (T *) &result); } catch (jthrowable t) { env->ex = t; } return wrap_value (env, (T) 0); } template static void _Jv_JNI_CallAnyVoidMethodV (JNIEnv *env, jobject obj, jclass klass, jmethodID id, va_list vargs) { obj = unwrap (obj); klass = unwrap (klass); if (style == normal) id = _Jv_LookupDeclaredMethod (obj->getClass (), id->name, id->signature); jclass decl_class = klass ? klass : obj->getClass (); JvAssert (decl_class != NULL); jclass return_type; JArray *arg_types; try { _Jv_GetTypesFromSignature (id, decl_class, &arg_types, &return_type); jvalue args[arg_types->length]; array_from_valist (args, arg_types, vargs); // For constructors we need to pass the Class we are instantiating. if (style == constructor) return_type = klass; jthrowable ex = _Jv_CallAnyMethodA (obj, return_type, id, style == constructor, arg_types, args, NULL); if (ex != NULL) env->ex = ex; } catch (jthrowable t) { env->ex = t; } } template static void _Jv_JNI_CallAnyVoidMethod (JNIEnv *env, jobject obj, jclass klass, jmethodID method, ...) { va_list args; va_start (args, method); _Jv_JNI_CallAnyVoidMethodV