ART: Refactor run-test 913

Remove references to ART. Filter roots from the JIT. Canonicalize
some thread IDs. Move the test to its own thread, and filter
stack-locals of other threads.

These changes ensure that the test is less dependent on the main
thread and its environment, which is required to use it in CTS.

Also fix a reporting issue for roots.

Bug: 32072923
Test: art/test/testrunner/testrunner.py --host -t 913
Change-Id: I8480ba7751fb6420c256db87cba11b8a65e25ea5
diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc
index 49d9aca..7fc5104 100644
--- a/runtime/openjdkjvmti/ti_heap.cc
+++ b/runtime/openjdkjvmti/ti_heap.cc
@@ -888,8 +888,8 @@
       bool add_to_worklist = ReportRoot(root_obj, info);
       // We use visited_ to mark roots already so we do not need another set.
       if (visited_->find(root_obj) == visited_->end()) {
-        visited_->insert(root_obj);
         if (add_to_worklist) {
+          visited_->insert(root_obj);
           worklist_->push_back(root_obj);
         }
       }
diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt
index 2a183ee..46e8171 100644
--- a/test/913-heaps/expected.txt
+++ b/test/913-heaps/expected.txt
@@ -1,9 +1,12 @@
 ---
 true true
+root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
 0@0 --(array-element@0)--> 1@1000 [size=16, length=-1]
+0@0 --(array-element@0)--> 3000@0 [size=136, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
 1002@0 --(interface)--> 2001@0 [size=124, length=-1]
 1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
@@ -40,11 +43,14 @@
 ---
 root@root --(jni-global)--> 1@1000 [size=16, length=-1]
 root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1]
