ART: Add reference following code

Add FollowReferences support. This is a work in progress.
Specific support for the metadata will be added iteratively.

Add a basic test to 913-heaps.

Bug: 31385354
Test: m test-art-host
Change-Id: I832b05a7397dc96cd9a868020dd7029255f6de87
diff --git a/test/913-heaps/src/Main.java b/test/913-heaps/src/Main.java
index 4d77a48..f463429 100644
--- a/test/913-heaps/src/Main.java
+++ b/test/913-heaps/src/Main.java
@@ -15,12 +15,14 @@
  */
 
 import java.util.ArrayList;
+import java.util.Collections;
 
 public class Main {
   public static void main(String[] args) throws Exception {
     System.loadLibrary(args[1]);
 
     doTest();
+    doFollowReferencesTest();
   }
 
   public static void doTest() throws Exception {
@@ -43,10 +45,161 @@
   }
 
   private static void printStats() {
-      System.out.println("---");
-      int s = getGcStarts();
-      int f = getGcFinishes();
-      System.out.println((s > 0) + " " + (f > 0));
+    System.out.println("---");
+    int s = getGcStarts();
+    int f = getGcFinishes();
+    System.out.println((s > 0) + " " + (f > 0));
+  }
+
+  public static void doFollowReferencesTest() throws Exception {
+    // Force GCs to clean up dirt.
+    Runtime.getRuntime().gc();
+    Runtime.getRuntime().gc();
+
+    tagClasses();
+    setTag(Thread.currentThread(), 3000);
+
+    {
+      ArrayList<Object> tmpStorage = new ArrayList<>();
+      doFollowReferencesTestNonRoot(tmpStorage);
+      tmpStorage = null;
+    }
+
+    // Force GCs to clean up dirt.
+    Runtime.getRuntime().gc();
+    Runtime.getRuntime().gc();
+
+    doFollowReferencesTestRoot();
+
+    // Force GCs to clean up dirt.
+    Runtime.getRuntime().gc();
+    Runtime.getRuntime().gc();
+  }
+
+  private static void doFollowReferencesTestNonRoot(ArrayList<Object> tmpStorage) {
+    A a = createTree();
+    tmpStorage.add(a);
+    doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, null);
+    doFollowReferencesTestImpl(a, Integer.MAX_VALUE, -1, null);
+    tmpStorage.clear();
+  }
+
+  private static void doFollowReferencesTestRoot() {
+    A a = createTree();
+    doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, a);
+    doFollowReferencesTestImpl(a, Integer.MAX_VALUE, -1, a);
+  }
+
+  private static void doFollowReferencesTestImpl(A root, int stopAfter, int followSet,
+      Object asRoot) {
+    String[] lines =
+        followReferences(0, null, root == null ? null : root.foo, stopAfter, followSet, asRoot);
+    // Note: sort the roots, as stack locals visit order isn't defined, so may depend on compiled
+    //       code. Do not sort non-roots, as the order here needs to be verified (elements are
+    //       finished before a reference is followed). The test setup (and root visit order)
+    //       luckily ensures that this is deterministic.
+
+    int i = 0;
+    ArrayList<String> rootLines = new ArrayList<>();
+    while (i < lines.length) {
+      if (lines[i].startsWith("root")) {
+        rootLines.add(lines[i]);
+      } else {
+        break;
+      }
+      i++;
+    }
+    Collections.sort(rootLines);
+    for (String l : rootLines) {
+      System.out.println(l);
+    }
+
+    // Print the non-root lines in order.
+    while (i < lines.length) {
+      System.out.println(lines[i]);
+      i++;
+    }
+
+    System.out.println("---");
+
+    // TODO: Test filters.
+  }
+
+  private static void tagClasses() {
+    setTag(A.class, 1000);
+    setTag(B.class, 1001);
+    setTag(C.class, 1002);
+    setTag(I1.class, 2000);
+    setTag(I2.class, 2001);
+  }
+
+  private static A createTree() {
+    A root = new A();
+    setTag(root, 1);
+
+    A foo = new A();
+    setTag(foo, 2);
+    root.foo = foo;
+
+    B foo2 = new B();
+    setTag(foo2, 3);
+    root.foo2 = foo2;
+
+    A bar = new A();
+    setTag(bar, 4);
+    foo2.bar = bar;
+
+    C bar2 = new C();
+    setTag(bar2, 5);
+    foo2.bar2 = bar2;
+
+    A baz = new A();
+    setTag(baz, 6);
+    bar2.baz = baz;
+    bar2.baz2 = root;
+
+    return root;
+  }
+
+  public static class A {
+    public A foo;
+    public A foo2;
+
+    public A() {}
+    public A(A a, A b) {
+      foo = a;
+      foo2 = b;
+    }
+  }
+
+  public static class B extends A {
+    public A bar;
+    public A bar2;
+
+    public B() {}
+    public B(A a, A b) {
+      bar = a;
+      bar2 = b;
+    }
+  }
+
+  public static interface I1 {
+    public final static int i1Field = 1;
+  }
+
+  public static interface I2 extends I1 {
+    public final static int i2Field = 2;
+  }
+
+  public static class C extends B implements I2 {
+    public A baz;
+    public A baz2;
+
+    public C() {}
+    public C(A a, A b) {
+      baz = a;
+      baz2 = b;
+    }
   }
 
   private static native void setupGcCallback();
@@ -54,4 +207,10 @@
   private static native int getGcStarts();
   private static native int getGcFinishes();
   private static native void forceGarbageCollection();
+
+  private static native void setTag(Object o, long tag);
+  private static native long getTag(Object o);
+
+  private static native String[] followReferences(int heapFilter, Class<?> klassFilter,
+      Object initialObject, int stopAfter, int followSet, Object jniRef);
 }