// 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 #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 #if USE_LIBFFI #include #else #include #endif typedef JArray< ::java::lang::annotation::Annotation * > * anno_a_t; typedef JArray< JArray< ::java::lang::annotation::Annotation * > *> * anno_aa_t; 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 (! this->isAccessible()) { if (! (Modifier::isPublic (meth->accflags))) { Class *caller = _Jv_StackTrace::GetCallingClass (&Method::class$); if (! _Jv_CheckAccess(caller, declaringClass, meth->accflags)) throw new IllegalAccessException; } else // Method is public, check to see if class is accessible. { jint flags = (declaringClass->accflags & (Modifier::PUBLIC | Modifier::PROTECTED | Modifier::PRIVATE)); if (flags == 0) // i.e. class is package private { Class *caller = _Jv_StackTrace::GetCallingClass (&Method::class$); if (! _Jv_ClassNameSamePackage (caller->name, declaringClass->name)) 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::getSignature() { return declaringClass->getReflectionSignature (this); } jobject java::lang::reflect::Method::getDefaultValue() { return declaringClass->getMethodDefaultValue(this); } anno_a_t java::lang::reflect::Method::getDeclaredAnnotationsInternal() { return (anno_a_t) declaringClass->getDeclaredAnnotations(this, false); } anno_aa_t java::lang::reflect::Method::getParameterAnnotationsInternal() { return (anno_aa_t) declaringClass->getDeclaredAnnotations(this, true); } 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 *) 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 **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 *args = (JArray *) 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 CallNonvirtualMethodA // 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 *parameter_types, const 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 *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; }