/* * Copyright (C) 2017 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.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Vector; import java.util.function.Function; public class Test990 { // Fields of these classes are accessed/modified differently in the RI and ART so we ignore them. static Collection> IGNORED_CLASSES = Arrays.asList(new Class[] { ClassLoader.class, Vector.class, }); static interface Printable { public void Print(); } static final class FieldWrite implements Printable { private Executable method; private Object target; private Field f; private String initialValue; private Class initialValueType; public FieldWrite(Executable method, Object target, Field f, Object v) { this.method = method; this.target = target; this.f = f; this.initialValue = genericToString(v); this.initialValueType = v != null ? v.getClass() : null; } @Override public void Print() { System.out.println("MODIFY of " + f + " on object of" + " type: " + (target == null ? null : target.getClass()) + " in method " + method + ". New value: " + initialValue + " (type: " + initialValueType + ")"); } } static final class FieldRead implements Printable { private Executable method; private Object target; private Field f; public FieldRead(Executable method, Object target, Field f) { this.method = method; this.target = target; this.f = f; } @Override public void Print() { System.out.println("ACCESS of " + f + " on object of" + " type " + (target == null ? null : target.getClass()) + " in method " + method); } } private static String genericToString(Object val) { if (val == null) { return "null"; } else if (val.getClass().isArray()) { return arrayToString(val); } else if (val instanceof Throwable) { StringWriter w = new StringWriter(); ((Throwable) val).printStackTrace(new PrintWriter(w)); return w.toString(); } else { return val.toString(); } } private static String charArrayToString(char[] src) { String[] res = new String[src.length]; for (int i = 0; i < src.length; i++) { if (Character.isISOControl(src[i])) { res[i] = Character.getName(src[i]); } else { res[i] = Character.toString(src[i]); } } return Arrays.toString(res); } private static String arrayToString(Object val) { Class klass = val.getClass(); if ((new Object[0]).getClass().isAssignableFrom(klass)) { return Arrays.toString( Arrays.stream((Object[])val).map(new Function() { public String apply(Object o) { return genericToString(o); } }).toArray()); } else if ((new byte[0]).getClass().isAssignableFrom(klass)) { return Arrays.toString((byte[])val); } else if ((new char[0]).getClass().isAssignableFrom(klass)) { return charArrayToString((char[])val); } else if ((new short[0]).getClass().isAssignableFrom(klass)) { return Arrays.toString((short[])val); } else if ((new int[0]).getClass().isAssignableFrom(klass)) { return Arrays.toString((int[])val); } else if ((new long[0]).getClass().isAssignableFrom(klass)) { return Arrays.toString((long[])val); } else if ((new float[0]).getClass().isAssignableFrom(klass)) { return Arrays.toString((float[])val); } else if ((new double[0]).getClass().isAssignableFrom(klass)) { return Arrays.toString((double[])val); } else { throw new Error("Unknown type " + klass); } } private static List results = new ArrayList<>(); public static void notifyFieldModify( Executable m, long location, Class f_klass, Object target, Field f, Object value) { if (IGNORED_CLASSES.contains(f_klass)) { return; } results.add(new FieldWrite(m, target, f, value)); } public static void notifyFieldAccess( Executable m, long location, Class f_klass, Object target, Field f) { if (IGNORED_CLASSES.contains(f_klass)) { return; } results.add(new FieldRead(m, target, f)); } static class TestClass1 { Object abc; int xyz; int foobar; public TestClass1(int xyz, Object abc) { this.xyz = xyz; this.abc = abc; } public void tweak(int def) { if (def == xyz) { foobar++; } } public String toString() { return "TestClass1 { abc: \"" + genericToString(abc) + "\", xyz: " + xyz + ", foobar: " + foobar + " }"; } } static class TestClass2 extends TestClass1 { static long TOTAL = 0; long baz; public TestClass2(long baz) { super(1337, "TESTING"); this.baz = baz; } public void tweak(int def) { TOTAL++; super.tweak(def); baz++; } public String toString() { return "TestClass2 { super: \"%s\", TOTAL: %d, baz: %d }".format( super.toString(), TOTAL, baz); } } public static void run() throws Exception { Trace.disableTracing(Thread.currentThread()); Trace.enableFieldTracing( Test990.class, Test990.class.getDeclaredMethod("notifyFieldAccess", Executable.class, Long.TYPE, Class.class, Object.class, Field.class), Test990.class.getDeclaredMethod("notifyFieldModify", Executable.class, Long.TYPE, Class.class, Object.class, Field.class, Object.class), Thread.currentThread()); Trace.watchAllFieldAccesses(); Trace.watchAllFieldModifications(); TestClass1 t1 = new TestClass1(1, "tc1"); TestClass1 t2 = new TestClass2(2); TestClass1 t3 = new TestClass1(3, t1); TestClass1 t4 = new TestClass1(4, t2); t1.tweak(1); t1.tweak(1); t2.tweak(12); t2.tweak(1337); t2.tweak(12); t2.tweak(1338); t1.tweak(t3.foobar); t4.tweak((int)((TestClass2)t2).baz); t4.tweak((int)TestClass2.TOTAL); t2.tweak((int)TestClass2.TOTAL); // Turn off tracing so we don't have to deal with print internals. Trace.disableTracing(Thread.currentThread()); printResults(); } public static void printResults() { for (Printable p : results) { p.Print(); } } }