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