// jni.cc - JNI implementation, including the jump table. /* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 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 #include #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 #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 16 // 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; // Flag to indicate some locals were allocated. int allocated_p; // Number of elements in frame. int size; // 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 ()); } jobject _Jv_UnwrapJNIweakReference (jobject obj) { return unwrap (obj); } static jobject JNICALL _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 JNICALL _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 JNICALL _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 JNICALL _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; frame->allocated_p = 0; memset (&frame->vec[0], 0, size * sizeof (jobject)); frame->next = env->locals; env->locals = frame; return 0; } static jint JNICALL _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 JNICALL _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; frame->allocated_p = 1; break; } } // If we found a slot, or if the frame we just searched is the // mark frame, then we are done. if (done || frame == NULL || 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; env->locals->allocated_p = 1; } mark_for_gc (obj, local_ref_table); return obj; } static jobject JNICALL _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 reusable bottom_locals, and we must // not free it. However, we must be sure to clear all its elements. if (n == NULL) { if (rf->allocated_p) memset (&rf->vec[0], 0, rf->size * sizeof (jobject)); rf->allocated_p = 0; rf = NULL; 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 JNICALL _Jv_JNI_PopLocalFrame (JNIEnv *env, jobject result) { return _Jv_JNI_PopLocalFrame (env, result, MARK_USER); } // Make sure an array's type is compatible with the type of the // destination. template static bool _Jv_JNI_check_types (JNIEnv *env, JArray *array, jclass K) { jclass klass = array->getClass()->getComponentType(); if (__builtin_expect (klass != K, false)) { env->ex = new java::lang::IllegalAccessError (); return false; } else return true; } // 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) { // Only enter slow path when we're not at the bottom, or there have been // allocations. Usually this is false and we can just null out the locals // field. if (__builtin_expect ((env->locals->next || env->locals->allocated_p), false)) _Jv_JNI_PopLocalFrame (env, NULL, MARK_SYSTEM); else env->locals = NULL; if (__builtin_expect (env->ex != NULL, false)) { jthrowable t = env->ex; env->ex = NULL; throw t; } } template T extract_from_jvalue(jvalue const & t); template<> jboolean extract_from_jvalue(jvalue const & jv) { return jv.z; } template<> jbyte extract_from_jvalue(jvalue const & jv) { return jv.b; } template<> jchar extract_from_jvalue(jvalue const & jv) { return jv.c; } template<> jshort extract_from_jvalue(jvalue const & jv) { return jv.s; } template<> jint extract_from_jvalue(jvalue const & jv) { return jv.i; } template<> jlong extract_from_jvalue(jvalue const & jv) { return jv.j; } template<> jfloat extract_from_jvalue(jvalue const & jv) { return jv.f; } template<> jdouble extract_from_jvalue(jvalue const & jv) { return jv.d; } template<> jobject extract_from_jvalue(jvalue const & jv) { return jv.l; } // 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 JNICALL _Jv_JNI_GetVersion (JNIEnv *) { return JNI_VERSION_1_4; } static jclass JNICALL _Jv_JNI_DefineClass (JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize bufLen) { try { loader = unwrap (loader); jstring sname = JvNewStringUTF (name); 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 (sname, bytes, 0, bufLen); return (jclass) wrap_value (env, result); } catch (jthrowable t) { env->ex = t; return NULL; } } static jclass JNICALL _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->getClassLoaderInternal (); 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 JNICALL _Jv_JNI_GetSuperclass (JNIEnv *env, jclass clazz) { return (jclass) wrap_value (env, unwrap (clazz)->getSuperclass ()); } static jboolean JNICALL _Jv_JNI_IsAssignableFrom (JNIEnv *, jclass clazz1, jclass clazz2) { return unwrap (clazz2)->isAssignableFrom (unwrap (clazz1)); } static jint JNICALL _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 JNICALL _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] = &java::lang::String::class$; Constructor *cons = clazz->getConstructor (argtypes); jobjectArray values = JvNewObjectArray (1, &java::lang::String::class$, 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 JNICALL _Jv_JNI_ExceptionOccurred (JNIEnv *env) { return (jthrowable) wrap_value (env, env->ex); } static void JNICALL _Jv_JNI_ExceptionDescribe (JNIEnv *env) { if (env->ex != NULL) env->ex->printStackTrace(); } static void JNICALL _Jv_JNI_ExceptionClear (JNIEnv *env) { env->ex = NULL; } static jboolean JNICALL _Jv_JNI_ExceptionCheck (JNIEnv *env) { return env->ex != NULL; } static void JNICALL _Jv_JNI_FatalError (JNIEnv *, const char *message) { JvFail (message); } static jboolean JNICALL _Jv_JNI_IsSameObject (JNIEnv *, jobject obj1, jobject obj2) { return unwrap (obj1) == unwrap (obj2); } static jobject JNICALL _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 obj = _Jv_AllocObject (clazz); } catch (jthrowable t) { env->ex = t; } return wrap_value (env, obj); } static jclass JNICALL _Jv_JNI_GetObjectClass (JNIEnv *env, jobject obj) { obj = unwrap (obj); JvAssert (obj); return (jclass) wrap_value (env, obj->getClass()); } static jboolean JNICALL _Jv_JNI_IsInstanceOf (JNIEnv *, jobject obj, jclass clazz) { return unwrap (clazz)->isInstance(unwrap (obj)); } // // This section concerns method invocation. // template static jmethodID JNICALL _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 (); } java::lang::StringBuffer *name_sig = new java::lang::StringBuffer (JvNewStringUTF (name)); name_sig->append ((jchar) ' ')->append (JvNewStringUTF (s)); env->ex = new java::lang::NoSuchMethodError (name_sig->toString ()); } 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) { // Here we assume that sizeof(int) >= sizeof(jint), because we // use `int' when decoding the varargs. Likewise for // float, and double. Also we assume that sizeof(jlong) >= // sizeof(int), i.e. that jlong values are not further // promoted. JvAssert (sizeof (int) >= sizeof (jint)); JvAssert (sizeof (jlong) >= sizeof (int)); JvAssert (sizeof (double) >= sizeof (jfloat)); JvAssert (sizeof (double) >= sizeof (jdouble)); 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 = (jint) va_arg (vargs, int); else if (arg_elts[i] == JvPrimClass (long)) values[i].j = (jlong) va_arg (vargs, jlong); else if (arg_elts[i] == JvPrimClass (float)) values[i].f = (jfloat) va_arg (vargs, double); else if (arg_elts[i] == JvPrimClass (double)) values[i].d = (jdouble) va_arg (vargs, double); 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 JNICALL _Jv_JNI_CallAnyMethodV (JNIEnv *env, jobject obj, jclass klass, jmethodID id, va_list vargs) { obj = unwrap (obj); klass = unwrap (klass); 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; _Jv_CallAnyMethodA (obj, return_type, id, style == constructor, style == normal, arg_types, args, &result); return wrap_value (env, extract_from_jvalue(result)); } catch (jthrowable t) { env->ex = t; } return wrap_value (env, (T) 0); } template static T JNICALL _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 JNICALL _Jv_JNI_CallAnyMethodA (JNIEnv *env, jobject obj, jclass klass, jmethodID id, jvalue *args) { obj = unwrap (obj); klass = unwrap (klass); 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; _Jv_CallAnyMethodA (obj, return_type, id, style == constructor, style == normal, arg_types, arg_copy, &result); return wrap_value (env, extract_from_jvalue(result)); } catch (jthrowable t) { env->ex = t; } return wrap_value (env, (T) 0); } template static void JNICALL _Jv_JNI_CallAnyVoidMethodV (JNIEnv *env, jobject obj, jclass klass, jmethodID id, va_list vargs) { obj = unwrap (obj); klass = unwrap (klass); 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; _Jv_CallAnyMethodA (obj, return_type, id, style == constructor, style == normal, arg_types, args, NULL); } catch (jthrowable t) { env->ex = t; } } template static void JNICALL _Jv_JNI_CallAnyVoidMethod (JNIEnv *env, jobject obj, jclass klass, jmethodID method, ...) { va_list args; va_start (args, method); _Jv_JNI_CallAnyVoidMethodV