+root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
 root@root --(thread)--> 1@1000 [size=16, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
+0@0 --(array-element@0)--> 3000@0 [size=136, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
 1002@0 --(interface)--> 2001@0 [size=124, length=-1]
 1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
@@ -79,18 +85,21 @@
 5@1002 --(field@9)--> 1@1000 [size=16, length=-1]
 6@1000 --(class)--> 1000@0 [size=123, length=-1]
 ---
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
 ---
 3@1001 --(class)--> 1001@0 [size=123, length=-1]
 ---
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
 ---
 3@1001 --(class)--> 1001@0 [size=123, length=-1]
 ---
+root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
 0@0 --(array-element@0)--> 1@1000 [size=16, length=-1]
+0@0 --(array-element@0)--> 3000@0 [size=136, length=-1]
 ---
 1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
 3@1001 --(class)--> 1001@0 [size=123, length=-1]
@@ -99,11 +108,14 @@
 ---
 root@root --(jni-global)--> 1@1000 [size=16, length=-1]
 root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1]
+root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
 root@root --(thread)--> 1@1000 [size=16, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
+0@0 --(array-element@0)--> 3000@0 [size=136, length=-1]
 ---
 1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
 3@1001 --(class)--> 1001@0 [size=123, length=-1]
@@ -182,10 +194,13 @@
 ---
 ---
 ---- untagged objects
+root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
 0@0 --(array-element@0)--> 1@1000 [size=16, length=-1]
+0@0 --(array-element@0)--> 3000@0 [size=136, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
 1002@0 --(interface)--> 2001@0 [size=124, length=-1]
 1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
@@ -222,11 +237,14 @@
 ---
 root@root --(jni-global)--> 1@1000 [size=16, length=-1]
 root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1]
+root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
 root@root --(thread)--> 1@1000 [size=16, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
+0@0 --(array-element@0)--> 3000@0 [size=136, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
 1002@0 --(interface)--> 2001@0 [size=124, length=-1]
 1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
@@ -262,8 +280,11 @@
 6@1000 --(class)--> 1000@0 [size=123, length=-1]
 ---
 ---- tagged classes
-root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
+0@0 --(array-element@0)--> 3000@0 [size=136, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
 1002@0 --(interface)--> 2001@0 [size=124, length=-1]
 1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
@@ -286,7 +307,10 @@
 5@1002 --(class)--> 1002@0 [size=123, length=-1]
 6@1000 --(class)--> 1000@0 [size=123, length=-1]
 ---
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
+0@0 --(array-element@0)--> 3000@0 [size=136, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
 1002@0 --(interface)--> 2001@0 [size=124, length=-1]
 1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 6a06b29..a1b76d5 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -19,40 +19,35 @@
 #include <string.h>
 
 #include <iostream>
+#include <sstream>
 #include <vector>
 
 #include "android-base/macros.h"
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
 
-#include "jit/jit.h"
 #include "jni.h"
-#include "native_stack_dump.h"
 #include "jvmti.h"
-#include "runtime.h"
-#include "scoped_thread_state_change-inl.h"
-#include "thread-inl.h"
-#include "thread_list.h"
 
 // Test infrastructure
 #include "jni_helper.h"
 #include "jvmti_helper.h"
 #include "test_env.h"
+#include "ti_utf.h"
 
 namespace art {
 namespace Test913Heaps {
 
 using android::base::StringPrintf;
 
+#define FINAL final
+#define OVERRIDE override
+#define UNREACHABLE  __builtin_unreachable
+
 extern "C" JNIEXPORT void JNICALL Java_art_Test913_forceGarbageCollection(
-    JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED) {
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
   jvmtiError ret = jvmti_env->ForceGarbageCollection();
-  if (ret != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(ret, &err);
-    printf("Error forcing a garbage collection: %s\n", err);
-    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
-  }
+  JvmtiErrorToException(env, jvmti_env, ret);
 }
 
 class IterationConfig {
@@ -92,7 +87,8 @@
                         user_data);
 }
 
-static bool Run(jint heap_filter,
+static bool Run(JNIEnv* env,
+                jint heap_filter,
                 jclass klass_filter,
                 jobject initial_object,
                 IterationConfig* config) {
@@ -105,14 +101,7 @@
                                                initial_object,
                                                &callbacks,
                                                config);
-  if (ret != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(ret, &err);
-    printf("Failure running FollowReferences: %s\n", err);
-    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
-    return false;
-  }
-  return true;
+  return !JvmtiErrorToException(env, jvmti_env, ret);
 }
 
 extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test913_followReferences(
@@ -142,6 +131,23 @@
                 jint length,
                 void* user_data ATTRIBUTE_UNUSED) OVERRIDE {
       jlong tag = *tag_ptr;
+
+      // Ignore any jni-global roots with untagged classes. These can be from the environment,
+      // or the JIT.
+      if (reference_kind == JVMTI_HEAP_REFERENCE_JNI_GLOBAL && class_tag == 0) {
+        return 0;
+      }
+      // Ignore classes (1000-1002@0) for thread objects. These can be held by the JIT.
+      if (reference_kind == JVMTI_HEAP_REFERENCE_THREAD && class_tag == 0 &&
+              (1000 <= *tag_ptr &&  *tag_ptr <= 1002)) {
+        return 0;
+      }
+      // Ignore stack-locals of untagged threads. That is the environment.
+      if (reference_kind == JVMTI_HEAP_REFERENCE_STACK_LOCAL &&
+          reference_info->stack_local.thread_tag != 3000) {
+        return 0;
+      }
+
       // Only check tagged objects.
       if (tag == 0) {
         return JVMTI_VISIT_OBJECTS;
@@ -201,10 +207,6 @@
                                   reference_info,
                                   adapted_size,
                                   length));
-
-      if (reference_kind == JVMTI_HEAP_REFERENCE_THREAD && *tag_ptr == 1000) {
-        DumpStacks();
-      }
     }
 
     std::vector<std::string> GetLines() const {
@@ -259,9 +261,15 @@
         if (info_.jni_local.method != nullptr) {
           jvmti_env->GetMethodName(info_.jni_local.method, &name, nullptr, nullptr);
         }
+        // Normalize the thread id, as this depends on the number of other threads
+        // and which thread is running the test. Should be:
+        //   jlong thread_id = info_.jni_local.thread_id;
+        // TODO: A pre-pass before the test should be able fetch this number, so it can
+        //       be compared explicitly.
+        jlong thread_id = 1;
         std::string ret = StringPrintf("jni-local[id=%" PRId64 ",tag=%" PRId64 ",depth=%d,"
                                        "method=%s]",
-                                       info_.jni_local.thread_id,
+                                       thread_id,
                                        info_.jni_local.thread_tag,
                                        info_.jni_local.depth,
                                        name == nullptr ? "<null>" : name);
@@ -284,14 +292,8 @@
                         jlong size,
                         jint length,
                         const jvmtiHeapReferenceInfo* reference_info)
-          REQUIRES_SHARED(Locks::mutator_lock_)
           : Elem(referrer, referree, size, length) {
         memcpy(&info_, reference_info, sizeof(jvmtiHeapReferenceInfo));
-        // Debug stack trace for failure condition. Remove when done.
-        if (info_.stack_local.depth == 3 && info_.stack_local.slot == 13) {
-          DumpNativeStack(std::cerr, GetTid());
-          Thread::Current()->DumpJavaStack(std::cerr, false, false);
-        }
       }
 
      protected:
@@ -300,9 +302,15 @@
         if (info_.stack_local.method != nullptr) {
           jvmti_env->GetMethodName(info_.stack_local.method, &name, nullptr, nullptr);
         }
+        // Normalize the thread id, as this depends on the number of other threads
+        // and which thread is running the test. Should be:
+        //   jlong thread_id = info_.stack_local.thread_id;
+        // TODO: A pre-pass before the test should be able fetch this number, so it can
+        //       be compared explicitly.
+        jlong thread_id = 1;
         std::string ret = StringPrintf("stack-local[id=%" PRId64 ",tag=%" PRId64 ",depth=%d,"
                                        "method=%s,vreg=%d,location=% " PRId64 "]",
-                                       info_.stack_local.thread_id,
+                                       thread_id,
                                        info_.stack_local.thread_tag,
                                        info_.stack_local.depth,
                                        name == nullptr ? "<null>" : name,
@@ -361,7 +369,13 @@
                                                         tmp));
         }
         case JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT: {
-          std::string tmp = StringPrintf("array-element@%d", reference_info->array.index);
+          jint index = reference_info->array.index;
+          // Normalize if it's "0@0" -> "3000@1".
+          // TODO: A pre-pass could probably give us this index to check explicitly.
+          if (referrer == "0@0" && referree == "3000@0") {
+            index = 0;
+          }
+          std::string tmp = StringPrintf("array-element@%d", index);
           return std::unique_ptr<Elem>(new StringElement(referrer,
                                                          referree,
                                                          size,
@@ -459,16 +473,6 @@
       UNREACHABLE();
     }
 
-    static void DumpStacks() NO_THREAD_SAFETY_ANALYSIS {
-      auto dump_function = [](art::Thread* t, void* data ATTRIBUTE_UNUSED) {
-        std::string name;
-        t->GetThreadName(name);
-        LOG(ERROR) << name;
-        art::DumpNativeStack(LOG_STREAM(ERROR), t->GetTid());
-      };
-      art::Runtime::Current()->GetThreadList()->ForEach(dump_function, nullptr);
-    }
-
     jint counter_;
     const jint stop_after_;
     const jint follow_set_;
@@ -476,8 +480,6 @@
     std::vector<std::unique_ptr<Elem>> lines_;
   };
 
-  jit::ScopedJitSuspend sjs;  // Wait to avoid JIT influence (e.g., JNI globals).
-
   // If jniRef isn't null, add a local and a global ref.
   ScopedLocalRef<jobject> jni_local_ref(env, nullptr);
   jobject jni_global_ref = nullptr;
@@ -487,7 +489,9 @@
   }
 
   PrintIterationConfig config(stop_after, follow_set);
-  Run(heap_filter, klass_filter, initial_object, &config);
+  if (!Run(env, heap_filter, klass_filter, initial_object, &config)) {
+    return nullptr;
+  }
 
   std::vector<std::string> lines = config.GetLines();
   jobjectArray ret = CreateObjectArray(env,
@@ -528,10 +532,10 @@
                                             void* user_data) {
       FindStringCallbacks* p = reinterpret_cast<FindStringCallbacks*>(user_data);
       if (*tag_ptr != 0) {
-        size_t utf_byte_count = CountUtf8Bytes(value, value_length);
+        size_t utf_byte_count = ti::CountUtf8Bytes(value, value_length);
         std::unique_ptr<char[]> mod_utf(new char[utf_byte_count + 1]);
         memset(mod_utf.get(), 0, utf_byte_count + 1);
-        ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
+        ti::ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
         p->data.push_back(android::base::StringPrintf("%" PRId64 "@%" PRId64 " (%" PRId64 ", '%s')",
                                                       *tag_ptr,
                                                       class_tag,
diff --git a/test/913-heaps/src/art/Test913.java b/test/913-heaps/src/art/Test913.java
index c54ecb0..2eb05ef 100644
--- a/test/913-heaps/src/art/Test913.java
+++ b/test/913-heaps/src/art/Test913.java
@@ -21,12 +21,34 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.concurrent.CountDownLatch;
 
 public class Test913 {
   public static void run() throws Exception {
     Main.bindAgentJNIForClass(Test913.class);
 
     doTest();
+
+    // Use a countdown latch for synchronization, as join() will introduce more roots.
+    final CountDownLatch cdl1 = new CountDownLatch(1);
+
+    // Run the follow-references tests on a dedicated thread so we know the specific Thread type.
+    Thread t = new Thread() {
+      @Override
+      public void run() {
+        try {
+          Test913.runFollowReferences();
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+        cdl1.countDown();
+      }
+    };
+    t.start();
+    cdl1.await();
+  }
+
+  public static void runFollowReferences() throws Exception {
     new TestConfig().doFollowReferencesTest();
 
     Runtime.getRuntime().gc();
@@ -481,7 +503,8 @@
           if (currentHead == null) {
             currentHead = referrer;
           } else {
-            if (!currentHead.equals(referrer)) {
+            // Ignore 0@0, as it can happen at any time (as it stands for all other objects).
+            if (!currentHead.equals(referrer) && !referrer.equals("0@0")) {
               completedReferrers.add(currentHead);
               currentHead = referrer;
               if (completedReferrers.contains(referrer)) {
diff --git a/test/Android.bp b/test/Android.bp
index 538fac4..033c4fc 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -260,6 +260,7 @@
         "908-gc-start-finish/gc_callbacks.cc",
         "910-methods/methods.cc",
         "911-get-stack-trace/stack_trace.cc",
+        "913-heaps/heaps.cc",
         "918-fields/fields.cc",
         "920-objects/objects.cc",
         "922-properties/properties.cc",
@@ -293,7 +294,6 @@
         "901-hello-ti-agent/basics.cc",
         "909-attach-agent/attach.cc",
         "912-classes/classes.cc",
-        "913-heaps/heaps.cc",
         "936-search-onload/search_onload.cc",
         "944-transform-classloaders/classloader.cc",
         "945-obsolete-native/obsolete_native.cc",