Fix for Thread::QuickDeliverException.

We may have a switch interpreter frame, or the top of the call
stack as caller when calling QuickDeliverException.

Test: 844-exception

Change-Id: I07c2387e36d7cadb1dc4e778e569e662e8a08dea
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 5ce9d17..3369e5d 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -3941,30 +3941,35 @@
   if (Dbg::IsForcedInterpreterNeededForException(this) || force_deopt || IsForceInterpreter()) {
     NthCallerVisitor visitor(this, 0, false);
     visitor.WalkStack();
-    if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.GetOuterMethod(), visitor.caller_pc)) {
-      // method_type shouldn't matter due to exception handling.
-      const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
-      // Save the exception into the deoptimization context so it can be restored
-      // before entering the interpreter.
-      if (force_deopt) {
-        VLOG(deopt) << "Deopting " << cf->GetMethod()->PrettyMethod() << " for frame-pop";
-        DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
-        // Get rid of the exception since we are doing a framepop instead.
-        LOG(WARNING) << "Suppressing pending exception for retry-instruction/frame-pop: "
-                     << exception->Dump();
-        ClearException();
+    if (visitor.GetCurrentQuickFrame() != nullptr) {
+      if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.GetOuterMethod(), visitor.caller_pc)) {
+        // method_type shouldn't matter due to exception handling.
+        const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
+        // Save the exception into the deoptimization context so it can be restored
+        // before entering the interpreter.
+        if (force_deopt) {
+          VLOG(deopt) << "Deopting " << cf->GetMethod()->PrettyMethod() << " for frame-pop";
+          DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
+          // Get rid of the exception since we are doing a framepop instead.
+          LOG(WARNING) << "Suppressing pending exception for retry-instruction/frame-pop: "
+                       << exception->Dump();
+          ClearException();
+        }
+        PushDeoptimizationContext(
+            JValue(),
+            /* is_reference= */ false,
+            (force_deopt ? nullptr : exception),
+            /* from_code= */ false,
+            method_type);
+        artDeoptimize(this);
+        UNREACHABLE();
+      } else {
+        LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
+                     << visitor.caller->PrettyMethod();
       }
-      PushDeoptimizationContext(
-          JValue(),
-          /* is_reference= */ false,
-          (force_deopt ? nullptr : exception),
-          /* from_code= */ false,
-          method_type);
-      artDeoptimize(this);
-      UNREACHABLE();
-    } else if (visitor.caller != nullptr) {
-      LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
-                   << visitor.caller->PrettyMethod();
+    } else {
+      // This is either top of call stack, or shadow frame.
+      DCHECK(visitor.caller == nullptr || visitor.IsShadowFrame());
     }
   }
 
diff --git a/test/844-exception/expected-stderr.txt b/test/844-exception/expected-stderr.txt
new file mode 100644
index 0000000..9804bd0
--- /dev/null
+++ b/test/844-exception/expected-stderr.txt
@@ -0,0 +1,5 @@
+Exception in thread "Thread-1" java.lang.Error: 
+	at Main.callMethodThatThrows(Main.java:57)
+	at Main$Inner2.<clinit>(Main.java:38)
+	at Main$Inner.<clinit>(Main.java:32)
+	at Main$MyThread.run(Main.java:24)
diff --git a/test/844-exception/expected-stdout.txt b/test/844-exception/expected-stdout.txt
new file mode 100644
index 0000000..6a5618e
--- /dev/null
+++ b/test/844-exception/expected-stdout.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/844-exception/info.txt b/test/844-exception/info.txt
new file mode 100644
index 0000000..a825fbb
--- /dev/null
+++ b/test/844-exception/info.txt
@@ -0,0 +1,2 @@
+Regression test for Thread::QuickDeliverException which used to expect a quick
+frame to be the caller.
diff --git a/test/844-exception/src/Main.java b/test/844-exception/src/Main.java
new file mode 100644
index 0000000..79d61cc
--- /dev/null
+++ b/test/844-exception/src/Main.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  static Main empty;
+
+  static class MyThread extends Thread {
+    public void run() {
+      // This will throw at `callMethodThatThrows` and trigger deoptimization checks which we used
+      // to crash on.
+      new Inner();
+    }
+  }
+
+  public static class Inner {
+    // Have a <clinit> method invoke another <clinit> method to ensure we execute in the
+    // interpreter.
+    static {
+      new Inner2();
+    }
+  }
+
+  public static class Inner2 {
+    static {
+      Main.callMethodThatThrows();
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+    // Disables use of nterp.
+    Main.setAsyncExceptionsThrown();
+
+    // Execute the test in a different thread, to ensure we still
+    // return a 0 exit status.
+    Thread t = new MyThread();
+    t.start();
+    t.join();
+  }
+
+  public static void callMethodThatThrows() {
+    // Ensures we get deoptimization requests.
+    Main.forceInterpreterOnThread();
+    throw new Error("");
+  }
+
+  public static native void forceInterpreterOnThread();
+  public static native void setAsyncExceptionsThrown();
+
+}
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index a900714..e0e9641 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -451,4 +451,16 @@
   return soa.Decode<mirror::Class>(c)->IsObsoleteObject();
 }
 
+extern "C" JNIEXPORT void JNICALL Java_Main_forceInterpreterOnThread(JNIEnv* env,
+                                                                     jclass cls ATTRIBUTE_UNUSED) {
+  ScopedObjectAccess soa(env);
+  MutexLock thread_list_mu(soa.Self(), *Locks::thread_list_lock_);
+  soa.Self()->IncrementForceInterpreterCount();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_setAsyncExceptionsThrown(JNIEnv* env ATTRIBUTE_UNUSED,
+                                                                     jclass cls ATTRIBUTE_UNUSED) {
+  Runtime::Current()->SetAsyncExceptionsThrown();
+}
+
 }  // namespace art
diff --git a/test/knownfailures.json b/test/knownfailures.json
index c370aaa..5e40eed 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1164,6 +1164,7 @@
                   "833-background-verification",
                   "836-32768classes",
                   "837-deopt",
+                  "844-exception",
                   "999-redefine-hiddenapi",
                   "1000-non-moving-space-stress",
                   "1001-app-image-regions",