| // natMethod.cc - Native code for Method class. | |
| /* Copyright (C) 1998, 1999, 2000, 2001 , 2002, 2003, 2004, 2005, 2006 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 <config.h> | |
| #include <gcj/cni.h> | |
| #include <jvm.h> | |
| #include <jni.h> | |
| #include <java-stack.h> | |
| #include <java/lang/reflect/Method.h> | |
| #include <java/lang/reflect/Constructor.h> | |
| #include <java/lang/reflect/InvocationTargetException.h> | |
| #include <java/lang/reflect/Modifier.h> | |
| #include <java/lang/Void.h> | |
| #include <java/lang/Byte.h> | |
| #include <java/lang/Boolean.h> | |
| #include <java/lang/Character.h> | |
| #include <java/lang/Short.h> | |
| #include <java/lang/Integer.h> | |
| #include <java/lang/Long.h> | |
| #include <java/lang/Float.h> | |
| #include <java/lang/Double.h> | |
| #include <java/lang/IllegalAccessException.h> | |
| #include <java/lang/IllegalArgumentException.h> | |
| #include <java/lang/IncompatibleClassChangeError.h> | |
| #include <java/lang/NullPointerException.h> | |
| #include <java/lang/ArrayIndexOutOfBoundsException.h> | |
| #include <java/lang/VirtualMachineError.h> | |
| #include <java/lang/Class.h> | |
| #include <gcj/method.h> | |
| #include <gnu/gcj/RawData.h> | |
| #include <java/lang/NoClassDefFoundError.h> | |
| #include <stdlib.h> | |
| #if USE_LIBFFI | |
| #include <ffi.h> | |
| #else | |
| #include <java/lang/UnsupportedOperationException.h> | |
| #endif | |
| struct cpair | |
| { | |
| jclass prim; | |
| jclass wrap; | |
| }; | |
| // This is used to determine when a primitive widening conversion is | |
| // allowed. | |
| static cpair primitives[] = | |
| { | |
| #define BOOLEAN 0 | |
| { JvPrimClass (boolean), &java::lang::Boolean::class$ }, | |
| { JvPrimClass (byte), &java::lang::Byte::class$ }, | |
| #define SHORT 2 | |
| { JvPrimClass (short), &java::lang::Short::class$ }, | |
| #define CHAR 3 | |
| { JvPrimClass (char), &java::lang::Character::class$ }, | |
| { JvPrimClass (int), &java::lang::Integer::class$ }, | |
| { JvPrimClass (long), &java::lang::Long::class$ }, | |
| { JvPrimClass (float), &java::lang::Float::class$ }, | |
| { JvPrimClass (double), &java::lang::Double::class$ }, | |
| { NULL, NULL } | |
| }; | |
| static inline jboolean | |
| can_widen (jclass from, jclass to) | |
| { | |
| int fromx = -1, tox = -1; | |
| for (int i = 0; primitives[i].prim; ++i) | |
| { | |
| if (primitives[i].wrap == from) | |
| fromx = i; | |
| if (primitives[i].prim == to) | |
| tox = i; | |
| } | |
| // Can't handle a miss. | |
| if (fromx == -1 || tox == -1) | |
| return false; | |
| // Boolean arguments may not be widened. | |
| if (fromx == BOOLEAN && tox != BOOLEAN) | |
| return false; | |
| // Nothing promotes to char. | |
| if (tox == CHAR && fromx != CHAR) | |
| return false; | |
| return fromx <= tox; | |
| } | |
| #ifdef USE_LIBFFI | |
| static inline ffi_type * | |
| get_ffi_type (jclass klass) | |
| { | |
| // A special case. | |
| if (klass == NULL) | |
| return &ffi_type_pointer; | |
| ffi_type *r; | |
| if (klass == JvPrimClass (byte)) | |
| r = &ffi_type_sint8; | |
| else if (klass == JvPrimClass (short)) | |
| r = &ffi_type_sint16; | |
| else if (klass == JvPrimClass (int)) | |
| r = &ffi_type_sint32; | |
| else if (klass == JvPrimClass (long)) | |
| r = &ffi_type_sint64; | |
| else if (klass == JvPrimClass (float)) | |
| r = &ffi_type_float; | |
| else if (klass == JvPrimClass (double)) | |
| r = &ffi_type_double; | |
| else if (klass == JvPrimClass (boolean)) | |
| { | |
| // On some platforms a bool is a byte, on others an int. | |
| if (sizeof (jboolean) == sizeof (jbyte)) | |
| r = &ffi_type_sint8; | |
| else | |
| { | |
| JvAssert (sizeof (jboolean) == sizeof (jint)); | |
| r = &ffi_type_sint32; | |
| } | |
| } | |
| else if (klass == JvPrimClass (char)) | |
| r = &ffi_type_uint16; | |
| else | |
| { | |
| JvAssert (! klass->isPrimitive()); | |
| r = &ffi_type_pointer; | |
| } | |
| return r; | |
| } | |
| #endif // USE_LIBFFI | |
| jobject | |
| java::lang::reflect::Method::invoke (jobject obj, jobjectArray args) | |
| { | |
| using namespace java::lang::reflect; | |
| jclass iface = NULL; | |
| if (parameter_types == NULL) | |
| getType (); | |
| jmethodID meth = _Jv_FromReflectedMethod (this); | |
| if (Modifier::isStatic(meth->accflags)) | |
| { | |
| // We have to initialize a static class. It is safe to do this | |
| // here and not in _Jv_CallAnyMethodA because JNI initializes a | |
| // class whenever a method lookup is done. | |
| _Jv_InitClass (declaringClass); | |
| } | |
| else | |
| { | |
| jclass objClass = JV_CLASS (obj); | |
| if (! _Jv_IsAssignableFrom (objClass, declaringClass)) | |
| throw new java::lang::IllegalArgumentException; | |
| } | |
| // Check accessibility, if required. | |
| if (! (Modifier::isPublic (meth->accflags) || this->isAccessible())) | |
| { | |
| Class *caller = _Jv_StackTrace::GetCallingClass (&Method::class$); | |
| if (! _Jv_CheckAccess(caller, declaringClass, meth->accflags)) | |
| throw new IllegalAccessException; | |
| } | |
| if (declaringClass->isInterface()) | |
| iface = declaringClass; | |
| return _Jv_CallAnyMethodA (obj, return_type, meth, false, | |
| parameter_types, args, iface); | |
| } | |
| jint | |
| java::lang::reflect::Method::getModifiersInternal () | |
| { | |
| return _Jv_FromReflectedMethod (this)->accflags; | |
| } | |
| jstring | |
| java::lang::reflect::Method::getName () | |
| { | |
| if (name == NULL) | |
| name = _Jv_NewStringUtf8Const (_Jv_FromReflectedMethod (this)->name); | |
| return name; | |
| } | |
| /* Internal method to set return_type and parameter_types fields. */ | |
| void | |
| java::lang::reflect::Method::getType () | |
| { | |
| _Jv_Method *method = _Jv_FromReflectedMethod (this); | |
| _Jv_GetTypesFromSignature (method, | |
| declaringClass, | |
| ¶meter_types, | |
| &return_type); | |
| int count = 0; | |
| if (method->throws != NULL) | |
| { | |
| while (method->throws[count] != NULL) | |
| ++count; | |
| } | |
| exception_types | |
| = (JArray<jclass> *) JvNewObjectArray (count, &java::lang::Class::class$, | |
| NULL); | |
| jclass *elts = elements (exception_types); | |
| for (int i = 0; i < count; ++i) | |
| elts[i] = _Jv_FindClass (method->throws[i], | |
| declaringClass->getClassLoaderInternal ()); | |
| } | |
| void | |
| _Jv_GetTypesFromSignature (jmethodID method, | |
| jclass declaringClass, | |
| JArray<jclass> **arg_types_out, | |
| jclass *return_type_out) | |
| { | |
| _Jv_Utf8Const* sig = method->signature; | |
| java::lang::ClassLoader *loader = declaringClass->getClassLoaderInternal(); | |
| char *ptr = sig->chars(); | |
| int numArgs = 0; | |
| /* First just count the number of parameters. */ | |
| // FIXME: should do some validation here, e.g., that there is only | |
| // one return type. | |
| for (; ; ptr++) | |
| { | |
| switch (*ptr) | |
| { | |
| case 0: | |
| case ')': | |
| case 'V': | |
| break; | |
| case '[': | |
| case '(': | |
| continue; | |
| case 'B': | |
| case 'C': | |
| case 'D': | |
| case 'F': | |
| case 'S': | |
| case 'I': | |
| case 'J': | |
| case 'Z': | |
| numArgs++; | |
| continue; | |
| case 'L': | |
| numArgs++; | |
| do | |
| ptr++; | |
| while (*ptr != ';' && ptr[1] != '\0'); | |
| continue; | |
| } | |
| break; | |
| } | |
| JArray<jclass> *args = (JArray<jclass> *) | |
| JvNewObjectArray (numArgs, &java::lang::Class::class$, NULL); | |
| jclass* argPtr = elements (args); | |
| for (ptr = sig->chars(); *ptr != '\0'; ptr++) | |
| { | |
| if (*ptr == '(') | |
| continue; | |
| if (*ptr == ')') | |
| { | |
| argPtr = return_type_out; | |
| continue; | |
| } | |
| char *end_ptr; | |
| jclass type = _Jv_FindClassFromSignature (ptr, loader, &end_ptr); | |
| if (type == NULL) | |
| // FIXME: This isn't ideal. | |
| throw new java::lang::NoClassDefFoundError (sig->toString()); | |
| // ARGPTR can be NULL if we are processing the return value of a | |
| // call from Constructor. | |
| if (argPtr) | |
| *argPtr++ = type; | |
| ptr = end_ptr; | |
| } | |
| *arg_types_out = args; | |
| } | |
| // This is a very rough analog of the JNI CallNonvirtual<type>MethodA | |
| // functions. It handles both Methods and Constructors, and it can | |
| // handle any return type. In the Constructor case, the `obj' | |
| // argument is unused and should be NULL; also, the `return_type' is | |
| // the class that the constructor will construct. RESULT is a pointer | |
| // to a `jvalue' (see jni.h); for a void method this should be NULL. | |
| // This function returns an exception (if one was thrown), or NULL if | |
| // the call went ok. | |
| void | |
| _Jv_CallAnyMethodA (jobject obj, | |
| jclass return_type, | |
| jmethodID meth, | |
| jboolean is_constructor, | |
| jboolean is_virtual_call, | |
| JArray<jclass> *parameter_types, | |
| jvalue *args, | |
| jvalue *result, | |
| jboolean is_jni_call, | |
| jclass iface) | |
| { | |
| using namespace java::lang::reflect; | |
| #ifdef USE_LIBFFI | |
| JvAssert (! is_constructor || ! obj); | |
| JvAssert (! is_constructor || return_type); | |
| // See whether call needs an object as the first argument. A | |
| // constructor does need a `this' argument, but it is one we create. | |
| jboolean needs_this = false; | |
| if (is_constructor | |
| || ! Modifier::isStatic(meth->accflags)) | |
| needs_this = true; | |
| int param_count = parameter_types->length; | |
| if (needs_this) | |
| ++param_count; | |
| ffi_type *rtype; | |
| // A constructor itself always returns void. | |
| if (is_constructor || return_type == JvPrimClass (void)) | |
| rtype = &ffi_type_void; | |
| else | |
| rtype = get_ffi_type (return_type); | |
| ffi_type **argtypes = (ffi_type **) __builtin_alloca (param_count | |
| * sizeof (ffi_type *)); | |
| jclass *paramelts = elements (parameter_types); | |
| // Special case for the `this' argument of a constructor. Note that | |
| // the JDK 1.2 docs specify that the new object must be allocated | |
| // before argument conversions are done. | |
| if (is_constructor) | |
| obj = _Jv_AllocObject (return_type); | |
| const int size_per_arg = sizeof(jvalue); | |
| ffi_cif cif; | |
| char *p = (char *) __builtin_alloca (param_count * size_per_arg); | |
| // Overallocate to get correct alignment. | |
| void **values = (void **) | |
| __builtin_alloca (param_count * sizeof (void *)); | |
| int i = 0; | |
| if (needs_this) | |
| { | |
| // The `NULL' type is `Object'. | |
| argtypes[i] = get_ffi_type (NULL); | |
| values[i] = p; | |
| memcpy (p, &obj, sizeof (jobject)); | |
| p += size_per_arg; | |
| ++i; | |
| } | |
| for (int arg = 0; i < param_count; ++i, ++arg) | |
| { | |
| int tsize; | |
| argtypes[i] = get_ffi_type (paramelts[arg]); | |
| if (paramelts[arg]->isPrimitive()) | |
| tsize = paramelts[arg]->size(); | |
| else | |
| tsize = sizeof (jobject); | |
| // Copy appropriate bits from the jvalue into the ffi array. | |
| // FIXME: we could do this copying all in one loop, above, by | |
| // over-allocating a bit. | |
| // How do we do this without breaking big-endian platforms? | |
| values[i] = p; | |
| memcpy (p, &args[arg], tsize); | |
| p += size_per_arg; | |
| } | |
| if (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, param_count, | |
| rtype, argtypes) != FFI_OK) | |
| throw new java::lang::VirtualMachineError(JvNewStringLatin1("internal error: ffi_prep_cif failed")); | |
| using namespace java::lang; | |
| using namespace java::lang::reflect; | |
| union | |
| { | |
| ffi_arg i; | |
| jobject o; | |
| jlong l; | |
| jfloat f; | |
| jdouble d; | |
| } ffi_result; | |
| switch (rtype->type) | |
| { | |
| case FFI_TYPE_VOID: | |
| break; | |
| case FFI_TYPE_SINT8: | |
| result->b = 0; | |
| break; | |
| case FFI_TYPE_SINT16: | |
| result->s = 0; | |
| break; | |
| case FFI_TYPE_UINT16: | |
| result->c = 0; | |
| break; | |
| case FFI_TYPE_SINT32: | |
| result->i = 0; | |
| break; | |
| case FFI_TYPE_SINT64: | |
| result->j = 0; | |
| break; | |
| case FFI_TYPE_FLOAT: | |
| result->f = 0; | |
| break; | |
| case FFI_TYPE_DOUBLE: | |
| result->d = 0; | |
| break; | |
| case FFI_TYPE_POINTER: | |
| result->l = 0; | |
| break; | |
| default: | |
| JvFail ("Unknown ffi_call return type"); | |
| break; | |
| } | |
| void *ncode; | |
| // FIXME: If a vtable index is -1 at this point it is invalid, so we | |
| // have to use the ncode. | |
| // | |
| // This can happen because methods in final classes don't have | |
| // vtable entries, but _Jv_isVirtualMethod() doesn't know that. We | |
| // could solve this problem by allocating a vtable index for methods | |
| // in final classes. | |
| if (is_virtual_call | |
| && ! Modifier::isFinal (meth->accflags) | |
| && (_Jv_ushort)-1 != meth->index) | |
| { | |
| _Jv_VTable *vtable = *(_Jv_VTable **) obj; | |
| if (iface == NULL) | |
| { | |
| if (is_jni_call && Modifier::isAbstract (meth->accflags)) | |
| { | |
| // With JNI we don't know if this is an interface call | |
| // or a call to an abstract method. Look up the method | |
| // by name, the slow way. | |
| _Jv_Method *concrete_meth | |
| = _Jv_LookupDeclaredMethod (vtable->clas, | |
| meth->name, | |
| meth->signature, | |
| NULL); | |
| if (concrete_meth == NULL | |
| || concrete_meth->ncode == NULL | |
| || Modifier::isAbstract(concrete_meth->accflags)) | |
| throw new java::lang::IncompatibleClassChangeError | |
| (_Jv_GetMethodString (vtable->clas, meth)); | |
| ncode = concrete_meth->ncode; | |
| } | |
| else | |
| ncode = vtable->get_method (meth->index); | |
| } | |
| else | |
| ncode = _Jv_LookupInterfaceMethodIdx (vtable->clas, iface, | |
| meth->index); | |
| } | |
| else | |
| { | |
| ncode = meth->ncode; | |
| } | |
| try | |
| { | |
| ffi_call (&cif, (void (*)()) ncode, &ffi_result, values); | |
| } | |
| catch (Throwable *ex) | |
| { | |
| // For JNI we just throw the real error. For reflection, we | |
| // wrap the underlying method's exception in an | |
| // InvocationTargetException. | |
| if (! is_jni_call) | |
| ex = new InvocationTargetException (ex); | |
| throw ex; | |
| } | |
| // Since ffi_call returns integer values promoted to a word, use | |
| // a narrowing conversion for jbyte, jchar, etc. results. | |
| // Note that boolean is handled either by the FFI_TYPE_SINT8 or | |
| // FFI_TYPE_SINT32 case. | |
| if (is_constructor) | |
| result->l = obj; | |
| else | |
| { | |
| switch (rtype->type) | |
| { | |
| case FFI_TYPE_VOID: | |
| break; | |
| case FFI_TYPE_SINT8: | |
| result->b = (jbyte)ffi_result.i; | |
| break; | |
| case FFI_TYPE_SINT16: | |
| result->s = (jshort)ffi_result.i; | |
| break; | |
| case FFI_TYPE_UINT16: | |
| result->c = (jchar)ffi_result.i; | |
| break; | |
| case FFI_TYPE_SINT32: | |
| result->i = (jint)ffi_result.i; | |
| break; | |
| case FFI_TYPE_SINT64: | |
| result->j = (jlong)ffi_result.l; | |
| break; | |
| case FFI_TYPE_FLOAT: | |
| result->f = (jfloat)ffi_result.f; | |
| break; | |
| case FFI_TYPE_DOUBLE: | |
| result->d = (jdouble)ffi_result.d; | |
| break; | |
| case FFI_TYPE_POINTER: | |
| result->l = (jobject)ffi_result.o; | |
| break; | |
| default: | |
| JvFail ("Unknown ffi_call return type"); | |
| break; | |
| } | |
| } | |
| #else | |
| throw new java::lang::UnsupportedOperationException(JvNewStringLatin1("reflection not available in this build")); | |
| #endif // USE_LIBFFI | |
| } | |
| // This is another version of _Jv_CallAnyMethodA, but this one does | |
| // more checking and is used by the reflection (and not JNI) code. | |
| jobject | |
| _Jv_CallAnyMethodA (jobject obj, | |
| jclass return_type, | |
| jmethodID meth, | |
| jboolean is_constructor, | |
| JArray<jclass> *parameter_types, | |
| jobjectArray args, | |
| jclass iface) | |
| { | |
| if (parameter_types->length == 0 && args == NULL) | |
| { | |
| // The JDK accepts this, so we do too. | |
| } | |
| else if (parameter_types->length != args->length) | |
| throw new java::lang::IllegalArgumentException; | |
| int param_count = parameter_types->length; | |
| jclass *paramelts = elements (parameter_types); | |
| jobject *argelts = args == NULL ? NULL : elements (args); | |
| jvalue argvals[param_count]; | |
| #define COPY(Where, What, Type) \ | |
| do { \ | |
| Type val = (What); \ | |
| memcpy ((Where), &val, sizeof (Type)); \ | |
| } while (0) | |
| for (int i = 0; i < param_count; ++i) | |
| { | |
| jclass k = argelts[i] ? argelts[i]->getClass() : NULL; | |
| if (paramelts[i]->isPrimitive()) | |
| { | |
| if (! argelts[i] | |
| || ! k | |
| || ! can_widen (k, paramelts[i])) | |
| throw new java::lang::IllegalArgumentException; | |
| if (paramelts[i] == JvPrimClass (boolean)) | |
| COPY (&argvals[i], | |
| ((java::lang::Boolean *) argelts[i])->booleanValue(), | |
| jboolean); | |
| else if (paramelts[i] == JvPrimClass (char)) | |
| COPY (&argvals[i], | |
| ((java::lang::Character *) argelts[i])->charValue(), | |
| jchar); | |
| else | |
| { | |
| java::lang::Number *num = (java::lang::Number *) argelts[i]; | |
| if (paramelts[i] == JvPrimClass (byte)) | |
| COPY (&argvals[i], num->byteValue(), jbyte); | |
| else if (paramelts[i] == JvPrimClass (short)) | |
| COPY (&argvals[i], num->shortValue(), jshort); | |
| else if (paramelts[i] == JvPrimClass (int)) | |
| COPY (&argvals[i], num->intValue(), jint); | |
| else if (paramelts[i] == JvPrimClass (long)) | |
| COPY (&argvals[i], num->longValue(), jlong); | |
| else if (paramelts[i] == JvPrimClass (float)) | |
| COPY (&argvals[i], num->floatValue(), jfloat); | |
| else if (paramelts[i] == JvPrimClass (double)) | |
| COPY (&argvals[i], num->doubleValue(), jdouble); | |
| } | |
| } | |
| else | |
| { | |
| if (argelts[i] && ! paramelts[i]->isAssignableFrom (k)) | |
| throw new java::lang::IllegalArgumentException; | |
| COPY (&argvals[i], argelts[i], jobject); | |
| } | |
| } | |
| jvalue ret_value; | |
| _Jv_CallAnyMethodA (obj, return_type, meth, is_constructor, | |
| _Jv_isVirtualMethod (meth), | |
| parameter_types, argvals, &ret_value, | |
| false, iface); | |
| jobject r; | |
| #define VAL(Wrapper, Field) (new Wrapper (ret_value.Field)) | |
| if (is_constructor) | |
| r = ret_value.l; | |
| else if (return_type == JvPrimClass (byte)) | |
| r = VAL (java::lang::Byte, b); | |
| else if (return_type == JvPrimClass (short)) | |
| r = VAL (java::lang::Short, s); | |
| else if (return_type == JvPrimClass (int)) | |
| r = VAL (java::lang::Integer, i); | |
| else if (return_type == JvPrimClass (long)) | |
| r = VAL (java::lang::Long, j); | |
| else if (return_type == JvPrimClass (float)) | |
| r = VAL (java::lang::Float, f); | |
| else if (return_type == JvPrimClass (double)) | |
| r = VAL (java::lang::Double, d); | |
| else if (return_type == JvPrimClass (boolean)) | |
| r = VAL (java::lang::Boolean, z); | |
| else if (return_type == JvPrimClass (char)) | |
| r = VAL (java::lang::Character, c); | |
| else if (return_type == JvPrimClass (void)) | |
| r = NULL; | |
| else | |
| { | |
| JvAssert (return_type == NULL || ! return_type->isPrimitive()); | |
| r = ret_value.l; | |
| } | |
| return r; | |
| } |