/* * 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 map = new HashMap(); Comparable the_value = "THE VALUE"; Supplier 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 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, "") + " resize +5 item 0 is " + Arrays.toString((Object[]) theInstanceClass.self_ref[0]) .replace(pre_to_string, "")); ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length + 5); System.out.println( "val is: " + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "")); System.out.println( "val is: " + Arrays.toString((Object[]) theInstanceClass.self_ref[0]) .replace(pre_to_string, "")); 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, "") + " resize -7 item 0 is " + Arrays.toString((Object[]) theInstanceClass.self_ref[0]) .replace(pre_to_string, "")); ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length - 7); System.out.println( "val is: " + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "")); System.out.println( "val is: " + Arrays.toString((Object[]) theInstanceClass.self_ref[0]) .replace(pre_to_string, "")); 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 getArr = new Supplier() { public final Object[] arr = new Object[] {"1", "2", "3"}; public Object[] get() { return arr; } }; final ArrayList msg1 = new ArrayList(); final ArrayList msg2 = new ArrayList(); final Consumer print1 = (String s) -> { msg1.add(s); }; final Consumer print2 = (String s) -> { msg2.add(s); }; Function, Runnable> r = (final Consumer 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 getArr = new Supplier() { 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 checker = (o) -> System.out.println("Same value? " + (o == arr)); Consumer 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 printer, Consumer 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 runWithJvmtiTagsObsolete() throws Exception { Object[] arr = new Object[] {"4", "44", "444"}; long globalID = 444_444_444l; System.out.println("Test jvmti-tags with obsolete"); Main.setTag(arr, globalID); StartCollectFrees(); StartAssignObsoleteIncrementedId(); 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); Object[] obsolete_tagged_obj = GetObjectsWithTag(globalID + 1); System.out.println("val is: " + Arrays.deepToString(GetObjectsWithTag(globalID))); EndAssignObsoleteIncrementedId(); long[] obsoletes_freed = CollectFreedTags(); DbgPrintln("Post hash: " + after_tagged_obj[0].hashCode()); System.out.println("Same value? " + (after_tagged_obj[0] == arr)); if (obsolete_tagged_obj.length >= 1) { DbgPrintln("Found objects with obsolete tag: " + Arrays.deepToString(obsolete_tagged_obj)); boolean bad = false; if (obsolete_tagged_obj.length != 1) { System.out.println( "Found obsolete tag'd objects: " + Arrays.deepHashCode(obsolete_tagged_obj) + " but only expected one!"); bad = true; } if (!Arrays.deepEquals( Arrays.copyOf(arr, ((Object[]) obsolete_tagged_obj[0]).length), (Object[]) obsolete_tagged_obj[0])) { System.out.println("Obsolete array was unexpectedly different than non-obsolete one!"); bad = true; } if (!Arrays.stream(obsoletes_freed).anyMatch((l) -> l == globalID + 1)) { DbgPrintln("Didn't see a free of the obsolete id"); } if (!bad) { System.out.println("Everything looks good WRT obsolete object!"); } } else { if (!Arrays.stream(obsoletes_freed).anyMatch((l) -> l == globalID + 1)) { System.out.println("Didn't see a free of the obsolete id"); } else { DbgPrintln("Saw a free of obsolete id!"); System.out.println("Everything looks good WRT obsolete object!"); } } } 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); // Grab obsolete reference using tags/detect free runAsThread(Test1974::runWithJvmtiTagsObsolete); } // 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 void ResizeArray(Supplier arr, int new_size); public static native long GetGlobalJniRef(T t); public static native long GetWeakGlobalJniRef(T t); public static native T ReadJniRef(long t); public static native Object[] GetObjectsWithTag(long tag); public static native void StartCollectFrees(); public static native void StartAssignObsoleteIncrementedId(); public static native void EndAssignObsoleteIncrementedId(); public static native long[] CollectFreedTags(); }