/* * Copyright (C) 2016 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.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.concurrent.CountDownLatch; public class Test913 { public static void run() throws Exception { doTest(); // Use a countdown latch for synchronization, as join() will introduce more roots. final CountDownLatch cdl1 = new CountDownLatch(1); // Run the follow-references tests on a dedicated thread so we know the specific Thread type. Thread t = new Thread() { @Override public void run() { try { Test913.runFollowReferences(); } catch (Exception e) { throw new RuntimeException(e); } cdl1.countDown(); } }; t.start(); cdl1.await(); doExtensionTests(); } public static void runFollowReferences() throws Exception { new TestConfig().doFollowReferencesTest(); Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); new TestConfig(null, 0, 1, -1).doFollowReferencesTest(); Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); new TestConfig(null, 0, Integer.MAX_VALUE, 1).doFollowReferencesTest(); Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); doStringTest(); Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); doPrimitiveArrayTest(); doPrimitiveFieldTest(); Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); // Test klass filter. System.out.println("--- klass ---"); new TestConfig(A.class, 0).doFollowReferencesTest(); // Test heap filter. System.out.println("--- heap_filter ---"); System.out.println("---- tagged objects"); new TestConfig(null, 0x4).doFollowReferencesTest(); System.out.println("---- untagged objects"); new TestConfig(null, 0x8).doFollowReferencesTest(); System.out.println("---- tagged classes"); new TestConfig(null, 0x10).doFollowReferencesTest(); System.out.println("---- untagged classes"); new TestConfig(null, 0x20).doFollowReferencesTest(); } public static void doTest() throws Exception { setupGcCallback(); enableGcTracking(true); runGc(); enableGcTracking(false); } public static void doStringTest() throws Exception { final String str = new String("HelloWorld"); final String str2 = new String(""); Object o = new Object() { String s = str; String s2 = str2; }; setTag(str, 1); setTag(str2, 2); System.out.println(Arrays.toString(followReferencesString(o))); System.out.println(getTag(str)); System.out.println(getTag(str2)); } public static void doPrimitiveArrayTest() throws Exception { final boolean[] zArray = new boolean[] { false, true }; setTag(zArray, 1); final byte[] bArray = new byte[] { 1, 2, 3 }; setTag(bArray, 2); final char[] cArray = new char[] { 'A', 'Z' }; setTag(cArray, 3); final short[] sArray = new short[] { 1, 2, 3 }; setTag(sArray, 4); final int[] iArray = new int[] { 1, 2, 3 }; setTag(iArray, 5); final float[] fArray = new float[] { 0.0f, 1.0f }; setTag(fArray, 6); final long[] lArray = new long[] { 1, 2, 3 }; setTag(lArray, 7); final double[] dArray = new double[] { 0.0, 1.0 }; setTag(dArray, 8); Object o = new Object() { Object z = zArray; Object b = bArray; Object c = cArray; Object s = sArray; Object i = iArray; Object f = fArray; Object l = lArray; Object d = dArray; }; System.out.println(followReferencesPrimitiveArray(o)); System.out.print(getTag(zArray)); System.out.print(getTag(bArray)); System.out.print(getTag(cArray)); System.out.print(getTag(sArray)); System.out.print(getTag(iArray)); System.out.print(getTag(fArray)); System.out.print(getTag(lArray)); System.out.println(getTag(dArray)); } public static void doPrimitiveFieldTest() throws Exception { // Force GCs to clean up dirt. Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); doTestPrimitiveFieldsClasses(); doTestPrimitiveFieldsIntegral(); // Force GCs to clean up dirt. Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); doTestPrimitiveFieldsFloat(); // Force GCs to clean up dirt. Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); } private static void doTestPrimitiveFieldsClasses() { setTag(IntObject.class, 10000); System.out.println(followReferencesPrimitiveFields(IntObject.class)); System.out.println(getTag(IntObject.class)); setTag(IntObject.class, 0); setTag(FloatObject.class, 10000); System.out.println(followReferencesPrimitiveFields(FloatObject.class)); System.out.println(getTag(FloatObject.class)); setTag(FloatObject.class, 0); boolean correctHeapValue = false; setTag(Inf1.class, 10000); String heapTrace = followReferencesPrimitiveFields(Inf1.class); if (!checkInitialized(Inf1.class)) { correctHeapValue = heapTrace.equals("10000@0 (static, int, index=0) 0000000000000000"); } else { correctHeapValue = heapTrace.equals("10000@0 (static, int, index=0) 0000000000000001"); } if (!correctHeapValue) System.out.println("Heap Trace for Inf1 is not as expected:\n" + heapTrace); System.out.println(getTag(Inf1.class)); setTag(Inf1.class, 0); setTag(Inf2.class, 10000); heapTrace = followReferencesPrimitiveFields(Inf2.class); if (!checkInitialized(Inf2.class)) { correctHeapValue = heapTrace.equals("10000@0 (static, int, index=1) 0000000000000000"); } else { correctHeapValue = heapTrace.equals("10000@0 (static, int, index=1) 0000000000000001"); } if (!correctHeapValue) System.out.println("Heap Trace for Inf2 is not as expected:\n" + heapTrace); System.out.println(getTag(Inf2.class)); setTag(Inf2.class, 0); } private static void doTestPrimitiveFieldsIntegral() { IntObject intObject = new IntObject(); setTag(intObject, 10000); System.out.println(followReferencesPrimitiveFields(intObject)); System.out.println(getTag(intObject)); } private static void doTestPrimitiveFieldsFloat() { FloatObject floatObject = new FloatObject(); setTag(floatObject, 10000); System.out.println(followReferencesPrimitiveFields(floatObject)); System.out.println(getTag(floatObject)); } static ArrayList extensionTestHolder; private static void doExtensionTests() { checkForExtensionApis(); extensionTestHolder = new ArrayList<>(); System.out.println(); try { getHeapName(-1); System.out.println("Expected failure for -1"); } catch (Exception e) { } System.out.println(getHeapName(0)); System.out.println(getHeapName(1)); System.out.println(getHeapName(2)); System.out.println(getHeapName(3)); try { getHeapName(4); System.out.println("Expected failure for -1"); } catch (Exception e) { } System.out.println(); setTag(Object.class, 100000); int objectClassHeapId = getObjectHeapId(100000); int objClassExpectedHeapId = hasImage() ? 1 : 3; if (objectClassHeapId != objClassExpectedHeapId) { throw new RuntimeException("Expected object class in heap " + objClassExpectedHeapId + " but received " + objectClassHeapId); } A a = new A(); extensionTestHolder.add(a); setTag(a, 100001); System.out.println(getObjectHeapId(100001)); checkGetObjectHeapIdInCallback(100000, objClassExpectedHeapId); checkGetObjectHeapIdInCallback(100001, 3); long baseTag = 30000000; setTag(Object.class, baseTag + objClassExpectedHeapId); setTag(Class.class, baseTag + objClassExpectedHeapId); Object o = new Object(); extensionTestHolder.add(o); setTag(o, baseTag + 3); iterateThroughHeapExt(); extensionTestHolder = null; } private static void runGc() { clearStats(); forceGarbageCollection(); printStats(); } private static void clearStats() { getGcStarts(); getGcFinishes(); } private static void printStats() { System.out.println("---"); int s = getGcStarts(); int f = getGcFinishes(); System.out.println((s > 0) + " " + (f > 0)); } private static boolean hasImage() { try { int pid = Integer.parseInt(new File("/proc/self").getCanonicalFile().getName()); BufferedReader reader = new BufferedReader(new FileReader("/proc/" + pid + "/maps")); String line; while ((line = reader.readLine()) != null) { if (line.endsWith(".art")) { reader.close(); return true; } } reader.close(); return false; } catch (Exception e) { throw new RuntimeException(e); } } private static class TestConfig { private Class klass = null; private int heapFilter = 0; private int stopAfter = Integer.MAX_VALUE; private int followSet = -1; public TestConfig() { } public TestConfig(Class klass, int heapFilter) { this.klass = klass; this.heapFilter = heapFilter; } public TestConfig(Class klass, int heapFilter, int stopAfter, int followSet) { this.klass = klass; this.heapFilter = heapFilter; this.stopAfter = stopAfter; this.followSet = followSet; } public void doFollowReferencesTest() throws Exception { // Force GCs to clean up dirt. Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); setTag(Thread.currentThread(), 3000); { ArrayList tmpStorage = new ArrayList<>(); doFollowReferencesTestNonRoot(tmpStorage); tmpStorage = null; } // Force GCs to clean up dirt. Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); doFollowReferencesTestRoot(); // Force GCs to clean up dirt. Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); } private void doFollowReferencesTestNonRoot(ArrayList tmpStorage) { Verifier v = new Verifier(); tagClasses(v); A a = createTree(v); tmpStorage.add(a); v.add("0@0", "1@1000"); // tmpStorage[0] --(array-element)--> a. doFollowReferencesTestImpl(null, stopAfter, followSet, null, v, null); doFollowReferencesTestImpl(a.foo2, stopAfter, followSet, null, v, "3@1001"); tmpStorage.clear(); } private void doFollowReferencesTestRoot() { Verifier v = new Verifier(); tagClasses(v); A a = createTree(v); doFollowReferencesTestImpl(null, stopAfter, followSet, a, v, null); doFollowReferencesTestImpl(a.foo2, stopAfter, followSet, a, v, "3@1001"); } private void doFollowReferencesTestImpl(A root, int stopAfter, int followSet, Object asRoot, Verifier v, String additionalEnabled) { String[] lines = followReferences(heapFilter, klass, root, stopAfter, followSet, asRoot); v.process(lines, additionalEnabled, heapFilter != 0 || klass != null); } private static void tagClasses(Verifier v) { setTag(A.class, 1000); registerClass(1000, A.class); setTag(B.class, 1001); registerClass(1001, B.class); v.add("1001@0", "1000@0"); // B.class --(superclass)--> A.class. setTag(C.class, 1002); registerClass(1002, C.class); v.add("1002@0", "1001@0"); // C.class --(superclass)--> B.class. v.add("1002@0", "2001@0"); // C.class --(interface)--> I2.class. setTag(I1.class, 2000); registerClass(2000, I1.class); setTag(I2.class, 2001); registerClass(2001, I2.class); v.add("2001@0", "2000@0"); // I2.class --(interface)--> I1.class. } private static A createTree(Verifier v) { A aInst = new A(); setTag(aInst, 1); String aInstStr = "1@1000"; String aClassStr = "1000@0"; v.add(aInstStr, aClassStr); // A -->(class) --> A.class. A a2Inst = new A(); setTag(a2Inst, 2); aInst.foo = a2Inst; String a2InstStr = "2@1000"; v.add(a2InstStr, aClassStr); // A2 -->(class) --> A.class. v.add(aInstStr, a2InstStr); // A -->(field) --> A2. B bInst = new B(); setTag(bInst, 3); aInst.foo2 = bInst; String bInstStr = "3@1001"; String bClassStr = "1001@0"; v.add(bInstStr, bClassStr); // B -->(class) --> B.class. v.add(aInstStr, bInstStr); // A -->(field) --> B. A a3Inst = new A(); setTag(a3Inst, 4); bInst.bar = a3Inst; String a3InstStr = "4@1000"; v.add(a3InstStr, aClassStr); // A3 -->(class) --> A.class. v.add(bInstStr, a3InstStr); // B -->(field) --> A3. C cInst = new C(); setTag(cInst, 5); bInst.bar2 = cInst; String cInstStr = "5@1000"; String cClassStr = "1002@0"; v.add(cInstStr, cClassStr); // C -->(class) --> C.class. v.add(bInstStr, cInstStr); // B -->(field) --> C. A a4Inst = new A(); setTag(a4Inst, 6); cInst.baz = a4Inst; String a4InstStr = "6@1000"; v.add(a4InstStr, aClassStr); // A4 -->(class) --> A.class. v.add(cInstStr, a4InstStr); // C -->(field) --> A4. cInst.baz2 = aInst; v.add(cInstStr, aInstStr); // C -->(field) --> A. A[] aArray = new A[2]; setTag(aArray, 500); aArray[1] = a2Inst; cInst.array = aArray; String aArrayStr = "500@0"; v.add(cInstStr, aArrayStr); v.add(aArrayStr, a2InstStr); return aInst; } } public static class A { public A foo; public A foo2; public A() {} public A(A a, A b) { foo = a; foo2 = b; } } public static class B extends A { public A bar; public A bar2; public B() {} public B(A a, A b) { bar = a; bar2 = b; } } public static interface I1 { public final static int i1Field = 1; } public static interface I2 extends I1 { public final static int i2Field = 2; } public static class C extends B implements I2 { public A baz; public A baz2; public A[] array; public C() {} public C(A a, A b) { baz = a; baz2 = b; } } private static interface Inf1 { public final static int A = 1; } private static interface Inf2 extends Inf1 { public final static int B = 1; } private static class IntObject implements Inf1 { byte b = (byte)1; char c= 'a'; short s = (short)2; int i = 3; long l = 4; Object o = new Object(); static int sI = 5; } private static class FloatObject extends IntObject implements Inf2 { float f = 1.23f; double d = 1.23; Object p = new Object(); static int sI = 6; } public static class Verifier { // Should roots with vreg=-1 be printed? public final static boolean PRINT_ROOTS_WITH_UNKNOWN_VREG = false; public static class Node { public String referrer; public HashSet referrees = new HashSet<>(); public Node(String r) { referrer = r; } public boolean isRoot() { return referrer.startsWith("root@"); } } HashMap nodes = new HashMap<>(); public Verifier() { } public void add(String referrer, String referree) { if (!nodes.containsKey(referrer)) { nodes.put(referrer, new Node(referrer)); } if (referree != null) { nodes.get(referrer).referrees.add(referree); } } public void process(String[] lines, String additionalEnabledReferrer, boolean filtered) { // This method isn't optimal. The loops could be merged. However, it's more readable if // the different parts are separated. ArrayList rootLines = new ArrayList<>(); ArrayList nonRootLines = new ArrayList<>(); // Check for consecutive chunks of referrers. Also ensure roots come first. { String currentHead = null; boolean rootsDone = false; HashSet completedReferrers = new HashSet<>(); for (String l : lines) { String referrer = getReferrer(l); if (isRoot(referrer)) { if (rootsDone) { System.out.println("ERROR: Late root " + l); print(lines); return; } rootLines.add(l); continue; } rootsDone = true; if (currentHead == null) { currentHead = referrer; } else { // Ignore 0@0, as it can happen at any time (as it stands for all other objects). if (!currentHead.equals(referrer) && !referrer.equals("0@0")) { completedReferrers.add(currentHead); currentHead = referrer; if (completedReferrers.contains(referrer)) { System.out.println("Non-contiguous referrer " + l); print(lines); return; } } } nonRootLines.add(l); } } // Sort (root order is not specified) and print the roots. // TODO: What about extra roots? JNI and the interpreter seem to introduce those (though it // isn't clear why a debuggable-AoT test doesn't have the same, at least for locals). // For now, swallow duplicates, and resolve once we have the metadata for the roots. { Collections.sort(rootLines); String lastRoot = null; for (String l : rootLines) { if (lastRoot != null && lastRoot.equals(l)) { continue; } lastRoot = l; if (!PRINT_ROOTS_WITH_UNKNOWN_VREG && l.indexOf("vreg=-1") > 0) { continue; } System.out.println(l); } } if (filtered) { // If we aren't tracking dependencies, just sort the lines and print. // TODO: As the verifier is currently using the output lines to track dependencies, we // cannot verify that output is correct when parts of it are suppressed by filters. // To correctly track this we need to take node information into account, and // actually analyze the graph. Collections.sort(nonRootLines); for (String l : nonRootLines) { System.out.println(l); } System.out.println("---"); return; } // Iterate through the lines, keeping track of which referrers are visited, to ensure the // order is acceptable. HashSet enabled = new HashSet<>(); if (additionalEnabledReferrer != null) { enabled.add(additionalEnabledReferrer); } // Always add "0@0". enabled.add("0@0"); for (String l : lines) { String referrer = getReferrer(l); String referree = getReferree(l); if (isRoot(referrer)) { // For a root src, just enable the referree. enabled.add(referree); } else { // Check that the referrer is enabled (may be visited). if (!enabled.contains(referrer)) { System.out.println("Referrer " + referrer + " not enabled: " + l); print(lines); return; } enabled.add(referree); } } // Now just sort the non-root lines and output them Collections.sort(nonRootLines); for (String l : nonRootLines) { System.out.println(l); } System.out.println("---"); } public static boolean isRoot(String ref) { return ref.startsWith("root@"); } private static String getReferrer(String line) { int i = line.indexOf(" --"); if (i <= 0) { throw new IllegalArgumentException(line); } int j = line.indexOf(' '); if (i != j) { throw new IllegalArgumentException(line); } return line.substring(0, i); } private static String getReferree(String line) { int i = line.indexOf("--> "); if (i <= 0) { throw new IllegalArgumentException(line); } int j = line.indexOf(' ', i + 4); if (j < 0) { throw new IllegalArgumentException(line); } return line.substring(i + 4, j); } private static void print(String[] lines) { for (String l : lines) { System.out.println(l); } } } private static void setTag(Object o, long tag) { Main.setTag(o, tag); } private static long getTag(Object o) { return Main.getTag(o); } private static native boolean checkInitialized(Class klass); private static native void setupGcCallback(); private static native void enableGcTracking(boolean enable); private static native int getGcStarts(); private static native int getGcFinishes(); private static native void forceGarbageCollection(); private static native void checkForExtensionApis(); private static native int getObjectHeapId(long tag); private static native String getHeapName(int heapId); private static native void checkGetObjectHeapIdInCallback(long tag, int heapId); public static native String[] followReferences(int heapFilter, Class klassFilter, Object initialObject, int stopAfter, int followSet, Object jniRef); public static native String[] followReferencesString(Object initialObject); public static native String followReferencesPrimitiveArray(Object initialObject); public static native String followReferencesPrimitiveFields(Object initialObject); private static native void iterateThroughHeapExt(); private static native void registerClass(long tag, Object obj); }