JNI: Fix failure to unlock for pending exception.
And add a regression test. This was broken by
https://android-review.googlesource.com/1898923
but it was not caught by any direct tests.
Test: Additional test in JniCompilerTest.
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Bug: 172332525
Bug: 208831945
Change-Id: I41d4999bbf43f8e58c88b87da47be6f7409d9ce1
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc
index 8de5c9c..abff4b6 100644
--- a/compiler/jni/jni_compiler_test.cc
+++ b/compiler/jni/jni_compiler_test.cc
@@ -1268,6 +1268,12 @@
env->ThrowNew(c, "hello");
}
+void Java_MyClassNatives_synchronizedThrowException(JNIEnv* env, jobject) {
+ JniCompilerTest::AssertCallerObjectLocked(env);
+ jclass c = env->FindClass("java/lang/RuntimeException");
+ env->ThrowNew(c, "hello");
+}
+
void JniCompilerTest::ExceptionHandlingImpl() {
{
ASSERT_FALSE(runtime_->IsStarted());
@@ -1277,7 +1283,9 @@
// all compilation needs to happen before Runtime::Start
CompileForTestWithCurrentJni(class_loader_, false, "foo", "()V");
CompileForTestWithCurrentJni(class_loader_, false, "throwException", "()V");
- CompileForTestWithCurrentJni(class_loader_, false, "foo", "()V");
+ if (gCurrentJni == enum_cast<uint32_t>(JniKind::kNormal)) {
+ CompileForTestWithCurrentJni(class_loader_, false, "synchronizedThrowException", "()V");
+ }
}
// Start runtime to avoid re-initialization in SetupForTest.
Thread::Current()->TransitionFromSuspendedToRunnable();
@@ -1298,17 +1306,39 @@
CURRENT_JNI_WRAPPER(Java_MyClassNatives_throwException));
// Call Java_MyClassNatives_throwException (JNI method that throws exception)
env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_);
- EXPECT_EQ(1, gJava_MyClassNatives_foo_calls[gCurrentJni]);
EXPECT_TRUE(env_->ExceptionCheck() == JNI_TRUE);
ScopedLocalRef<jthrowable> exception(env_, env_->ExceptionOccurred());
env_->ExceptionClear();
EXPECT_TRUE(env_->IsInstanceOf(exception.get(), jlre.get()));
// Check a single call of a JNI method is ok
+ EXPECT_EQ(1, gJava_MyClassNatives_foo_calls[gCurrentJni]);
SetUpForTest(false, "foo", "()V", reinterpret_cast<void*>(&Java_MyClassNatives_foo));
env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_);
EXPECT_EQ(2, gJava_MyClassNatives_foo_calls[gCurrentJni]);
+ if (gCurrentJni == enum_cast<uint32_t>(JniKind::kNormal)) {
+ SetUpForTest(false, "synchronizedThrowException", "()V",
+ CURRENT_JNI_WRAPPER(Java_MyClassNatives_synchronizedThrowException));
+ LockWord lock_word = GetLockWord(jobj_);
+ ASSERT_EQ(lock_word.GetState(), LockWord::kUnlocked);
+ // Call Java_MyClassNatives_synchronizedThrowException (synchronized JNI method
+ // that throws exception) to check that we correctly unlock the object.
+ env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_);
+ EXPECT_TRUE(env_->ExceptionCheck() == JNI_TRUE);
+ ScopedLocalRef<jthrowable> exception2(env_, env_->ExceptionOccurred());
+ env_->ExceptionClear();
+ EXPECT_TRUE(env_->IsInstanceOf(exception2.get(), jlre.get()));
+ lock_word = GetLockWord(jobj_);
+ EXPECT_EQ(lock_word.GetState(), LockWord::kUnlocked);
+
+ // Check a single call of a JNI method is ok
+ EXPECT_EQ(2, gJava_MyClassNatives_foo_calls[gCurrentJni]);
+ SetUpForTest(false, "foo", "()V", reinterpret_cast<void*>(&Java_MyClassNatives_foo));
+ env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_);
+ EXPECT_EQ(3, gJava_MyClassNatives_foo_calls[gCurrentJni]);
+ }
+
gJava_MyClassNatives_foo_calls[gCurrentJni] = 0;
}