Refactor reflective method invocation.
Move invocation code out of JNI internal into reflection, including ArgArray
code. Make reflective invocation use the ArgArray to build arguments rather
than allocating a jvalue[] and unboxing arguments into that.
Move reflection part of jni_internal_test into reflection_test.
Make greater use of fast JNI.
Change-Id: Ib381372df5f9a83679e30e7275de24fa0e6b1057
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index 0bfa70f..4310557 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -19,7 +19,6 @@
#include "class_linker.h"
#include "common_throws.h"
#include "dex_file-inl.h"
-#include "invoke_arg_array_builder.h"
#include "jni_internal.h"
#include "mirror/art_field-inl.h"
#include "mirror/art_method-inl.h"
@@ -29,12 +28,440 @@
#include "mirror/object_array-inl.h"
#include "object_utils.h"
#include "scoped_thread_state_change.h"
+#include "stack.h"
#include "well_known_classes.h"
namespace art {
-jobject InvokeMethod(const ScopedObjectAccess& soa, jobject javaMethod, jobject javaReceiver,
- jobject javaArgs) {
+class ArgArray {
+ public:
+ explicit ArgArray(const char* shorty, uint32_t shorty_len)
+ : shorty_(shorty), shorty_len_(shorty_len), num_bytes_(0) {
+ size_t num_slots = shorty_len + 1; // +1 in case of receiver.
+ if (LIKELY((num_slots * 2) < kSmallArgArraySize)) {
+ // We can trivially use the small arg array.
+ arg_array_ = small_arg_array_;
+ } else {
+ // Analyze shorty to see if we need the large arg array.
+ for (size_t i = 1; i < shorty_len; ++i) {
+ char c = shorty[i];
+ if (c == 'J' || c == 'D') {
+ num_slots++;
+ }
+ }
+ if (num_slots <= kSmallArgArraySize) {
+ arg_array_ = small_arg_array_;
+ } else {
+ large_arg_array_.reset(new uint32_t[num_slots]);
+ arg_array_ = large_arg_array_.get();
+ }
+ }
+ }
+
+ uint32_t* GetArray() {
+ return arg_array_;
+ }
+
+ uint32_t GetNumBytes() {
+ return num_bytes_;
+ }
+
+ void Append(uint32_t value) {
+ arg_array_[num_bytes_ / 4] = value;
+ num_bytes_ += 4;
+ }
+
+ void Append(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Append(StackReference<mirror::Object>::FromMirrorPtr(obj).AsVRegValue());
+ }
+
+ void AppendWide(uint64_t value) {
+ // For ARM and MIPS portable, align wide values to 8 bytes (ArgArray starts at offset of 4).
+#if defined(ART_USE_PORTABLE_COMPILER) && (defined(__arm__) || defined(__mips__))
+ if (num_bytes_ % 8 == 0) {
+ num_bytes_ += 4;
+ }
+#endif
+ arg_array_[num_bytes_ / 4] = value;
+ arg_array_[(num_bytes_ / 4) + 1] = value >> 32;
+ num_bytes_ += 8;
+ }
+
+ void AppendFloat(float value) {
+ jvalue jv;
+ jv.f = value;
+ Append(jv.i);
+ }
+
+ void AppendDouble(double value) {
+ jvalue jv;
+ jv.d = value;
+ AppendWide(jv.j);
+ }
+
+ void BuildArgArray(const ScopedObjectAccess& soa, mirror::Object* receiver, va_list ap)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ // Set receiver if non-null (method is not static)
+ if (receiver != nullptr) {
+ Append(receiver);
+ }
+ for (size_t i = 1; i < shorty_len_; ++i) {
+ switch (shorty_[i]) {
+ case 'Z':
+ case 'B':
+ case 'C':
+ case 'S':
+ case 'I':
+ Append(va_arg(ap, jint));
+ break;
+ case 'F':
+ AppendFloat(va_arg(ap, jdouble));
+ break;
+ case 'L':
+ Append(soa.Decode<mirror::Object*>(va_arg(ap, jobject)));
+ break;
+ case 'D':
+ AppendDouble(va_arg(ap, jdouble));
+ break;
+ case 'J':
+ AppendWide(va_arg(ap, jlong));
+ break;
+#ifndef NDEBUG
+ default:
+ LOG(FATAL) << "Unexpected shorty character: " << shorty_[i];
+#endif
+ }
+ }
+ }
+
+ void BuildArgArray(const ScopedObjectAccessUnchecked& soa, mirror::Object* receiver,
+ jvalue* args)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ // Set receiver if non-null (method is not static)
+ if (receiver != nullptr) {
+ Append(receiver);
+ }
+ for (size_t i = 1, args_offset = 0; i < shorty_len_; ++i, ++args_offset) {
+ switch (shorty_[i]) {
+ case 'Z':
+ Append(args[args_offset].z);
+ break;
+ case 'B':
+ Append(args[args_offset].b);
+ break;
+ case 'C':
+ Append(args[args_offset].c);
+ break;
+ case 'S':
+ Append(args[args_offset].s);
+ break;
+ case 'I':
+ case 'F':
+ Append(args[args_offset].i);
+ break;
+ case 'L':
+ Append(soa.Decode<mirror::Object*>(args[args_offset].l));
+ break;
+ case 'D':
+ case 'J':
+ AppendWide(args[args_offset].j);
+ break;
+#ifndef NDEBUG
+ default:
+ LOG(FATAL) << "Unexpected shorty character: " << shorty_[i];
+#endif
+ }
+ }
+ }
+
+ void BuildArgArrayFromFrame(ShadowFrame* shadow_frame, uint32_t arg_offset)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ // Set receiver if non-null (method is not static)
+ size_t cur_arg = arg_offset;
+ if (!shadow_frame->GetMethod()->IsStatic()) {
+ Append(shadow_frame->GetVReg(cur_arg));
+ cur_arg++;
+ }
+ for (size_t i = 1; i < shorty_len_; ++i) {
+ switch (shorty_[i]) {
+ case 'Z':
+ case 'B':
+ case 'C':
+ case 'S':
+ case 'I':
+ case 'F':
+ case 'L':
+ Append(shadow_frame->GetVReg(cur_arg));
+ cur_arg++;
+ break;
+ case 'D':
+ case 'J':
+ AppendWide(shadow_frame->GetVRegLong(cur_arg));
+ cur_arg++;
+ cur_arg++;
+ break;
+#ifndef NDEBUG
+ default:
+ LOG(FATAL) << "Unexpected shorty character: " << shorty_[i];
+#endif
+ }
+ }
+ }
+
+ static void ThrowIllegalPrimitiveArgumentException(const char* expected,
+ const StringPiece& found_descriptor)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ThrowIllegalArgumentException(nullptr,
+ StringPrintf("Invalid primitive conversion from %s to %s", expected,
+ PrettyDescriptor(found_descriptor.as_string()).c_str()).c_str());
+ }
+
+ bool BuildArgArray(const ScopedObjectAccess& soa, mirror::Object* receiver,
+ mirror::ObjectArray<mirror::Object>* args, MethodHelper& mh)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ const DexFile::TypeList* classes = mh.GetParameterTypeList();
+ // Set receiver if non-null (method is not static)
+ if (receiver != nullptr) {
+ Append(receiver);
+ }
+ for (size_t i = 1, args_offset = 0; i < shorty_len_; ++i, ++args_offset) {
+ mirror::Object* arg = args->Get(args_offset);
+ if (((shorty_[i] == 'L') && (arg != nullptr)) || ((arg == nullptr && shorty_[i] != 'L'))) {
+ mirror::Class* dst_class =
+ mh.GetClassFromTypeIdx(classes->GetTypeItem(args_offset).type_idx_);
+ if (UNLIKELY(arg == nullptr || !arg->InstanceOf(dst_class))) {
+ ThrowIllegalArgumentException(nullptr,
+ StringPrintf("method %s argument %d has type %s, got %s",
+ PrettyMethod(mh.GetMethod(), false).c_str(),
+ args_offset + 1, // Humans don't count from 0.
+ PrettyDescriptor(dst_class).c_str(),
+ PrettyTypeOf(arg).c_str()).c_str());
+ return false;
+ }
+ }
+
+#define DO_FIRST_ARG(match_descriptor, get_fn, append) { \
+ const StringPiece src_descriptor(arg != nullptr \
+ ? ClassHelper(arg->GetClass<>()).GetDescriptor() \
+ : "null"); \
+ if (LIKELY(src_descriptor == match_descriptor)) { \
+ mirror::ArtField* primitive_field = arg->GetClass()->GetIFields()->Get(0); \
+ append(primitive_field-> get_fn(arg));
+
+#define DO_ARG(match_descriptor, get_fn, append) \
+ } else if (LIKELY(src_descriptor == match_descriptor)) { \
+ mirror::ArtField* primitive_field = arg->GetClass()->GetIFields()->Get(0); \
+ append(primitive_field-> get_fn(arg));
+
+#define DO_FAIL(expected) \
+ } else { \
+ if (arg->GetClass<>()->IsPrimitive()) { \
+ ThrowIllegalPrimitiveArgumentException(expected, src_descriptor); \
+ } else { \
+ ThrowIllegalArgumentException(nullptr, \
+ StringPrintf("method %s argument %d has type %s, got %s", \
+ PrettyMethod(mh.GetMethod(), false).c_str(), \
+ args_offset + 1, \
+ expected, \
+ PrettyTypeOf(arg).c_str()).c_str()); \
+ } \
+ return false; \
+ } }
+
+ switch (shorty_[i]) {
+ case 'L':
+ Append(arg);
+ break;
+ case 'Z':
+ DO_FIRST_ARG("Ljava/lang/Boolean;", GetBoolean, Append)
+ DO_FAIL("boolean")
+ break;
+ case 'B':
+ DO_FIRST_ARG("Ljava/lang/Byte;", GetByte, Append)
+ DO_FAIL("byte")
+ break;
+ case 'C':
+ DO_FIRST_ARG("Ljava/lang/Character;", GetChar, Append)
+ DO_FAIL("char")
+ break;
+ case 'S':
+ DO_FIRST_ARG("Ljava/lang/Short;", GetShort, Append)
+ DO_ARG("Ljava/lang/Byte;", GetByte, Append)
+ DO_FAIL("short")
+ break;
+ case 'I':
+ DO_FIRST_ARG("Ljava/lang/Integer;", GetInt, Append)
+ DO_ARG("Ljava/lang/Character;", GetChar, Append)
+ DO_ARG("Ljava/lang/Short;", GetShort, Append)
+ DO_ARG("Ljava/lang/Byte;", GetByte, Append)
+ DO_FAIL("int")
+ break;
+ case 'J':
+ DO_FIRST_ARG("Ljava/lang/Long;", GetLong, AppendWide)
+ DO_ARG("Ljava/lang/Integer;", GetInt, AppendWide)
+ DO_ARG("Ljava/lang/Character;", GetChar, AppendWide)
+ DO_ARG("Ljava/lang/Short;", GetShort, AppendWide)
+ DO_ARG("Ljava/lang/Byte;", GetByte, AppendWide)
+ DO_FAIL("long")
+ break;
+ case 'F':
+ DO_FIRST_ARG("Ljava/lang/Float;", GetFloat, AppendFloat)
+ DO_ARG("Ljava/lang/Long;", GetLong, AppendFloat)
+ DO_ARG("Ljava/lang/Integer;", GetInt, AppendFloat)
+ DO_ARG("Ljava/lang/Character;", GetChar, AppendFloat)
+ DO_ARG("Ljava/lang/Short;", GetShort, AppendFloat)
+ DO_ARG("Ljava/lang/Byte;", GetByte, AppendFloat)
+ DO_FAIL("float")
+ break;
+ case 'D':
+ DO_FIRST_ARG("Ljava/lang/Double;", GetDouble, AppendDouble)
+ DO_ARG("Ljava/lang/Float;", GetFloat, AppendDouble)
+ DO_ARG("Ljava/lang/Long;", GetLong, AppendDouble)
+ DO_ARG("Ljava/lang/Integer;", GetInt, AppendDouble)
+ DO_ARG("Ljava/lang/Character;", GetChar, AppendDouble)
+ DO_ARG("Ljava/lang/Short;", GetShort, AppendDouble)
+ DO_ARG("Ljava/lang/Byte;", GetByte, AppendDouble)
+ DO_FAIL("double")
+ break;
+#ifndef NDEBUG
+ default:
+ LOG(FATAL) << "Unexpected shorty character: " << shorty_[i];
+#endif
+ }
+#undef DO_FIRST_ARG
+#undef DO_ARG
+#undef DO_FAIL
+ }
+ return true;
+ }
+
+ private:
+ enum { kSmallArgArraySize = 16 };
+ const char* const shorty_;
+ const uint32_t shorty_len_;
+ uint32_t num_bytes_;
+ uint32_t* arg_array_;
+ uint32_t small_arg_array_[kSmallArgArraySize];
+ UniquePtr<uint32_t[]> large_arg_array_;
+};
+
+static void CheckMethodArguments(mirror::ArtMethod* m, uint32_t* args)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ const DexFile::TypeList* params = MethodHelper(m).GetParameterTypeList();
+ if (params == nullptr) {
+ return; // No arguments so nothing to check.
+ }
+ uint32_t offset = 0;
+ uint32_t num_params = params->Size();
+ size_t error_count = 0;
+ if (!m->IsStatic()) {
+ offset = 1;
+ }
+ for (uint32_t i = 0; i < num_params; i++) {
+ uint16_t type_idx = params->GetTypeItem(i).type_idx_;
+ mirror::Class* param_type = MethodHelper(m).GetClassFromTypeIdx(type_idx);
+ if (param_type == nullptr) {
+ Thread* self = Thread::Current();
+ CHECK(self->IsExceptionPending());
+ LOG(ERROR) << "Internal error: unresolvable type for argument type in JNI invoke: "
+ << MethodHelper(m).GetTypeDescriptorFromTypeIdx(type_idx) << "\n"
+ << self->GetException(nullptr)->Dump();
+ self->ClearException();
+ ++error_count;
+ } else if (!param_type->IsPrimitive()) {
+ // TODO: check primitives are in range.
+ mirror::Object* argument = reinterpret_cast<mirror::Object*>(args[i + offset]);
+ if (argument != nullptr && !argument->InstanceOf(param_type)) {
+ LOG(ERROR) << "JNI ERROR (app bug): attempt to pass an instance of "
+ << PrettyTypeOf(argument) << " as argument " << (i + 1)
+ << " to " << PrettyMethod(m);
+ ++error_count;
+ }
+ } else if (param_type->IsPrimitiveLong() || param_type->IsPrimitiveDouble()) {
+ offset++;
+ }
+ }
+ if (error_count > 0) {
+ // TODO: pass the JNI function name (such as "CallVoidMethodV") through so we can call JniAbort
+ // with an argument.
+ JniAbortF(nullptr, "bad arguments passed to %s (see above for details)",
+ PrettyMethod(m).c_str());
+ }
+}
+
+static mirror::ArtMethod* FindVirtualMethod(mirror::Object* receiver,
+ mirror::ArtMethod* method)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(method);
+}
+
+
+static void InvokeWithArgArray(const ScopedObjectAccessUnchecked& soa, mirror::ArtMethod* method,
+ ArgArray* arg_array, JValue* result, const char* shorty)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ uint32_t* args = arg_array->GetArray();
+ if (UNLIKELY(soa.Env()->check_jni)) {
+ CheckMethodArguments(method, args);
+ }
+ method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);
+}
+
+JValue InvokeWithVarArgs(const ScopedObjectAccess& soa, jobject obj, jmethodID mid, va_list args)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ mirror::ArtMethod* method = soa.DecodeMethod(mid);
+ mirror::Object* receiver = method->IsStatic() ? nullptr : soa.Decode<mirror::Object*>(obj);
+ MethodHelper mh(method);
+ JValue result;
+ ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
+ arg_array.BuildArgArray(soa, receiver, args);
+ InvokeWithArgArray(soa, method, &arg_array, &result, mh.GetShorty());
+ return result;
+}
+
+JValue InvokeWithJValues(const ScopedObjectAccessUnchecked& soa, mirror::Object* receiver,
+ jmethodID mid, jvalue* args) {
+ mirror::ArtMethod* method = soa.DecodeMethod(mid);
+ MethodHelper mh(method);
+ JValue result;
+ ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
+ arg_array.BuildArgArray(soa, receiver, args);
+ InvokeWithArgArray(soa, method, &arg_array, &result, mh.GetShorty());
+ return result;
+}
+
+JValue InvokeVirtualOrInterfaceWithJValues(const ScopedObjectAccess& soa,
+ mirror::Object* receiver, jmethodID mid, jvalue* args) {
+ mirror::ArtMethod* method = FindVirtualMethod(receiver, soa.DecodeMethod(mid));
+ MethodHelper mh(method);
+ JValue result;
+ ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
+ arg_array.BuildArgArray(soa, receiver, args);
+ InvokeWithArgArray(soa, method, &arg_array, &result, mh.GetShorty());
+ return result;
+}
+
+JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccess& soa,
+ jobject obj, jmethodID mid, va_list args) {
+ mirror::Object* receiver = soa.Decode<mirror::Object*>(obj);
+ mirror::ArtMethod* method = FindVirtualMethod(receiver, soa.DecodeMethod(mid));
+ MethodHelper mh(method);
+ JValue result;
+ ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
+ arg_array.BuildArgArray(soa, receiver, args);
+ InvokeWithArgArray(soa, method, &arg_array, &result, mh.GetShorty());
+ return result;
+}
+
+void InvokeWithShadowFrame(Thread* self, ShadowFrame* shadow_frame, uint16_t arg_offset,
+ MethodHelper& mh, JValue* result) {
+ ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
+ arg_array.BuildArgArrayFromFrame(shadow_frame, arg_offset);
+ shadow_frame->GetMethod()->Invoke(self, arg_array.GetArray(), arg_array.GetNumBytes(), result,
+ mh.GetShorty());
+}
+
+jobject InvokeMethod(const ScopedObjectAccess& soa, jobject javaMethod,
+ jobject javaReceiver, jobject javaArgs) {
jmethodID mid = soa.Env()->FromReflectedMethod(javaMethod);
mirror::ArtMethod* m = soa.DecodeMethod(mid);
@@ -47,17 +474,16 @@
declaring_class = sirt_c.get();
}
- mirror::Object* receiver = NULL;
+ mirror::Object* receiver = nullptr;
if (!m->IsStatic()) {
// Check that the receiver is non-null and an instance of the field's declaring class.
receiver = soa.Decode<mirror::Object*>(javaReceiver);
- if (!VerifyObjectInClass(receiver, declaring_class)) {
+ if (!VerifyObjectIsClass(receiver, declaring_class)) {
return NULL;
}
// Find the actual implementation of the virtual method.
m = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(m);
- mid = soa.EncodeMethod(m);
}
// Get our arrays of arguments and their types, and check they're the same size.
@@ -65,8 +491,8 @@
soa.Decode<mirror::ObjectArray<mirror::Object>*>(javaArgs);
MethodHelper mh(m);
const DexFile::TypeList* classes = mh.GetParameterTypeList();
- uint32_t classes_size = classes == NULL ? 0 : classes->Size();
- uint32_t arg_count = (objects != NULL) ? objects->GetLength() : 0;
+ uint32_t classes_size = (classes == nullptr) ? 0 : classes->Size();
+ uint32_t arg_count = (objects != nullptr) ? objects->GetLength() : 0;
if (arg_count != classes_size) {
ThrowIllegalArgumentException(NULL,
StringPrintf("Wrong number of arguments; expected %d, got %d",
@@ -74,22 +500,15 @@
return NULL;
}
- // Translate javaArgs to a jvalue[].
- UniquePtr<jvalue[]> args(new jvalue[arg_count]);
- JValue* decoded_args = reinterpret_cast<JValue*>(args.get());
- for (uint32_t i = 0; i < arg_count; ++i) {
- mirror::Object* arg = objects->Get(i);
- mirror::Class* dst_class = mh.GetClassFromTypeIdx(classes->GetTypeItem(i).type_idx_);
- if (!UnboxPrimitiveForArgument(arg, dst_class, decoded_args[i], m, i)) {
- return NULL;
- }
- if (!dst_class->IsPrimitive()) {
- args[i].l = soa.AddLocalReference<jobject>(arg);
- }
+ // Invoke the method.
+ JValue result;
+ ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
+ if (!arg_array.BuildArgArray(soa, receiver, objects, mh)) {
+ CHECK(soa.Self()->IsExceptionPending());
+ return nullptr;
}
- // Invoke the method.
- JValue value(InvokeWithJValues(soa, javaReceiver, mid, args.get()));
+ InvokeWithArgArray(soa, m, &arg_array, &result, mh.GetShorty());
// Wrap any exception with "Ljava/lang/reflect/InvocationTargetException;" and return early.
if (soa.Self()->IsExceptionPending()) {
@@ -103,10 +522,11 @@
}
// Box if necessary and return.
- return soa.AddLocalReference<jobject>(BoxPrimitive(mh.GetReturnType()->GetPrimitiveType(), value));
+ return soa.AddLocalReference<jobject>(BoxPrimitive(mh.GetReturnType()->GetPrimitiveType(),
+ result));
}
-bool VerifyObjectInClass(mirror::Object* o, mirror::Class* c) {
+bool VerifyObjectIsClass(mirror::Object* o, mirror::Class* c) {
if (o == NULL) {
ThrowNullPointerException(NULL, "null receiver");
return false;
@@ -218,6 +638,10 @@
if (src_class == Primitive::kPrimNot) {
return value.GetL();
}
+ if (src_class == Primitive::kPrimVoid) {
+ // There's no such thing as a void field, and void methods invoked via reflection return null.
+ return nullptr;
+ }
jmethodID m = NULL;
const char* shorty;
@@ -254,20 +678,15 @@
m = WellKnownClasses::java_lang_Short_valueOf;
shorty = "LS";
break;
- case Primitive::kPrimVoid:
- // There's no such thing as a void field, and void methods invoked via reflection return null.
- return nullptr;
default:
LOG(FATAL) << static_cast<int>(src_class);
shorty = nullptr;
}
ScopedObjectAccessUnchecked soa(Thread::Current());
- if (kIsDebugBuild) {
- CHECK_EQ(soa.Self()->GetState(), kRunnable);
- }
+ DCHECK_EQ(soa.Self()->GetState(), kRunnable);
- ArgArray arg_array(nullptr, 0);
+ ArgArray arg_array(shorty, 2);
JValue result;
if (src_class == Primitive::kPrimDouble || src_class == Primitive::kPrimLong) {
arg_array.AppendWide(value.GetJ());