1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package art;
18 
19 import java.lang.ref.WeakReference;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.function.Consumer;
25 import java.util.function.Function;
26 import java.util.function.Supplier;
27 
28 public class Test1974 {
29 
30   public static final boolean DEBUG = false;
31 
32   public static int[] static_field = new int[] {1, 2, 3};
33   public static Object[] static_field_ref = new String[] {"a", "b", "c"};
34 
35   public static final class InstanceClass {
36     public int[] instance_field = new int[] {1, 2, 3};
37     public Object[] self_ref;
38 
InstanceClass()39     public InstanceClass() {
40       self_ref = new Object[] {null, "A", "B", "C"};
41       self_ref[0] = self_ref;
42     }
43   }
44 
45   static InstanceClass theInstanceClass;
46   static InstanceClass theOtherInstanceClass;
47 
48   static {
49     theInstanceClass = new InstanceClass();
50     theOtherInstanceClass = new InstanceClass();
51     theOtherInstanceClass.instance_field = theInstanceClass.instance_field;
52     theOtherInstanceClass.self_ref = theInstanceClass.self_ref;
53   }
54 
DbgPrintln(String s)55   public static void DbgPrintln(String s) {
56     if (DEBUG) {
57       System.out.println(s);
58     }
59   }
60 
61   public interface ThrowRunnable extends Runnable {
run()62     public default void run() {
63       try {
64         throwRun();
65       } catch (Exception e) {
66         throw new Error("Exception in runner!", e);
67       }
68     }
69 
throwRun()70     public void throwRun() throws Exception;
71   }
72 
runAsThread(ThrowRunnable r)73   public static void runAsThread(ThrowRunnable r) throws Exception {
74     Thread t = new Thread(r);
75     t.start();
76     t.join();
77     System.out.println("");
78   }
79 
runInstance()80   public static void runInstance() {
81     System.out.println("Test instance");
82     DbgPrintln("Pre hash: " + theInstanceClass.instance_field.hashCode());
83     System.out.println(
84         "val is: " + Arrays.toString(theInstanceClass.instance_field) + " resize +3");
85     ResizeArray(() -> theInstanceClass.instance_field, theInstanceClass.instance_field.length + 5);
86     System.out.println("val is: " + Arrays.toString(theInstanceClass.instance_field));
87     DbgPrintln("Post hash: " + theInstanceClass.instance_field.hashCode());
88     System.out.println(
89         "Same value? " + (theInstanceClass.instance_field == theOtherInstanceClass.instance_field));
90   }
91 
runHashMap()92   public static void runHashMap() {
93     System.out.println("Test HashMap");
94     HashMap<byte[], Comparable> map = new HashMap();
95     Comparable the_value = "THE VALUE";
96     Supplier<byte[]> get_the_value =
97         () ->
98             map.entrySet().stream()
99                 .filter((x) -> x.getValue().equals(the_value))
100                 .findFirst()
101                 .get()
102                 .getKey();
103     map.put(new byte[] {1, 2, 3, 4}, the_value);
104     map.put(new byte[] {1, 2, 3, 4}, "Other Value");
105     map.put(new byte[] {1, 4}, "Third value");
106     System.out.println("val is: " + Arrays.toString(get_the_value.get()) + " resize +3");
107     System.out.print("Map is: ");
108     map.entrySet().stream()
109         .sorted((x, y) -> x.getValue().compareTo(y.getValue()))
110         .forEach(
111             (e) -> {
112               System.out.print("(" + Arrays.toString(e.getKey()) + "->" + e.getValue() + "), ");
113             });
114     System.out.println();
115     ResizeArray(get_the_value, 7);
116     System.out.println("val is: " + Arrays.toString(get_the_value.get()));
117     System.out.print("Map is: ");
118     map.entrySet().stream()
119         .sorted((x, y) -> x.getValue().compareTo(y.getValue()))
120         .forEach(
121             (e) -> {
122               System.out.print("(" + Arrays.toString(e.getKey()) + "->" + e.getValue() + "), ");
123             });
124     System.out.println();
125   }
126 
runWeakReference()127   public static void runWeakReference() {
128     System.out.println("Test j.l.r.WeakReference");
129     String[] arr = new String[] {"weak", "ref"};
130     WeakReference<String[]> wr = new WeakReference(arr);
131     DbgPrintln("Pre hash: " + wr.get().hashCode());
132     System.out.println("val is: " + Arrays.toString(wr.get()) + " resize +3");
133     ResizeArray(wr::get, wr.get().length + 5);
134     System.out.println("val is: " + Arrays.toString(wr.get()));
135     DbgPrintln("Post hash: " + wr.get().hashCode());
136     System.out.println("Same value? " + (wr.get() == arr));
137   }
138 
runInstanceSelfRef()139   public static void runInstanceSelfRef() {
140     System.out.println("Test instance self-ref");
141     DbgPrintln("Pre hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
142     String pre_to_string = theInstanceClass.self_ref.toString();
143     System.out.println(
144         "val is: "
145             + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>")
146             + " resize +5 item 0 is "
147             + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
148                 .replace(pre_to_string, "<SELF REF>"));
149     ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length + 5);
150     System.out.println(
151         "val is: "
152             + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>"));
153     System.out.println(
154         "val is: "
155             + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
156                 .replace(pre_to_string, "<SELF REF>"));
157     DbgPrintln("Post hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
158     System.out.println(
159         "Same value? " + (theInstanceClass.self_ref == theOtherInstanceClass.self_ref));
160     System.out.println(
161         "Same structure? " + (theInstanceClass.self_ref == theInstanceClass.self_ref[0]));
162     System.out.println(
163         "Same inner-structure? "
164             + (theInstanceClass.self_ref[0] == ((Object[]) theInstanceClass.self_ref[0])[0]));
165   }
166 
runInstanceSelfRefSmall()167   public static void runInstanceSelfRefSmall() {
168     System.out.println("Test instance self-ref smaller");
169     DbgPrintln("Pre hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
170     String pre_to_string = theInstanceClass.self_ref.toString();
171     System.out.println(
172         "val is: "
173             + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>")
174             + " resize -7 item 0 is "
175             + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
176                 .replace(pre_to_string, "<SELF REF>"));
177     ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length - 7);
178     System.out.println(
179         "val is: "
180             + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>"));
181     System.out.println(
182         "val is: "
183             + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
184                 .replace(pre_to_string, "<SELF REF>"));
185     DbgPrintln("Post hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
186     System.out.println(
187         "Same value? " + (theInstanceClass.self_ref == theOtherInstanceClass.self_ref));
188     System.out.println(
189         "Same structure? " + (theInstanceClass.self_ref == theInstanceClass.self_ref[0]));
190     System.out.println(
191         "Same inner-structure? "
192             + (theInstanceClass.self_ref[0] == ((Object[]) theInstanceClass.self_ref[0])[0]));
193   }
194 
runLocal()195   public static void runLocal() throws Exception {
196     final int[] arr_loc = new int[] {2, 3, 4};
197     int[] arr_loc_2 = arr_loc;
198 
199     System.out.println("Test local");
200     DbgPrintln("Pre hash: " + arr_loc.hashCode());
201     System.out.println("val is: " + Arrays.toString(arr_loc) + " resize +5");
202     ResizeArray(() -> arr_loc, arr_loc.length + 5);
203     System.out.println("val is: " + Arrays.toString(arr_loc));
204     DbgPrintln("Post hash: " + arr_loc.hashCode());
205     System.out.println("Same value? " + (arr_loc == arr_loc_2));
206   }
207 
runLocalSmall()208   public static void runLocalSmall() throws Exception {
209     final int[] arr_loc = new int[] {1, 2, 3, 4, 5};
210     int[] arr_loc_2 = arr_loc;
211 
212     System.out.println("Test local smaller");
213     DbgPrintln("Pre hash: " + arr_loc.hashCode());
214     System.out.println("val is: " + Arrays.toString(arr_loc) + " resize -2");
215     ResizeArray(() -> arr_loc, arr_loc.length - 2);
216     System.out.println("val is: " + Arrays.toString(arr_loc));
217     DbgPrintln("Post hash: " + arr_loc.hashCode());
218     System.out.println("Same value? " + (arr_loc == arr_loc_2));
219   }
220 
runMultiThreadLocal()221   public static void runMultiThreadLocal() throws Exception {
222     final CountDownLatch cdl = new CountDownLatch(1);
223     final CountDownLatch start_cdl = new CountDownLatch(2);
224     final Supplier<Object[]> getArr =
225         new Supplier<Object[]>() {
226           public final Object[] arr = new Object[] {"1", "2", "3"};
227 
228           public Object[] get() {
229             return arr;
230           }
231         };
232     final ArrayList<String> msg1 = new ArrayList();
233     final ArrayList<String> msg2 = new ArrayList();
234     final Consumer<String> print1 =
235         (String s) -> {
236           msg1.add(s);
237         };
238     final Consumer<String> print2 =
239         (String s) -> {
240           msg2.add(s);
241         };
242     Function<Consumer<String>, Runnable> r =
243         (final Consumer<String> c) ->
244             () -> {
245               c.accept("Test local multi-thread");
246               Object[] arr_loc = getArr.get();
247               Object[] arr_loc_2 = getArr.get();
248 
249               DbgPrintln("Pre hash: " + arr_loc.hashCode());
250               c.accept("val is: " + Arrays.toString(arr_loc) + " resize -2");
251 
252               try {
253                 start_cdl.countDown();
254                 cdl.await();
255               } catch (Exception e) {
256                 throw new Error("failed await", e);
257               }
258               c.accept("val is: " + Arrays.toString(arr_loc));
259               DbgPrintln("Post hash: " + arr_loc.hashCode());
260               c.accept("Same value? " + (arr_loc == arr_loc_2));
261             };
262     Thread t1 = new Thread(r.apply(print1));
263     Thread t2 = new Thread(r.apply(print2));
264     t1.start();
265     t2.start();
266     start_cdl.await();
267     ResizeArray(getArr, 1);
268     cdl.countDown();
269     t1.join();
270     t2.join();
271     for (String s : msg1) {
272       System.out.println("T1: " + s);
273     }
274     for (String s : msg2) {
275       System.out.println("T2: " + s);
276     }
277   }
278 
runWithLocks()279   public static void runWithLocks() throws Exception {
280     final CountDownLatch cdl = new CountDownLatch(1);
281     final CountDownLatch start_cdl = new CountDownLatch(2);
282     final CountDownLatch waiter_start_cdl = new CountDownLatch(1);
283     final Supplier<Object[]> getArr =
284         new Supplier<Object[]>() {
285           public final Object[] arr = new Object[] {"A", "2", "C"};
286 
287           public Object[] get() {
288             return arr;
289           }
290         };
291     // basic order of operations noted above each line.
292     // Waiter runs to the 'wait' then t1 runs to the cdl.await, then current thread runs.
293     Runnable r =
294         () -> {
295           System.out.println("Test locks");
296           Object[] arr_loc = getArr.get();
297           Object[] arr_loc_2 = getArr.get();
298 
299           DbgPrintln("Pre hash: " + arr_loc.hashCode());
300           System.out.println("val is: " + Arrays.toString(arr_loc) + " resize -2");
301 
302           try {
303             // OP 1
304             waiter_start_cdl.await();
305             // OP 6
306             synchronized (arr_loc) {
307               // OP 7
308               synchronized (arr_loc_2) {
309                 // OP 8
310                 start_cdl.countDown();
311                 // OP 9
312                 cdl.await();
313                 // OP 13
314               }
315             }
316           } catch (Exception e) {
317             throw new Error("failed await", e);
318           }
319           System.out.println("val is: " + Arrays.toString(arr_loc));
320           DbgPrintln("Post hash: " + arr_loc.hashCode());
321           System.out.println("Same value? " + (arr_loc == arr_loc_2));
322         };
323     Thread t1 = new Thread(r);
324     Thread waiter =
325         new Thread(
326             () -> {
327               try {
328                 Object a = getArr.get();
329                 // OP 2
330                 synchronized (a) {
331                   // OP 3
332                   waiter_start_cdl.countDown();
333                   // OP 4
334                   start_cdl.countDown();
335                   // OP 5
336                   a.wait();
337                   // OP 15
338                 }
339               } catch (Exception e) {
340                 throw new Error("Failed wait!", e);
341               }
342             });
343     waiter.start();
344     t1.start();
345     // OP 10
346     start_cdl.await();
347     // OP 11
348     ResizeArray(getArr, 1);
349     // OP 12
350     cdl.countDown();
351     // OP 14
352     synchronized (getArr.get()) {
353       // Make sure thread wakes up and has the right lock.
354       getArr.get().notifyAll();
355     }
356     waiter.join();
357     t1.join();
358     // Make sure other threads can still lock it.
359     synchronized (getArr.get()) {
360     }
361     System.out.println("Locks seem to all work.");
362   }
363 
runWithJniGlobal()364   public static void runWithJniGlobal() throws Exception {
365     Object[] arr = new Object[] {"1", "11", "111"};
366     final long globalID = GetGlobalJniRef(arr);
367     System.out.println("Test jni-ref");
368     DbgPrintln("Pre hash: " + ReadJniRef(globalID).hashCode());
369     System.out.println(
370         "val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)) + " resize +5");
371     ResizeArray(() -> ReadJniRef(globalID), ((Object[]) ReadJniRef(globalID)).length + 5);
372     System.out.println("val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)));
373     DbgPrintln("Post hash: " + ReadJniRef(globalID).hashCode());
374     System.out.println("Same value? " + (ReadJniRef(globalID) == arr));
375   }
376 
runWithJniWeakGlobal()377   public static void runWithJniWeakGlobal() throws Exception {
378     Object[] arr = new Object[] {"2", "22", "222"};
379     final long globalID = GetWeakGlobalJniRef(arr);
380     System.out.println("Test weak jni-ref");
381     DbgPrintln("Pre hash: " + ReadJniRef(globalID).hashCode());
382     System.out.println(
383         "val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)) + " resize +5");
384     ResizeArray(() -> ReadJniRef(globalID), ((Object[]) ReadJniRef(globalID)).length + 5);
385     System.out.println("val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)));
386     DbgPrintln("Post hash: " + ReadJniRef(globalID).hashCode());
387     System.out.println("Same value? " + (ReadJniRef(globalID) == arr));
388     if (ReadJniRef(globalID) != arr) {
389       throw new Error("Didn't update weak global!");
390     }
391   }
392 
runWithJniLocals()393   public static void runWithJniLocals() throws Exception {
394     final Object[] arr = new Object[] {"3", "32", "322"};
395     System.out.println("Test jni local ref");
396     Consumer<Object> checker = (o) -> System.out.println("Same value? " + (o == arr));
397     Consumer<Object> printer =
398         (o) -> System.out.println("val is: " + Arrays.toString((Object[]) o));
399     Runnable resize =
400         () -> {
401           System.out.println("Resize +4");
402           ResizeArray(() -> arr, arr.length + 4);
403         };
404     runNativeTest(arr, resize, printer, checker);
405   }
406 
runNativeTest( Object[] arr, Runnable resize, Consumer<Object> printer, Consumer<Object> checker)407   public static native void runNativeTest(
408       Object[] arr, Runnable resize, Consumer<Object> printer, Consumer<Object> checker);
409 
runWithJvmtiTags()410   public static void runWithJvmtiTags() throws Exception {
411     Object[] arr = new Object[] {"3", "33", "333"};
412     long globalID = 333_333_333l;
413     Main.setTag(arr, globalID);
414     System.out.println("Test jvmti-tags");
415     DbgPrintln("Pre hash: " + arr.hashCode());
416     System.out.println(
417         "val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)) + " resize +5");
418     ResizeArray(() -> arr, arr.length + 5);
419     Object[] after_tagged_obj = GetObjectsWithTag(globalID);
420     System.out.println("val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)));
421     DbgPrintln("Post hash: " + after_tagged_obj[0].hashCode());
422     System.out.println("Same value? " + (after_tagged_obj[0] == arr));
423   }
424 
runWithJvmtiTagsObsolete()425   public static void runWithJvmtiTagsObsolete() throws Exception {
426     Object[] arr = new Object[] {"4", "44", "444"};
427     long globalID = 444_444_444l;
428     System.out.println("Test jvmti-tags with obsolete");
429     Main.setTag(arr, globalID);
430     StartCollectFrees();
431     StartAssignObsoleteIncrementedId();
432     DbgPrintln("Pre hash: " + arr.hashCode());
433     System.out.println(
434         "val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)) + " resize +5");
435     ResizeArray(() -> arr, arr.length + 5);
436     Object[] after_tagged_obj = GetObjectsWithTag(globalID);
437     Object[] obsolete_tagged_obj = GetObjectsWithTag(globalID + 1);
438     System.out.println("val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)));
439     EndAssignObsoleteIncrementedId();
440     long[] obsoletes_freed = CollectFreedTags();
441     DbgPrintln("Post hash: " + after_tagged_obj[0].hashCode());
442     System.out.println("Same value? " + (after_tagged_obj[0] == arr));
443     if (obsolete_tagged_obj.length >= 1) {
444       DbgPrintln("Found objects with obsolete tag: " + Arrays.deepToString(obsolete_tagged_obj));
445       boolean bad = false;
446       if (obsolete_tagged_obj.length != 1) {
447         System.out.println(
448             "Found obsolete tag'd objects: "
449                 + Arrays.deepHashCode(obsolete_tagged_obj)
450                 + " but only expected one!");
451         bad = true;
452       }
453       if (!Arrays.deepEquals(
454           Arrays.copyOf(arr, ((Object[]) obsolete_tagged_obj[0]).length),
455           (Object[]) obsolete_tagged_obj[0])) {
456         System.out.println("Obsolete array was unexpectedly different than non-obsolete one!");
457         bad = true;
458       }
459       if (!Arrays.stream(obsoletes_freed).anyMatch((l) -> l == globalID + 1)) {
460         DbgPrintln("Didn't see a free of the obsolete id");
461       }
462       if (!bad) {
463         System.out.println("Everything looks good WRT obsolete object!");
464       }
465     } else {
466       if (!Arrays.stream(obsoletes_freed).anyMatch((l) -> l == globalID + 1)) {
467         System.out.println("Didn't see a free of the obsolete id");
468       } else {
469         DbgPrintln("Saw a free of obsolete id!");
470         System.out.println("Everything looks good WRT obsolete object!");
471       }
472     }
473   }
474 
run()475   public static void run() throws Exception {
476     // Simple
477     runAsThread(Test1974::runInstance);
478 
479     // HashMap
480     runAsThread(Test1974::runHashMap);
481 
482     // j.l.ref.WeakReference
483     runAsThread(Test1974::runWeakReference);
484 
485     // Self-referential arrays.
486     runAsThread(Test1974::runInstanceSelfRef);
487     runAsThread(Test1974::runInstanceSelfRefSmall);
488 
489     // Local variables simple
490     runAsThread(Test1974::runLocal);
491     runAsThread(Test1974::runLocalSmall);
492 
493     // multiple threads local variables
494     runAsThread(Test1974::runMultiThreadLocal);
495 
496     // using as monitors and waiting
497     runAsThread(Test1974::runWithLocks);
498 
499     // Basic jni global refs
500     runAsThread(Test1974::runWithJniGlobal);
501 
502     // Basic jni weak global refs
503     runAsThread(Test1974::runWithJniWeakGlobal);
504 
505     // Basic JNI local refs
506     runAsThread(Test1974::runWithJniLocals);
507 
508     // Basic jvmti tags
509     runAsThread(Test1974::runWithJvmtiTags);
510 
511     // Grab obsolete reference using tags/detect free
512     runAsThread(Test1974::runWithJvmtiTagsObsolete);
513   }
514 
515   // Use a supplier so that we don't have to have a local ref to the resized
516   // array if we don't want it
ResizeArray(Supplier<T> arr, int new_size)517   public static native <T> void ResizeArray(Supplier<T> arr, int new_size);
518 
GetGlobalJniRef(T t)519   public static native <T> long GetGlobalJniRef(T t);
520 
GetWeakGlobalJniRef(T t)521   public static native <T> long GetWeakGlobalJniRef(T t);
522 
ReadJniRef(long t)523   public static native <T> T ReadJniRef(long t);
524 
GetObjectsWithTag(long tag)525   public static native Object[] GetObjectsWithTag(long tag);
526 
StartCollectFrees()527   public static native void StartCollectFrees();
528 
StartAssignObsoleteIncrementedId()529   public static native void StartAssignObsoleteIncrementedId();
530 
EndAssignObsoleteIncrementedId()531   public static native void EndAssignObsoleteIncrementedId();
532 
CollectFreedTags()533   public static native long[] CollectFreedTags();
534 }
535