Implement various JNI internals. Fix off by 1 bug.
Make GetMethodID and GetStaticMethodID throw NoSuchMethodError. Add
simple unit tests. Implement RegisterNatives.
Fix bug that CreateArgArray doesn't pass the last argument correctly.
Change-Id: I5b87ffdbb72a7ef75147a002560b8c47e7af35f4
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index 75992ea..e23b162 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -89,7 +89,7 @@
size_t num_bytes = method->NumArgArrayBytes();
scoped_array<byte> arg_array(new byte[num_bytes]);
const StringPiece& shorty = method->GetShorty();
- for (int i = 1, offset = 0; i < shorty.size() - 1; ++i) {
+ for (int i = 1, offset = 0; i < shorty.size(); ++i) {
switch (shorty[i]) {
case 'Z':
case 'B':
@@ -127,7 +127,7 @@
size_t num_bytes = method->NumArgArrayBytes();
scoped_array<byte> arg_array(new byte[num_bytes]);
const StringPiece& shorty = method->GetShorty();
- for (int i = 1, offset = 0; i < shorty.size() - 1; ++i) {
+ for (int i = 1, offset = 0; i < shorty.size(); ++i) {
switch (shorty[i]) {
case 'Z':
case 'B':
@@ -284,8 +284,23 @@
jthrowable ExceptionOccurred(JNIEnv* env) {
ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- return NULL;
+ Object* exception = ts.Self()->GetException();
+ if (exception == NULL) {
+ return NULL;
+ } else {
+ // TODO: if adding a local reference failing causes the VM to abort
+ // then the following check will never occur.
+ jthrowable localException = AddLocalReference<jthrowable>(ts, exception);
+ if (localException == NULL) {
+ // We were unable to add a new local reference, and threw a new
+ // exception. We can't return "exception", because it's not a
+ // local reference. So we have to return NULL, indicating that
+ // there was no exception, even though it's pretty much raining
+ // exceptions in here.
+ LOG(WARNING) << "JNI WARNING: addLocal/exception combo";
+ }
+ return localException;
+ }
}
void ExceptionDescribe(JNIEnv* env) {
@@ -381,10 +396,19 @@
return NULL;
}
-jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass clazz) {
+jboolean IsInstanceOf(JNIEnv* env, jobject jobj, jclass clazz) {
ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- return JNI_FALSE;
+ CHECK_NE(static_cast<jclass>(NULL), clazz);
+ if (jobj == NULL) {
+ // NB. JNI is different from regular Java instanceof in this respect
+ return JNI_TRUE;
+ } else {
+ // TODO: retrieve handle value for object
+ Object* obj = reinterpret_cast<Object*>(jobj);
+ // TODO: retrieve handle value for class
+ Class* klass = reinterpret_cast<Class*>(clazz);
+ return Object::InstanceOf(obj, klass) ? JNI_TRUE : JNI_FALSE;
+ }
}
jmethodID GetMethodID(JNIEnv* env,
@@ -401,17 +425,31 @@
// private methods and constructors.
method = klass->FindDeclaredDirectMethod(name, sig);
}
- if (method == NULL || method->IsStatic()) {
- LG << "NoSuchMethodError"; // TODO: throw NoSuchMethodError
+ if (method == NULL) {
+ Thread* self = Thread::Current();
+ std::string class_name = klass->GetDescriptor().ToString();
+ // TODO: pretty print method names through a single routine
+ self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+ "no method \"%s.%s%s\"",
+ class_name.c_str(), name, sig);
return NULL;
- }
- // TODO: create a JNI weak global reference for method
- bool success = EnsureInvokeStub(method);
- if (!success) {
- // TODO: throw OutOfMemoryException
+ } else if (method->IsStatic()) {
+ Thread* self = Thread::Current();
+ std::string class_name = klass->GetDescriptor().ToString();
+ // TODO: pretty print method names through a single routine
+ self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+ "method \"%s.%s%s\" is static",
+ class_name.c_str(), name, sig);
return NULL;
+ } else {
+ // TODO: create a JNI weak global reference for method
+ bool success = EnsureInvokeStub(method);
+ if (!success) {
+ // TODO: throw OutOfMemoryException
+ return NULL;
+ }
+ return reinterpret_cast<jmethodID>(method);
}
- return reinterpret_cast<jmethodID>(method);
}
jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
@@ -933,17 +971,33 @@
// TODO: initialize the class
}
Method* method = klass->FindDirectMethod(name, sig);
- if (method == NULL || !method->IsStatic()) {
- LG << "NoSuchMethodError"; // TODO: throw NoSuchMethodError
+ if (method == NULL) {
+ Thread* self = Thread::Current();
+ std::string class_name = klass->GetDescriptor().ToString();
+ // TODO: pretty print method names through a single routine
+ // TODO: may want to FindVirtualMethod to give more informative error
+ // message here
+ self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+ "no method \"%s.%s%s\"",
+ class_name.c_str(), name, sig);
return NULL;
- }
- // TODO: create a JNI weak global reference for method
- bool success = EnsureInvokeStub(method);
- if (!success) {
- // TODO: throw OutOfMemoryException
+ } else if (!method->IsStatic()) {
+ Thread* self = Thread::Current();
+ std::string class_name = klass->GetDescriptor().ToString();
+ // TODO: pretty print method names through a single routine
+ self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+ "method \"%s.%s%s\" is not static",
+ class_name.c_str(), name, sig);
return NULL;
+ } else {
+ // TODO: create a JNI weak global reference for method
+ bool success = EnsureInvokeStub(method);
+ if (!success) {
+ // TODO: throw OutOfMemoryException
+ return NULL;
+ }
+ return reinterpret_cast<jmethodID>(method);
}
- return reinterpret_cast<jmethodID>(method);
}
jobject CallStaticObjectMethod(JNIEnv* env,
@@ -1597,8 +1651,35 @@
jint RegisterNatives(JNIEnv* env,
jclass clazz, const JNINativeMethod* methods, jint nMethods) {
ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- return 0;
+ // TODO: retrieve handle value for class
+ Class* klass = reinterpret_cast<Class*>(clazz);
+ for(int i = 0; i < nMethods; i++) {
+ const char* name = methods[i].name;
+ const char* sig = methods[i].signature;
+ Method* method = klass->FindDirectMethod(name, sig);
+ if (method == NULL) {
+ method = klass->FindVirtualMethod(name, sig);
+ }
+ if (method == NULL) {
+ Thread* self = Thread::Current();
+ std::string class_name = klass->GetDescriptor().ToString();
+ // TODO: pretty print method names through a single routine
+ self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+ "no method \"%s.%s%s\"",
+ class_name.c_str(), name, sig);
+ return JNI_ERR;
+ } else if (!method->IsNative()) {
+ Thread* self = Thread::Current();
+ std::string class_name = klass->GetDescriptor().ToString();
+ // TODO: pretty print method names through a single routine
+ self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+ "method \"%s.%s%s\" is not native",
+ class_name.c_str(), name, sig);
+ return JNI_ERR;
+ }
+ method->RegisterNative(methods[i].fnPtr);
+ }
+ return JNI_OK;
}
jint UnregisterNatives(JNIEnv* env, jclass clazz) {
diff --git a/src/jni_internal_test.cc b/src/jni_internal_test.cc
index 569dfda..04b3225 100644
--- a/src/jni_internal_test.cc
+++ b/src/jni_internal_test.cc
@@ -57,6 +57,117 @@
EXPECT_CLASS_NOT_FOUND("K");
}
+TEST_F(JniInternalTest, GetMethodID) {
+ jclass jlobject = env_->FindClass("java/lang/Object");
+ jclass jlstring = env_->FindClass("java/lang/String");
+ jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
+
+ // Sanity check that no exceptions are pending
+ EXPECT_FALSE(env_->ExceptionCheck());
+
+ // Check that java.lang.Object.foo() doesn't exist and NoSuchMethodError is
+ // a pending exception
+ jmethodID method = env_->GetMethodID(jlobject, "foo", "()V");
+ EXPECT_EQ(static_cast<jmethodID>(NULL), method);
+ EXPECT_TRUE(env_->ExceptionCheck());
+ jthrowable exception = env_->ExceptionOccurred();
+ EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+ EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+ env_->ExceptionClear();
+
+ // Check that java.lang.Object.equals() does exist
+#if defined(__arm__)
+ method = env_->GetMethodID(jlobject, "equals", "(Ljava/lang/Object;)Z");
+ EXPECT_NE(static_cast<jmethodID>(NULL), method);
+ EXPECT_FALSE(env_->ExceptionCheck());
+#endif
+
+ // Check that GetMethodID for java.lang.String.valueOf(int) fails as the
+ // method is static
+ method = env_->GetMethodID(jlstring, "valueOf", "(I)Ljava/lang/String;");
+ EXPECT_EQ(static_cast<jmethodID>(NULL), method);
+ EXPECT_TRUE(env_->ExceptionCheck());
+ exception = env_->ExceptionOccurred();
+ EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+ EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+ env_->ExceptionClear();
+}
+
+TEST_F(JniInternalTest, GetStaticMethodID) {
+ jclass jlobject = env_->FindClass("java/lang/Object");
+ jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
+
+ // Sanity check that no exceptions are pending
+ EXPECT_FALSE(env_->ExceptionCheck());
+
+ // Check that java.lang.Object.foo() doesn't exist and NoSuchMethodError is
+ // a pending exception
+ jmethodID method = env_->GetStaticMethodID(jlobject, "foo", "()V");
+ EXPECT_EQ(static_cast<jmethodID>(NULL), method);
+ EXPECT_TRUE(env_->ExceptionCheck());
+ jthrowable exception = env_->ExceptionOccurred();
+ EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+ EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+ env_->ExceptionClear();
+
+ // Check that GetStaticMethodID for java.lang.Object.equals(Object) fails as
+ // the method is not static
+ method = env_->GetStaticMethodID(jlobject, "equals", "(Ljava/lang/Object;)Z");
+ EXPECT_EQ(static_cast<jmethodID>(NULL), method);
+ EXPECT_TRUE(env_->ExceptionCheck());
+ exception = env_->ExceptionOccurred();
+ EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+ EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+ env_->ExceptionClear();
+
+ // Check that java.lang.String.valueOf(int) does exist
+#if defined(__arm__)
+ jclass jlstring = env_->FindClass("java/lang/String");
+ method = env_->GetStaticMethodID(jlstring, "valueOf",
+ "(I)Ljava/lang/String;");
+ EXPECT_NE(static_cast<jmethodID>(NULL), method);
+ EXPECT_FALSE(env_->ExceptionCheck());
+#endif
+}
+
+TEST_F(JniInternalTest, RegisterNatives) {
+ jclass jlobject = env_->FindClass("java/lang/Object");
+ jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
+
+ // Sanity check that no exceptions are pending
+ EXPECT_FALSE(env_->ExceptionCheck());
+
+ // Check that registering to a non-existent java.lang.Object.foo() causes a
+ // NoSuchMethodError
+ {
+ JNINativeMethod methods[] = {{"foo", "()V", NULL}};
+ env_->RegisterNatives(jlobject, methods, 1);
+ }
+ EXPECT_TRUE(env_->ExceptionCheck());
+ jthrowable exception = env_->ExceptionOccurred();
+ EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+ EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+ env_->ExceptionClear();
+
+ // Check that registering non-native methods causes a NoSuchMethodError
+ {
+ JNINativeMethod methods[] = {{"equals", "(Ljava/lang/Object;)Z", NULL}};
+ env_->RegisterNatives(jlobject, methods, 1);
+ }
+ EXPECT_TRUE(env_->ExceptionCheck());
+ exception = env_->ExceptionOccurred();
+ EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+ EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+ env_->ExceptionClear();
+
+ // Check that registering native methods is successful
+ {
+ JNINativeMethod methods[] = {{"hashCode", "()I", NULL}};
+ env_->RegisterNatives(jlobject, methods, 1);
+ }
+ EXPECT_FALSE(env_->ExceptionCheck());
+}
+
TEST_F(JniInternalTest, NewPrimitiveArray) {
// TODO: death tests for negative array sizes.