1/*
2 * Copyright (C) 2016 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
17package art;
18
19import java.io.BufferedReader;
20import java.io.File;
21import java.io.FileReader;
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.Collections;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.concurrent.CountDownLatch;
28
29public class Test913 {
30  public static void run() throws Exception {
31    doTest();
32
33    // Use a countdown latch for synchronization, as join() will introduce more roots.
34    final CountDownLatch cdl1 = new CountDownLatch(1);
35
36    // Run the follow-references tests on a dedicated thread so we know the specific Thread type.
37    Thread t = new Thread() {
38      @Override
39      public void run() {
40        try {
41          Test913.runFollowReferences();
42        } catch (Exception e) {
43          throw new RuntimeException(e);
44        }
45        cdl1.countDown();
46      }
47    };
48    t.start();
49    cdl1.await();
50
51    doExtensionTests();
52  }
53
54  public static void runFollowReferences() throws Exception {
55    new TestConfig().doFollowReferencesTest();
56
57    Runtime.getRuntime().gc();
58    Runtime.getRuntime().gc();
59
60    new TestConfig(null, 0, 1, -1).doFollowReferencesTest();
61
62    Runtime.getRuntime().gc();
63    Runtime.getRuntime().gc();
64
65    new TestConfig(null, 0, Integer.MAX_VALUE, 1).doFollowReferencesTest();
66
67    Runtime.getRuntime().gc();
68    Runtime.getRuntime().gc();
69
70    doStringTest();
71
72    Runtime.getRuntime().gc();
73    Runtime.getRuntime().gc();
74
75    doPrimitiveArrayTest();
76    doPrimitiveFieldTest();
77
78    Runtime.getRuntime().gc();
79    Runtime.getRuntime().gc();
80
81    // Test klass filter.
82    System.out.println("--- klass ---");
83    new TestConfig(A.class, 0).doFollowReferencesTest();
84
85    // Test heap filter.
86    System.out.println("--- heap_filter ---");
87    System.out.println("---- tagged objects");
88    new TestConfig(null, 0x4).doFollowReferencesTest();
89    System.out.println("---- untagged objects");
90    new TestConfig(null, 0x8).doFollowReferencesTest();
91    System.out.println("---- tagged classes");
92    new TestConfig(null, 0x10).doFollowReferencesTest();
93    System.out.println("---- untagged classes");
94    new TestConfig(null, 0x20).doFollowReferencesTest();
95  }
96
97  public static void doTest() throws Exception {
98    setupGcCallback();
99
100    enableGcTracking(true);
101    runGc();
102    enableGcTracking(false);
103  }
104
105  public static void doStringTest() throws Exception {
106    final String str = new String("HelloWorld");
107    final String str2 = new String("");
108    Object o = new Object() {
109      String s = str;
110      String s2 = str2;
111    };
112
113    setTag(str, 1);
114    setTag(str2, 2);
115    System.out.println(Arrays.toString(followReferencesString(o)));
116    System.out.println(getTag(str));
117    System.out.println(getTag(str2));
118  }
119
120  public static void doPrimitiveArrayTest() throws Exception {
121    final boolean[] zArray = new boolean[] { false, true };
122    setTag(zArray, 1);
123
124    final byte[] bArray = new byte[] { 1, 2, 3 };
125    setTag(bArray, 2);
126
127    final char[] cArray = new char[] { 'A', 'Z' };
128    setTag(cArray, 3);
129
130    final short[] sArray = new short[] { 1, 2, 3 };
131    setTag(sArray, 4);
132
133    final int[] iArray = new int[] { 1, 2, 3 };
134    setTag(iArray, 5);
135
136    final float[] fArray = new float[] { 0.0f, 1.0f };
137    setTag(fArray, 6);
138
139    final long[] lArray = new long[] { 1, 2, 3 };
140    setTag(lArray, 7);
141
142    final double[] dArray = new double[] { 0.0, 1.0 };
143    setTag(dArray, 8);
144
145    Object o = new Object() {
146      Object z = zArray;
147      Object b = bArray;
148      Object c = cArray;
149      Object s = sArray;
150      Object i = iArray;
151      Object f = fArray;
152      Object l = lArray;
153      Object d = dArray;
154    };
155
156    System.out.println(followReferencesPrimitiveArray(o));
157    System.out.print(getTag(zArray));
158    System.out.print(getTag(bArray));
159    System.out.print(getTag(cArray));
160    System.out.print(getTag(sArray));
161    System.out.print(getTag(iArray));
162    System.out.print(getTag(fArray));
163    System.out.print(getTag(lArray));
164    System.out.println(getTag(dArray));
165  }
166
167  public static void doPrimitiveFieldTest() throws Exception {
168    // Force GCs to clean up dirt.
169    Runtime.getRuntime().gc();
170    Runtime.getRuntime().gc();
171
172    doTestPrimitiveFieldsClasses();
173
174    doTestPrimitiveFieldsIntegral();
175
176    // Force GCs to clean up dirt.
177    Runtime.getRuntime().gc();
178    Runtime.getRuntime().gc();
179
180    doTestPrimitiveFieldsFloat();
181
182    // Force GCs to clean up dirt.
183    Runtime.getRuntime().gc();
184    Runtime.getRuntime().gc();
185  }
186
187  private static void doTestPrimitiveFieldsClasses() {
188    setTag(IntObject.class, 10000);
189    System.out.println(followReferencesPrimitiveFields(IntObject.class));
190    System.out.println(getTag(IntObject.class));
191    setTag(IntObject.class, 0);
192
193    setTag(FloatObject.class, 10000);
194    System.out.println(followReferencesPrimitiveFields(FloatObject.class));
195    System.out.println(getTag(FloatObject.class));
196    setTag(FloatObject.class, 0);
197
198    setTag(Inf1.class, 10000);
199    System.out.println(followReferencesPrimitiveFields(Inf1.class));
200    System.out.println(getTag(Inf1.class));
201    setTag(Inf1.class, 0);
202
203    setTag(Inf2.class, 10000);
204    System.out.println(followReferencesPrimitiveFields(Inf2.class));
205    System.out.println(getTag(Inf2.class));
206    setTag(Inf2.class, 0);
207  }
208
209  private static void doTestPrimitiveFieldsIntegral() {
210    IntObject intObject = new IntObject();
211    setTag(intObject, 10000);
212    System.out.println(followReferencesPrimitiveFields(intObject));
213    System.out.println(getTag(intObject));
214  }
215
216  private static void doTestPrimitiveFieldsFloat() {
217    FloatObject floatObject = new FloatObject();
218    setTag(floatObject, 10000);
219    System.out.println(followReferencesPrimitiveFields(floatObject));
220    System.out.println(getTag(floatObject));
221  }
222
223  static ArrayList<Object> extensionTestHolder;
224
225  private static void doExtensionTests() {
226    checkForExtensionApis();
227
228    extensionTestHolder = new ArrayList<>();
229    System.out.println();
230
231    try {
232      getHeapName(-1);
233      System.out.println("Expected failure for -1");
234    } catch (Exception e) {
235    }
236    System.out.println(getHeapName(0));
237    System.out.println(getHeapName(1));
238    System.out.println(getHeapName(2));
239    System.out.println(getHeapName(3));
240    try {
241      getHeapName(4);
242      System.out.println("Expected failure for -1");
243    } catch (Exception e) {
244    }
245
246    System.out.println();
247
248    setTag(Object.class, 100000);
249    int objectClassHeapId = getObjectHeapId(100000);
250    int objClassExpectedHeapId = hasImage() ? 1 : 3;
251    if (objectClassHeapId != objClassExpectedHeapId) {
252      throw new RuntimeException("Expected object class in heap " + objClassExpectedHeapId +
253          " but received " + objectClassHeapId);
254    }
255
256    A a = new A();
257    extensionTestHolder.add(a);
258    setTag(a, 100001);
259    System.out.println(getObjectHeapId(100001));
260
261    checkGetObjectHeapIdInCallback(100000, objClassExpectedHeapId);
262    checkGetObjectHeapIdInCallback(100001, 3);
263
264    long baseTag = 30000000;
265    setTag(Object.class, baseTag + objClassExpectedHeapId);
266    setTag(Class.class, baseTag + objClassExpectedHeapId);
267    Object o = new Object();
268    extensionTestHolder.add(o);
269    setTag(o, baseTag + 3);
270
271    iterateThroughHeapExt();
272
273    extensionTestHolder = null;
274  }
275
276  private static void runGc() {
277    clearStats();
278    forceGarbageCollection();
279    printStats();
280  }
281
282  private static void clearStats() {
283    getGcStarts();
284    getGcFinishes();
285  }
286
287  private static void printStats() {
288    System.out.println("---");
289    int s = getGcStarts();
290    int f = getGcFinishes();
291    System.out.println((s > 0) + " " + (f > 0));
292  }
293
294  private static boolean hasImage() {
295    try {
296      int pid = Integer.parseInt(new File("/proc/self").getCanonicalFile().getName());
297      BufferedReader reader = new BufferedReader(new FileReader("/proc/" + pid + "/maps"));
298      String line;
299      while ((line = reader.readLine()) != null) {
300        if (line.endsWith(".art")) {
301          reader.close();
302          return true;
303        }
304      }
305      reader.close();
306      return false;
307    } catch (Exception e) {
308      throw new RuntimeException(e);
309    }
310  }
311
312  private static class TestConfig {
313    private Class<?> klass = null;
314    private int heapFilter = 0;
315    private int stopAfter = Integer.MAX_VALUE;
316    private int followSet = -1;
317
318    public TestConfig() {
319    }
320    public TestConfig(Class<?> klass, int heapFilter) {
321      this.klass = klass;
322      this.heapFilter = heapFilter;
323    }
324    public TestConfig(Class<?> klass, int heapFilter, int stopAfter, int followSet) {
325      this.klass = klass;
326      this.heapFilter = heapFilter;
327      this.stopAfter = stopAfter;
328      this.followSet = followSet;
329    }
330
331    public void doFollowReferencesTest() throws Exception {
332      // Force GCs to clean up dirt.
333      Runtime.getRuntime().gc();
334      Runtime.getRuntime().gc();
335
336      setTag(Thread.currentThread(), 3000);
337
338      {
339        ArrayList<Object> tmpStorage = new ArrayList<>();
340        doFollowReferencesTestNonRoot(tmpStorage);
341        tmpStorage = null;
342      }
343
344      // Force GCs to clean up dirt.
345      Runtime.getRuntime().gc();
346      Runtime.getRuntime().gc();
347
348      doFollowReferencesTestRoot();
349
350      // Force GCs to clean up dirt.
351      Runtime.getRuntime().gc();
352      Runtime.getRuntime().gc();
353    }
354
355    private void doFollowReferencesTestNonRoot(ArrayList<Object> tmpStorage) {
356      Verifier v = new Verifier();
357      tagClasses(v);
358      A a = createTree(v);
359      tmpStorage.add(a);
360      v.add("0@0", "1@1000");  // tmpStorage[0] --(array-element)--> a.
361
362      doFollowReferencesTestImpl(null, stopAfter, followSet, null, v, null);
363      doFollowReferencesTestImpl(a.foo2, stopAfter, followSet, null, v, "3@1001");
364
365      tmpStorage.clear();
366    }
367
368    private void doFollowReferencesTestRoot() {
369      Verifier v = new Verifier();
370      tagClasses(v);
371      A a = createTree(v);
372
373      doFollowReferencesTestImpl(null, stopAfter, followSet, a, v, null);
374      doFollowReferencesTestImpl(a.foo2, stopAfter, followSet, a, v, "3@1001");
375    }
376
377    private void doFollowReferencesTestImpl(A root, int stopAfter, int followSet,
378        Object asRoot, Verifier v, String additionalEnabled) {
379      String[] lines =
380          followReferences(heapFilter, klass, root, stopAfter, followSet, asRoot);
381
382      v.process(lines, additionalEnabled, heapFilter != 0 || klass != null);
383    }
384
385    private static void tagClasses(Verifier v) {
386      setTag(A.class, 1000);
387
388      setTag(B.class, 1001);
389      v.add("1001@0", "1000@0");  // B.class --(superclass)--> A.class.
390
391      setTag(C.class, 1002);
392      v.add("1002@0", "1001@0");  // C.class --(superclass)--> B.class.
393      v.add("1002@0", "2001@0");  // C.class --(interface)--> I2.class.
394
395      setTag(I1.class, 2000);
396
397      setTag(I2.class, 2001);
398      v.add("2001@0", "2000@0");  // I2.class --(interface)--> I1.class.
399    }
400
401    private static A createTree(Verifier v) {
402      A aInst = new A();
403      setTag(aInst, 1);
404      String aInstStr = "1@1000";
405      String aClassStr = "1000@0";
406      v.add(aInstStr, aClassStr);  // A -->(class) --> A.class.
407
408      A a2Inst = new A();
409      setTag(a2Inst, 2);
410      aInst.foo = a2Inst;
411      String a2InstStr = "2@1000";
412      v.add(a2InstStr, aClassStr);  // A2 -->(class) --> A.class.
413      v.add(aInstStr, a2InstStr);   // A -->(field) --> A2.
414
415      B bInst = new B();
416      setTag(bInst, 3);
417      aInst.foo2 = bInst;
418      String bInstStr = "3@1001";
419      String bClassStr = "1001@0";
420      v.add(bInstStr, bClassStr);  // B -->(class) --> B.class.
421      v.add(aInstStr, bInstStr);   // A -->(field) --> B.
422
423      A a3Inst = new A();
424      setTag(a3Inst, 4);
425      bInst.bar = a3Inst;
426      String a3InstStr = "4@1000";
427      v.add(a3InstStr, aClassStr);  // A3 -->(class) --> A.class.
428      v.add(bInstStr, a3InstStr);   // B -->(field) --> A3.
429
430      C cInst = new C();
431      setTag(cInst, 5);
432      bInst.bar2 = cInst;
433      String cInstStr = "5@1000";
434      String cClassStr = "1002@0";
435      v.add(cInstStr, cClassStr);  // C -->(class) --> C.class.
436      v.add(bInstStr, cInstStr);   // B -->(field) --> C.
437
438      A a4Inst = new A();
439      setTag(a4Inst, 6);
440      cInst.baz = a4Inst;
441      String a4InstStr = "6@1000";
442      v.add(a4InstStr, aClassStr);  // A4 -->(class) --> A.class.
443      v.add(cInstStr, a4InstStr);   // C -->(field) --> A4.
444
445      cInst.baz2 = aInst;
446      v.add(cInstStr, aInstStr);  // C -->(field) --> A.
447
448      A[] aArray = new A[2];
449      setTag(aArray, 500);
450      aArray[1] = a2Inst;
451      cInst.array = aArray;
452      String aArrayStr = "500@0";
453      v.add(cInstStr, aArrayStr);
454      v.add(aArrayStr, a2InstStr);
455
456      return aInst;
457    }
458  }
459
460  public static class A {
461    public A foo;
462    public A foo2;
463
464    public A() {}
465    public A(A a, A b) {
466      foo = a;
467      foo2 = b;
468    }
469  }
470
471  public static class B extends A {
472    public A bar;
473    public A bar2;
474
475    public B() {}
476    public B(A a, A b) {
477      bar = a;
478      bar2 = b;
479    }
480  }
481
482  public static interface I1 {
483    public final static int i1Field = 1;
484  }
485
486  public static interface I2 extends I1 {
487    public final static int i2Field = 2;
488  }
489
490  public static class C extends B implements I2 {
491    public A baz;
492    public A baz2;
493    public A[] array;
494
495    public C() {}
496    public C(A a, A b) {
497      baz = a;
498      baz2 = b;
499    }
500  }
501
502  private static interface Inf1 {
503    public final static int A = 1;
504  }
505
506  private static interface Inf2 extends Inf1 {
507    public final static int B = 1;
508  }
509
510  private static class IntObject implements Inf1 {
511    byte b = (byte)1;
512    char c= 'a';
513    short s = (short)2;
514    int i = 3;
515    long l = 4;
516    Object o = new Object();
517    static int sI = 5;
518  }
519
520  private static class FloatObject extends IntObject implements Inf2 {
521    float f = 1.23f;
522    double d = 1.23;
523    Object p = new Object();
524    static int sI = 6;
525  }
526
527  public static class Verifier {
528    // Should roots with vreg=-1 be printed?
529    public final static boolean PRINT_ROOTS_WITH_UNKNOWN_VREG = false;
530
531    public static class Node {
532      public String referrer;
533
534      public HashSet<String> referrees = new HashSet<>();
535
536      public Node(String r) {
537        referrer = r;
538      }
539
540      public boolean isRoot() {
541        return referrer.startsWith("root@");
542      }
543    }
544
545    HashMap<String, Node> nodes = new HashMap<>();
546
547    public Verifier() {
548    }
549
550    public void add(String referrer, String referree) {
551      if (!nodes.containsKey(referrer)) {
552        nodes.put(referrer, new Node(referrer));
553      }
554      if (referree != null) {
555        nodes.get(referrer).referrees.add(referree);
556      }
557    }
558
559    public void process(String[] lines, String additionalEnabledReferrer, boolean filtered) {
560      // This method isn't optimal. The loops could be merged. However, it's more readable if
561      // the different parts are separated.
562
563      ArrayList<String> rootLines = new ArrayList<>();
564      ArrayList<String> nonRootLines = new ArrayList<>();
565
566      // Check for consecutive chunks of referrers. Also ensure roots come first.
567      {
568        String currentHead = null;
569        boolean rootsDone = false;
570        HashSet<String> completedReferrers = new HashSet<>();
571        for (String l : lines) {
572          String referrer = getReferrer(l);
573
574          if (isRoot(referrer)) {
575            if (rootsDone) {
576              System.out.println("ERROR: Late root " + l);
577              print(lines);
578              return;
579            }
580            rootLines.add(l);
581            continue;
582          }
583
584          rootsDone = true;
585
586          if (currentHead == null) {
587            currentHead = referrer;
588          } else {
589            // Ignore 0@0, as it can happen at any time (as it stands for all other objects).
590            if (!currentHead.equals(referrer) && !referrer.equals("0@0")) {
591              completedReferrers.add(currentHead);
592              currentHead = referrer;
593              if (completedReferrers.contains(referrer)) {
594                System.out.println("Non-contiguous referrer " + l);
595                print(lines);
596                return;
597              }
598            }
599          }
600          nonRootLines.add(l);
601        }
602      }
603
604      // Sort (root order is not specified) and print the roots.
605      // TODO: What about extra roots? JNI and the interpreter seem to introduce those (though it
606      //       isn't clear why a debuggable-AoT test doesn't have the same, at least for locals).
607      //       For now, swallow duplicates, and resolve once we have the metadata for the roots.
608      {
609        Collections.sort(rootLines);
610        String lastRoot = null;
611        for (String l : rootLines) {
612          if (lastRoot != null && lastRoot.equals(l)) {
613            continue;
614          }
615          lastRoot = l;
616          if (!PRINT_ROOTS_WITH_UNKNOWN_VREG && l.indexOf("vreg=-1") > 0) {
617            continue;
618          }
619          System.out.println(l);
620        }
621      }
622
623      if (filtered) {
624        // If we aren't tracking dependencies, just sort the lines and print.
625        // TODO: As the verifier is currently using the output lines to track dependencies, we
626        //       cannot verify that output is correct when parts of it are suppressed by filters.
627        //       To correctly track this we need to take node information into account, and
628        //       actually analyze the graph.
629        Collections.sort(nonRootLines);
630        for (String l : nonRootLines) {
631          System.out.println(l);
632        }
633
634        System.out.println("---");
635        return;
636      }
637
638      // Iterate through the lines, keeping track of which referrers are visited, to ensure the
639      // order is acceptable.
640      HashSet<String> enabled = new HashSet<>();
641      if (additionalEnabledReferrer != null) {
642        enabled.add(additionalEnabledReferrer);
643      }
644      // Always add "0@0".
645      enabled.add("0@0");
646
647      for (String l : lines) {
648        String referrer = getReferrer(l);
649        String referree = getReferree(l);
650        if (isRoot(referrer)) {
651          // For a root src, just enable the referree.
652          enabled.add(referree);
653        } else {
654          // Check that the referrer is enabled (may be visited).
655          if (!enabled.contains(referrer)) {
656            System.out.println("Referrer " + referrer + " not enabled: " + l);
657            print(lines);
658            return;
659          }
660          enabled.add(referree);
661        }
662      }
663
664      // Now just sort the non-root lines and output them
665      Collections.sort(nonRootLines);
666      for (String l : nonRootLines) {
667        System.out.println(l);
668      }
669
670      System.out.println("---");
671    }
672
673    public static boolean isRoot(String ref) {
674      return ref.startsWith("root@");
675    }
676
677    private static String getReferrer(String line) {
678      int i = line.indexOf(" --");
679      if (i <= 0) {
680        throw new IllegalArgumentException(line);
681      }
682      int j = line.indexOf(' ');
683      if (i != j) {
684        throw new IllegalArgumentException(line);
685      }
686      return line.substring(0, i);
687    }
688
689    private static String getReferree(String line) {
690      int i = line.indexOf("--> ");
691      if (i <= 0) {
692        throw new IllegalArgumentException(line);
693      }
694      int j = line.indexOf(' ', i + 4);
695      if (j < 0) {
696        throw new IllegalArgumentException(line);
697      }
698      return line.substring(i + 4, j);
699    }
700
701    private static void print(String[] lines) {
702      for (String l : lines) {
703        System.out.println(l);
704      }
705    }
706  }
707
708  private static void setTag(Object o, long tag) {
709    Main.setTag(o, tag);
710  }
711  private static long getTag(Object o) {
712    return Main.getTag(o);
713  }
714
715  private static native void setupGcCallback();
716  private static native void enableGcTracking(boolean enable);
717  private static native int getGcStarts();
718  private static native int getGcFinishes();
719  private static native void forceGarbageCollection();
720
721  private static native void checkForExtensionApis();
722  private static native int getObjectHeapId(long tag);
723  private static native String getHeapName(int heapId);
724  private static native void checkGetObjectHeapIdInCallback(long tag, int heapId);
725
726  public static native String[] followReferences(int heapFilter, Class<?> klassFilter,
727      Object initialObject, int stopAfter, int followSet, Object jniRef);
728  public static native String[] followReferencesString(Object initialObject);
729  public static native String followReferencesPrimitiveArray(Object initialObject);
730  public static native String followReferencesPrimitiveFields(Object initialObject);
731
732  private static native void iterateThroughHeapExt();
733}
734