| // 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; |
| } |