Ensure GetThreadState only counts user-code suspensions

Previously we would set the JVMTI_THREAD_STATE_SUSPENDED bit
regardless of the actual cause of suspension. We changed it to follow
the spec so that only user-code suspensions are counted.

Bug: 63743122
Bug: 34415266
Test: ./test.py --host -j50
Change-Id: I65d337cbfed8768f18005721d41e646a815d1ef7
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index d1cee9a..fe0e3bb 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -300,35 +300,51 @@
   return ERR(NONE);
 }
 
-// Return the thread's (or current thread, if null) thread state. Return kStarting in case
-// there's no native counterpart (thread hasn't been started, yet, or is dead).
-static art::ThreadState GetNativeThreadState(jthread thread,
-                                             const art::ScopedObjectAccessAlreadyRunnable& soa,
-                                             art::Thread** native_thread)
-    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+struct InternalThreadState {
+  art::Thread* native_thread;
+  art::ThreadState art_state;
+  int thread_user_code_suspend_count;
+};
+
+// Return the thread's (or current thread, if null) thread state.
+static InternalThreadState GetNativeThreadState(jthread thread,
+                                                const art::ScopedObjectAccessAlreadyRunnable& soa)
+    REQUIRES_SHARED(art::Locks::mutator_lock_)
+    REQUIRES(art::Locks::user_code_suspension_lock_) {
   art::Thread* self = nullptr;
-  art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+  art::MutexLock tll_mu(soa.Self(), *art::Locks::thread_list_lock_);
   if (thread == nullptr) {
     self = art::Thread::Current();
   } else {
     self = art::Thread::FromManagedThread(soa, thread);
   }
-  *native_thread = self;
+  InternalThreadState thread_state = {};
+  art::MutexLock tscl_mu(soa.Self(), *art::Locks::thread_suspend_count_lock_);
+  thread_state.native_thread = self;
   if (self == nullptr || self->IsStillStarting()) {
-    return art::ThreadState::kStarting;
+    thread_state.art_state = art::ThreadState::kStarting;
+    thread_state.thread_user_code_suspend_count = 0;
+  } else {
+    thread_state.art_state = self->GetState();
+    thread_state.thread_user_code_suspend_count = self->GetUserCodeSuspendCount();
   }
-  return self->GetState();
+  return thread_state;
 }
 
-static jint GetJvmtiThreadStateFromInternal(art::ThreadState internal_thread_state) {
+static jint GetJvmtiThreadStateFromInternal(const InternalThreadState& state) {
+  art::ThreadState internal_thread_state = state.art_state;
   jint jvmti_state = JVMTI_THREAD_STATE_ALIVE;
 
-  if (internal_thread_state == art::ThreadState::kSuspended) {
+  if (state.thread_user_code_suspend_count != 0) {
     jvmti_state |= JVMTI_THREAD_STATE_SUSPENDED;
     // Note: We do not have data about the previous state. Otherwise we should load the previous
     //       state here.
   }
 
+  if (state.native_thread->IsInterrupted()) {
+    jvmti_state |= JVMTI_THREAD_STATE_INTERRUPTED;
+  }
+
   if (internal_thread_state == art::ThreadState::kNative) {
     jvmti_state |= JVMTI_THREAD_STATE_IN_NATIVE;
   }
@@ -365,8 +381,8 @@
   return jvmti_state;
 }
 
-static jint GetJavaStateFromInternal(art::ThreadState internal_thread_state) {
-  switch (internal_thread_state) {
+static jint GetJavaStateFromInternal(const InternalThreadState& state) {
+  switch (state.art_state) {
     case art::ThreadState::kTerminated:
       return JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED;
 
@@ -408,6 +424,14 @@
   UNREACHABLE();
 }
 
+// Suspends the current thread if it has any suspend requests on it.
+static void SuspendCheck(art::Thread* self)
+    REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_) {
+  art::ScopedObjectAccess soa(self);
+  // Really this is only needed if we are in FastJNI and actually have the mutator_lock_ already.
+  self->FullSuspendCheck();
+}
+
 jvmtiError ThreadUtil::GetThreadState(jvmtiEnv* env ATTRIBUTE_UNUSED,
                                       jthread thread,
                                       jint* thread_state_ptr) {
@@ -415,16 +439,35 @@
     return ERR(NULL_POINTER);
   }
 
-  art::ScopedObjectAccess soa(art::Thread::Current());
-  art::Thread* native_thread = nullptr;
-  art::ThreadState internal_thread_state = GetNativeThreadState(thread, soa, &native_thread);
+  art::Thread* self = art::Thread::Current();
+  InternalThreadState state = {};
+  // Loop since we need to bail out and try again if we would end up getting suspended while holding
+  // the user_code_suspension_lock_ due to a SuspendReason::kForUserCode. In this situation we
+  // release the lock, wait to get resumed and try again.
+  do {
+    SuspendCheck(self);
+    art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_);
+    {
+      art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_);
+      if (self->GetUserCodeSuspendCount() != 0) {
+        // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_
+        // by a user-code suspension. We retry and do another SuspendCheck to clear this.
+        continue;
+      }
+    }
+    art::ScopedObjectAccess soa(self);
+    state = GetNativeThreadState(thread, soa);
+    break;
+  } while (true);
 
-  if (internal_thread_state == art::ThreadState::kStarting) {
+  if (state.art_state == art::ThreadState::kStarting) {
     if (thread == nullptr) {
       // No native thread, and no Java thread? We must be starting up. Report as wrong phase.
       return ERR(WRONG_PHASE);
     }
 
+    art::ScopedObjectAccess soa(self);
+
     // Need to read the Java "started" field to know whether this is starting or terminated.
     art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread);
     art::ObjPtr<art::mirror::Class> klass = peer->GetClass();
@@ -437,21 +480,16 @@
     *thread_state_ptr = started ? kTerminatedState : kStartedState;
     return ERR(NONE);
   }
-  DCHECK(native_thread != nullptr);
+  DCHECK(state.native_thread != nullptr);
 
   // Translate internal thread state to JVMTI and Java state.
-  jint jvmti_state = GetJvmtiThreadStateFromInternal(internal_thread_state);
-  if (native_thread->IsInterrupted()) {
-    jvmti_state |= JVMTI_THREAD_STATE_INTERRUPTED;
-  }
-  if (native_thread->IsSuspended()) {
-    jvmti_state |= JVMTI_THREAD_STATE_SUSPENDED;
-  }
+  jint jvmti_state = GetJvmtiThreadStateFromInternal(state);
 
   // Java state is derived from nativeGetState.
-  // Note: Our implementation assigns "runnable" to suspended. As such, we will have slightly
-  //       different mask. However, this is for consistency with the Java view.
-  jint java_state = GetJavaStateFromInternal(internal_thread_state);
+  // TODO: Our implementation assigns "runnable" to suspended. As such, we will have slightly
+  //       different mask if a thread got suspended due to user-code. However, this is for
+  //       consistency with the Java view.
+  jint java_state = GetJavaStateFromInternal(state);
 
   *thread_state_ptr = jvmti_state | java_state;
 
@@ -661,14 +699,6 @@
   return ERR(NONE);
 }
 
-// Suspends the current thread if it has any suspend requests on it.
-static void SuspendCheck(art::Thread* self)
-    REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_) {
-  art::ScopedObjectAccess soa(self);
-  // Really this is only needed if we are in FastJNI and actually have the mutator_lock_ already.
-  self->FullSuspendCheck();
-}
-
 jvmtiError ThreadUtil::SuspendOther(art::Thread* self,
                                     jthread target_jthread,
                                     art::Thread* target) {