diff options
author | 2019-07-18 16:08:41 -0700 | |
---|---|---|
committer | 2019-08-01 17:50:27 +0000 | |
commit | c14ec8facf0ea51e7531fa0acbed3410468b0356 (patch) | |
tree | a70bc3cffcaf763e4951c96918bc6f8b25a472b3 /test/1974-resize-array/src | |
parent | 147a911636402f2658ce60bd3ad62eb197b00f74 (diff) |
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
Diffstat (limited to 'test/1974-resize-array/src')
-rw-r--r-- | test/1974-resize-array/src/Main.java | 21 | ||||
l--------- | test/1974-resize-array/src/art/Main.java | 1 | ||||
-rw-r--r-- | test/1974-resize-array/src/art/Test1974.java | 473 |
3 files changed, 495 insertions, 0 deletions
diff --git a/test/1974-resize-array/src/Main.java b/test/1974-resize-array/src/Main.java new file mode 100644 index 0000000000..38439731f6 --- /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 0000000000..84ae4ac310 --- /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 0000000000..8460e827eb --- /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); +} |