Add resize arrays JVMTI extension

As a prototype for more general object replacement functionality add a
new JVMTI extension that allows one to change the size of arrays. This
extension is 'com.android.art.heap.change_array_size'. As far as any
JVMTI agent, JNI or Java Language code can observer this extension
atomically replaces every reference (strong and weak, global and
local, etc.) with a newly allocated array with the same contents but a
different length. Internally a whole new array will be created then
the old array will have its contents (including lock-word) copied and
all references to the old array will be replaced with the new array.

Test: ./test.py --host
Bug: 134162467

Change-Id: I92a0beabb02e0c92c8c8f9639836014ff1266878
diff --git a/test/1974-resize-array/src/Main.java b/test/1974-resize-array/src/Main.java
new file mode 100644
index 0000000..3843973
--- /dev/null
+++ b/test/1974-resize-array/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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 {
+  public static void main(String[] args) throws Exception {
+    art.Test1974.run();
+  }
+}
diff --git a/test/1974-resize-array/src/art/Main.java b/test/1974-resize-array/src/art/Main.java
new file mode 120000
index 0000000..84ae4ac
--- /dev/null
+++ b/test/1974-resize-array/src/art/Main.java
@@ -0,0 +1 @@
+../../../jvmti-common/Main.java
\ No newline at end of file
diff --git a/test/1974-resize-array/src/art/Test1974.java b/test/1974-resize-array/src/art/Test1974.java
new file mode 100644
index 0000000..8460e82
--- /dev/null
+++ b/test/1974-resize-array/src/art/Test1974.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package art;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class Test1974 {
+
+  public static final boolean DEBUG = false;
+
+  public static int[] static_field = new int[] {1, 2, 3};
+  public static Object[] static_field_ref = new String[] {"a", "b", "c"};
+
+  public static final class InstanceClass {
+    public int[] instance_field = new int[] {1, 2, 3};
+    public Object[] self_ref;
+
+    public InstanceClass() {
+      self_ref = new Object[] {null, "A", "B", "C"};
+      self_ref[0] = self_ref;
+    }
+  }
+
+  static InstanceClass theInstanceClass;
+  static InstanceClass theOtherInstanceClass;
+
+  static {
+    theInstanceClass = new InstanceClass();
+    theOtherInstanceClass = new InstanceClass();
+    theOtherInstanceClass.instance_field = theInstanceClass.instance_field;
+    theOtherInstanceClass.self_ref = theInstanceClass.self_ref;
+  }
+
+  public static void DbgPrintln(String s) {
+    if (DEBUG) {
+      System.out.println(s);
+    }
+  }
+
+  public interface ThrowRunnable extends Runnable {
+    public default void run() {
+      try {
+        throwRun();
+      } catch (Exception e) {
+        throw new Error("Exception in runner!", e);
+      }
+    }
+
+    public void throwRun() throws Exception;
+  }
+
+  public static void runAsThread(ThrowRunnable r) throws Exception {
+    Thread t = new Thread(r);
+    t.start();
+    t.join();
+    System.out.println("");
+  }
+
+  public static void runInstance() {
+    System.out.println("Test instance");
+    DbgPrintln("Pre hash: " + theInstanceClass.instance_field.hashCode());
+    System.out.println(
+        "val is: " + Arrays.toString(theInstanceClass.instance_field) + " resize +3");
+    ResizeArray(() -> theInstanceClass.instance_field, theInstanceClass.instance_field.length + 5);
+    System.out.println("val is: " + Arrays.toString(theInstanceClass.instance_field));
+    DbgPrintln("Post hash: " + theInstanceClass.instance_field.hashCode());
+    System.out.println(
+        "Same value? " + (theInstanceClass.instance_field == theOtherInstanceClass.instance_field));
+  }
+
+  public static void runHashMap() {
+    System.out.println("Test HashMap");
+    HashMap<byte[], Comparable> map = new HashMap();
+    Comparable the_value = "THE VALUE";
+    Supplier<byte[]> get_the_value =
+        () ->
+            map.entrySet().stream()
+                .filter((x) -> x.getValue().equals(the_value))
+                .findFirst()
+                .get()
+                .getKey();
+    map.put(new byte[] {1, 2, 3, 4}, the_value);
+    map.put(new byte[] {1, 2, 3, 4}, "Other Value");
+    map.put(new byte[] {1, 4}, "Third value");
+    System.out.println("val is: " + Arrays.toString(get_the_value.get()) + " resize +3");
+    System.out.print("Map is: ");
+    map.entrySet().stream()
+        .sorted((x, y) -> x.getValue().compareTo(y.getValue()))
+        .forEach(
+            (e) -> {
+              System.out.print("(" + Arrays.toString(e.getKey()) + "->" + e.getValue() + "), ");
+            });
+    System.out.println();
+    ResizeArray(get_the_value, 7);
+    System.out.println("val is: " + Arrays.toString(get_the_value.get()));
+    System.out.print("Map is: ");
+    map.entrySet().stream()
+        .sorted((x, y) -> x.getValue().compareTo(y.getValue()))
+        .forEach(
+            (e) -> {
+              System.out.print("(" + Arrays.toString(e.getKey()) + "->" + e.getValue() + "), ");
+            });
+    System.out.println();
+  }
+
+  public static void runWeakReference() {
+    System.out.println("Test j.l.r.WeakReference");
+    String[] arr = new String[] {"weak", "ref"};
+    WeakReference<String[]> wr = new WeakReference(arr);
+    DbgPrintln("Pre hash: " + wr.get().hashCode());
+    System.out.println("val is: " + Arrays.toString(wr.get()) + " resize +3");
+    ResizeArray(wr::get, wr.get().length + 5);
+    System.out.println("val is: " + Arrays.toString(wr.get()));
+    DbgPrintln("Post hash: " + wr.get().hashCode());
+    System.out.println("Same value? " + (wr.get() == arr));
+  }
+
+  public static void runInstanceSelfRef() {
+    System.out.println("Test instance self-ref");
+    DbgPrintln("Pre hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+    String pre_to_string = theInstanceClass.self_ref.toString();
+    System.out.println(
+        "val is: "
+            + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>")
+            + " resize +5 item 0 is "
+            + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+                .replace(pre_to_string, "<SELF REF>"));
+    ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length + 5);
+    System.out.println(
+        "val is: "
+            + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>"));
+    System.out.println(
+        "val is: "
+            + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+                .replace(pre_to_string, "<SELF REF>"));
+    DbgPrintln("Post hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+    System.out.println(
+        "Same value? " + (theInstanceClass.self_ref == theOtherInstanceClass.self_ref));
+    System.out.println(
+        "Same structure? " + (theInstanceClass.self_ref == theInstanceClass.self_ref[0]));
+    System.out.println(
+        "Same inner-structure? "
+            + (theInstanceClass.self_ref[0] == ((Object[]) theInstanceClass.self_ref[0])[0]));
+  }
+
+  public static void runInstanceSelfRefSmall() {
+    System.out.println("Test instance self-ref smaller");
+    DbgPrintln("Pre hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+    String pre_to_string = theInstanceClass.self_ref.toString();
+    System.out.println(
+        "val is: "
+            + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>")
+            + " resize -7 item 0 is "
+            + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+                .replace(pre_to_string, "<SELF REF>"));
+    ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length - 7);
+    System.out.println(
+        "val is: "
+            + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>"));
+    System.out.println(
+        "val is: "
+            + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+                .replace(pre_to_string, "<SELF REF>"));
+    DbgPrintln("Post hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+    System.out.println(
+        "Same value? " + (theInstanceClass.self_ref == theOtherInstanceClass.self_ref));
+    System.out.println(
+        "Same structure? " + (theInstanceClass.self_ref == theInstanceClass.self_ref[0]));
+    System.out.println(
+        "Same inner-structure? "
+            + (theInstanceClass.self_ref[0] == ((Object[]) theInstanceClass.self_ref[0])[0]));
+  }
+
+  public static void runLocal() throws Exception {
+    final int[] arr_loc = new int[] {2, 3, 4};
+    int[] arr_loc_2 = arr_loc;
+
+    System.out.println("Test local");
+    DbgPrintln("Pre hash: " + arr_loc.hashCode());
+    System.out.println("val is: " + Arrays.toString(arr_loc) + " resize +5");
+    ResizeArray(() -> arr_loc, arr_loc.length + 5);
+    System.out.println("val is: " + Arrays.toString(arr_loc));
+    DbgPrintln("Post hash: " + arr_loc.hashCode());
+    System.out.println("Same value? " + (arr_loc == arr_loc_2));
+  }
+
+  public static void runLocalSmall() throws Exception {
+    final int[] arr_loc = new int[] {1, 2, 3, 4, 5};
+    int[] arr_loc_2 = arr_loc;
+
+    System.out.println("Test local smaller");
+    DbgPrintln("Pre hash: " + arr_loc.hashCode());
+    System.out.println("val is: " + Arrays.toString(arr_loc) + " resize -2");
+    ResizeArray(() -> arr_loc, arr_loc.length - 2);
+    System.out.println("val is: " + Arrays.toString(arr_loc));
+    DbgPrintln("Post hash: " + arr_loc.hashCode());
+    System.out.println("Same value? " + (arr_loc == arr_loc_2));
+  }
+
+  public static void runMultiThreadLocal() throws Exception {
+    final CountDownLatch cdl = new CountDownLatch(1);
+    final CountDownLatch start_cdl = new CountDownLatch(2);
+    final Supplier<Object[]> getArr =
+        new Supplier<Object[]>() {
+          public final Object[] arr = new Object[] {"1", "2", "3"};
+
+          public Object[] get() {
+            return arr;
+          }
+        };
+    final ArrayList<String> msg1 = new ArrayList();
+    final ArrayList<String> msg2 = new ArrayList();
+    final Consumer<String> print1 =
+        (String s) -> {
+          msg1.add(s);
+        };
+    final Consumer<String> print2 =
+        (String s) -> {
+          msg2.add(s);
+        };
+    Function<Consumer<String>, Runnable> r =
+        (final Consumer<String> c) ->
+            () -> {
+              c.accept("Test local multi-thread");
+              Object[] arr_loc = getArr.get();
+              Object[] arr_loc_2 = getArr.get();
+
+              DbgPrintln("Pre hash: " + arr_loc.hashCode());
+              c.accept("val is: " + Arrays.toString(arr_loc) + " resize -2");
+
+              try {
+                start_cdl.countDown();
+                cdl.await();
+              } catch (Exception e) {
+                throw new Error("failed await", e);
+              }
+              c.accept("val is: " + Arrays.toString(arr_loc));
+              DbgPrintln("Post hash: " + arr_loc.hashCode());
+              c.accept("Same value? " + (arr_loc == arr_loc_2));
+            };
+    Thread t1 = new Thread(r.apply(print1));
+    Thread t2 = new Thread(r.apply(print2));
+    t1.start();
+    t2.start();
+    start_cdl.await();
+    ResizeArray(getArr, 1);
+    cdl.countDown();
+    t1.join();
+    t2.join();
+    for (String s : msg1) {
+      System.out.println("T1: " + s);
+    }
+    for (String s : msg2) {
+      System.out.println("T2: " + s);
+    }
+  }
+
+  public static void runWithLocks() throws Exception {
+    final CountDownLatch cdl = new CountDownLatch(1);
+    final CountDownLatch start_cdl = new CountDownLatch(2);
+    final CountDownLatch waiter_start_cdl = new CountDownLatch(1);
+    final Supplier<Object[]> getArr =
+        new Supplier<Object[]>() {
+          public final Object[] arr = new Object[] {"A", "2", "C"};
+
+          public Object[] get() {
+            return arr;
+          }
+        };
+    // basic order of operations noted above each line.
+    // Waiter runs to the 'wait' then t1 runs to the cdl.await, then current thread runs.
+    Runnable r =
+        () -> {
+          System.out.println("Test locks");
+          Object[] arr_loc = getArr.get();
+          Object[] arr_loc_2 = getArr.get();
+
+          DbgPrintln("Pre hash: " + arr_loc.hashCode());
+          System.out.println("val is: " + Arrays.toString(arr_loc) + " resize -2");
+
+          try {
+            // OP 1
+            waiter_start_cdl.await();
+            // OP 6
+            synchronized (arr_loc) {
+              // OP 7
+              synchronized (arr_loc_2) {
+                // OP 8
+                start_cdl.countDown();
+                // OP 9
+                cdl.await();
+                // OP 13
+              }
+            }
+          } catch (Exception e) {
+            throw new Error("failed await", e);
+          }
+          System.out.println("val is: " + Arrays.toString(arr_loc));
+          DbgPrintln("Post hash: " + arr_loc.hashCode());
+          System.out.println("Same value? " + (arr_loc == arr_loc_2));
+        };
+    Thread t1 = new Thread(r);
+    Thread waiter =
+        new Thread(
+            () -> {
+              try {
+                Object a = getArr.get();
+                // OP 2
+                synchronized (a) {
+                  // OP 3
+                  waiter_start_cdl.countDown();
+                  // OP 4
+                  start_cdl.countDown();
+                  // OP 5
+                  a.wait();
+                  // OP 15
+                }
+              } catch (Exception e) {
+                throw new Error("Failed wait!", e);
+              }
+            });
+    waiter.start();
+    t1.start();
+    // OP 10
+    start_cdl.await();
+    // OP 11
+    ResizeArray(getArr, 1);
+    // OP 12
+    cdl.countDown();
+    // OP 14
+    synchronized (getArr.get()) {
+      // Make sure thread wakes up and has the right lock.
+      getArr.get().notifyAll();
+    }
+    waiter.join();
+    t1.join();
+    // Make sure other threads can still lock it.
+    synchronized (getArr.get()) {
+    }
+    System.out.println("Locks seem to all work.");
+  }
+
+  public static void runWithJniGlobal() throws Exception {
+    Object[] arr = new Object[] {"1", "11", "111"};
+    final long globalID = GetGlobalJniRef(arr);
+    System.out.println("Test jni-ref");
+    DbgPrintln("Pre hash: " + ReadJniRef(globalID).hashCode());
+    System.out.println(
+        "val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)) + " resize +5");
+    ResizeArray(() -> ReadJniRef(globalID), ((Object[]) ReadJniRef(globalID)).length + 5);
+    System.out.println("val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)));
+    DbgPrintln("Post hash: " + ReadJniRef(globalID).hashCode());
+    System.out.println("Same value? " + (ReadJniRef(globalID) == arr));
+  }
+
+  public static void runWithJniWeakGlobal() throws Exception {
+    Object[] arr = new Object[] {"2", "22", "222"};
+    final long globalID = GetWeakGlobalJniRef(arr);
+    System.out.println("Test weak jni-ref");
+    DbgPrintln("Pre hash: " + ReadJniRef(globalID).hashCode());
+    System.out.println(
+        "val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)) + " resize +5");
+    ResizeArray(() -> ReadJniRef(globalID), ((Object[]) ReadJniRef(globalID)).length + 5);
+    System.out.println("val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)));
+    DbgPrintln("Post hash: " + ReadJniRef(globalID).hashCode());
+    System.out.println("Same value? " + (ReadJniRef(globalID) == arr));
+    if (ReadJniRef(globalID) != arr) {
+      throw new Error("Didn't update weak global!");
+    }
+  }
+
+  public static void runWithJniLocals() throws Exception {
+    final Object[] arr = new Object[] {"3", "32", "322"};
+    System.out.println("Test jni local ref");
+    Consumer<Object> checker = (o) -> System.out.println("Same value? " + (o == arr));
+    Consumer<Object> printer =
+        (o) -> System.out.println("val is: " + Arrays.toString((Object[]) o));
+    Runnable resize =
+        () -> {
+          System.out.println("Resize +4");
+          ResizeArray(() -> arr, arr.length + 4);
+        };
+    runNativeTest(arr, resize, printer, checker);
+  }
+
+  public static native void runNativeTest(
+      Object[] arr, Runnable resize, Consumer<Object> printer, Consumer<Object> checker);
+
+  public static void runWithJvmtiTags() throws Exception {
+    Object[] arr = new Object[] {"3", "33", "333"};
+    long globalID = 333_333_333l;
+    Main.setTag(arr, globalID);
+    System.out.println("Test jvmti-tags");
+    DbgPrintln("Pre hash: " + arr.hashCode());
+    System.out.println(
+        "val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)) + " resize +5");
+    ResizeArray(() -> arr, arr.length + 5);
+    Object[] after_tagged_obj = GetObjectsWithTag(globalID);
+    System.out.println("val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)));
+    DbgPrintln("Post hash: " + after_tagged_obj[0].hashCode());
+    System.out.println("Same value? " + (after_tagged_obj[0] == arr));
+  }
+
+  public static void run() throws Exception {
+    // Simple
+    runAsThread(Test1974::runInstance);
+
+    // HashMap
+    runAsThread(Test1974::runHashMap);
+
+    // j.l.ref.WeakReference
+    runAsThread(Test1974::runWeakReference);
+
+    // Self-referential arrays.
+    runAsThread(Test1974::runInstanceSelfRef);
+    runAsThread(Test1974::runInstanceSelfRefSmall);
+
+    // Local variables simple
+    runAsThread(Test1974::runLocal);
+    runAsThread(Test1974::runLocalSmall);
+
+    // multiple threads local variables
+    runAsThread(Test1974::runMultiThreadLocal);
+
+    // using as monitors and waiting
+    runAsThread(Test1974::runWithLocks);
+
+    // Basic jni global refs
+    runAsThread(Test1974::runWithJniGlobal);
+
+    // Basic jni weak global refs
+    runAsThread(Test1974::runWithJniWeakGlobal);
+
+    // Basic JNI local refs
+    runAsThread(Test1974::runWithJniLocals);
+
+    // Basic jvmti tags
+    runAsThread(Test1974::runWithJvmtiTags);
+  }
+
+  // Use a supplier so that we don't have to have a local ref to the resized
+  // array if we don't want it
+  public static native <T> void ResizeArray(Supplier<T> arr, int new_size);
+
+  public static native <T> long GetGlobalJniRef(T t);
+
+  public static native <T> long GetWeakGlobalJniRef(T t);
+
+  public static native <T> T ReadJniRef(long t);
+
+  public static native Object[] GetObjectsWithTag(long tag);
+}