1 /*
2  * Copyright (C) 2017 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.io.PrintWriter;
20 import java.io.StringWriter;
21 import java.lang.reflect.Executable;
22 import java.lang.reflect.InvocationHandler;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.Proxy;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30 import java.util.function.Function;
31 import java.util.function.IntUnaryOperator;
32 import java.util.function.Predicate;
33 
34 public class Test988 {
35 
36     // Methods with non-deterministic output that should not be printed.
37     static List<Predicate<Executable>> NON_DETERMINISTIC_OUTPUT_METHODS = new ArrayList<>();
38     static List<Predicate<Executable>> NON_DETERMINISTIC_OUTPUT_TYPE_METHODS = new ArrayList<>();
39     static List<Class<?>> NON_DETERMINISTIC_TYPE_NAMES = new ArrayList<>();
40 
41     static Predicate<Executable> IS_NON_DETERMINISTIC_OUTPUT =
42         (x) -> NON_DETERMINISTIC_OUTPUT_METHODS.stream().anyMatch((pred) -> pred.test(x));
43     static Predicate<Executable> IS_NON_DETERMINISTIC_OUTPUT_TYPE =
44         (x) -> NON_DETERMINISTIC_OUTPUT_TYPE_METHODS.stream().anyMatch((pred) -> pred.test(x));
45 
EqPred(Executable m)46     public static final Predicate<Executable> EqPred(Executable m) {
47       return (Executable n) -> n.equals(m);
48     }
49 
50     static {
51       // Throwable.nativeFillInStackTrace is only on android and hiddenapi so we
52       // should avoid trying to find it at all.
NON_DETERMINISTIC_OUTPUT_METHODS.add(Executable ex)53       NON_DETERMINISTIC_OUTPUT_METHODS.add(
54         (Executable ex) -> {
55           return ex.getDeclaringClass().equals(Throwable.class)
56               && ex.getName().equals("nativeFillInStackTrace");
57         });
58       try {
59         NON_DETERMINISTIC_OUTPUT_METHODS.add(
60             EqPred(Thread.class.getDeclaredMethod("currentThread")));
61         NON_DETERMINISTIC_OUTPUT_TYPE_METHODS.add(
62             EqPred(Thread.class.getDeclaredMethod("currentThread")));
63       } catch (Exception e) {}
64       try {
65         NON_DETERMINISTIC_TYPE_NAMES.add(
Proxy.getProxyClass()66             Proxy.getProxyClass(Test988.class.getClassLoader(), new Class[] { Runnable.class }));
67       } catch (Exception e) {}
68     }
69 
70     static interface Printable {
Print()71         public void Print();
72     }
73 
74     static final class MethodEntry implements Printable {
75         private Executable m;
76         private int cnt;
MethodEntry(Executable m, int cnt)77         public MethodEntry(Executable m, int cnt) {
78             this.m = m;
79             this.cnt = cnt;
80         }
81         @Override
Print()82         public void Print() {
83             System.out.println(whitespace(cnt) + "=> " + methodToString(m));
84         }
85     }
86 
genericToString(Object val)87     private static String genericToString(Object val) {
88       if (val == null) {
89         return "null";
90       } else if (val.getClass().isArray()) {
91         return arrayToString(val);
92       } else if (val instanceof Throwable) {
93         StringWriter w = new StringWriter();
94         Throwable thr = ((Throwable) val);
95         w.write(thr.getClass().getName() + ": " + thr.getMessage() + "\n");
96         for (StackTraceElement e : thr.getStackTrace()) {
97           if (e.getClassName().startsWith("art.")) {
98             w.write("\t" + e + "\n");
99           } else {
100             w.write("\t<additional hidden frames>\n");
101             break;
102           }
103         }
104         return w.toString();
105       } else {
106         return val.toString();
107       }
108     }
109 
charArrayToString(char[] src)110     private static String charArrayToString(char[] src) {
111       String[] res = new String[src.length];
112       for (int i = 0; i < src.length; i++) {
113         if (Character.isISOControl(src[i])) {
114           res[i] = Character.getName(src[i]);
115         } else {
116           res[i] = Character.toString(src[i]);
117         }
118       }
119       return Arrays.toString(res);
120     }
121 
arrayToString(Object val)122     private static String arrayToString(Object val) {
123       Class<?> klass = val.getClass();
124       if ((new Object[0]).getClass().isAssignableFrom(klass)) {
125         return Arrays.toString(
126             Arrays.stream((Object[])val).map(new Function<Object, String>() {
127               public String apply(Object o) {
128                 return Test988.genericToString(o);
129               }
130             }).toArray());
131       } else if ((new byte[0]).getClass().isAssignableFrom(klass)) {
132         return Arrays.toString((byte[])val);
133       } else if ((new char[0]).getClass().isAssignableFrom(klass)) {
134         return charArrayToString((char[])val);
135       } else if ((new short[0]).getClass().isAssignableFrom(klass)) {
136         return Arrays.toString((short[])val);
137       } else if ((new int[0]).getClass().isAssignableFrom(klass)) {
138         return Arrays.toString((int[])val);
139       } else if ((new long[0]).getClass().isAssignableFrom(klass)) {
140         return Arrays.toString((long[])val);
141       } else if ((new float[0]).getClass().isAssignableFrom(klass)) {
142         return Arrays.toString((float[])val);
143       } else if ((new double[0]).getClass().isAssignableFrom(klass)) {
144         return Arrays.toString((double[])val);
145       } else {
146         throw new Error("Unknown type " + klass);
147       }
148     }
149 
150     static String methodToString(Executable m) {
151       // Make the output more similar between ART and RI,
152       // by removing the 'native' specifier from methods.
153       String methodStr;
154       if (NON_DETERMINISTIC_TYPE_NAMES.contains(m.getDeclaringClass())) {
155         methodStr = m.toString().replace(m.getDeclaringClass().getName(),
156             "<non-deterministic-type " +
157             NON_DETERMINISTIC_TYPE_NAMES.indexOf(m.getDeclaringClass()) +
158             ">");
159       } else {
160         methodStr = m.toString();
161       }
162       return methodStr.replaceFirst(" native", "");
163     }
164 
165     static final class MethodReturn implements Printable {
166         private Executable m;
167         private Object val;
168         private int cnt;
169         public MethodReturn(Executable m, Object val, int cnt) {
170             this.m = m;
171             this.val = val;
172             this.cnt = cnt;
173         }
174         @Override
175         public void Print() {
176             String print;
177             if (IS_NON_DETERMINISTIC_OUTPUT.test(m)) {
178                 print = "<non-deterministic>";
179             } else {
180                 print = genericToString(val);
181             }
182             Class<?> klass = null;
183             if (val != null) {
184               klass = val.getClass();
185             }
186             String klass_print;
187             if (klass == null) {
188               klass_print =  "null";
189             } else if (NON_DETERMINISTIC_TYPE_NAMES.contains(klass)) {
190               klass_print = "<non-deterministic-class " +
191                   NON_DETERMINISTIC_TYPE_NAMES.indexOf(klass) + ">";
192             } else if (IS_NON_DETERMINISTIC_OUTPUT_TYPE.test(m)) {
193               klass_print = "<non-deterministic>";
194             } else {
195               klass_print = klass.toString();
196             }
197             System.out.println(
198                 whitespace(cnt) + "<= " + methodToString(m) + " -> <" + klass_print + ": " + print + ">");
199         }
200     }
201 
202     static final class MethodThrownThrough implements Printable {
203         private Executable m;
204         private int cnt;
205         public MethodThrownThrough(Executable m, int cnt) {
206             this.m = m;
207             this.cnt = cnt;
208         }
209         @Override
210         public void Print() {
211             System.out.println(whitespace(cnt) + "<= " + methodToString(m) + " EXCEPTION");
212         }
213     }
214 
215     private static String whitespace(int n) {
216       String out = "";
217       while (n > 0) {
218         n--;
219         out += ".";
220       }
221       return out;
222     }
223 
224     static final class FibThrow implements Printable {
225         private String format;
226         private int arg;
227         private Throwable res;
228         public FibThrow(String format, int arg, Throwable res) {
229             this.format = format;
230             this.arg = arg;
231             this.res = res;
232         }
233 
234         @Override
235         public void Print() {
236             System.out.printf(format, arg, genericToString(res));
237         }
238     }
239 
240     static final class FibResult implements Printable {
241         private String format;
242         private int arg;
243         private int res;
244         public FibResult(String format, int arg, int res) {
245             this.format = format;
246             this.arg = arg;
247             this.res = res;
248         }
249 
250         @Override
251         public void Print() {
252             System.out.printf(format, arg, res);
253         }
254     }
255 
256     private static List<Printable> results = new ArrayList<>();
257     // Starts with => enableMethodTracing
258     //             .=> enableTracing
259     private static int cnt = 2;
260 
261     // Iterative version
262     static final class IterOp implements IntUnaryOperator {
263       public int applyAsInt(int x) {
264         return iter_fibonacci(x);
265       }
266     }
267     static int iter_fibonacci(int n) {
268         if (n < 0) {
269             throw new Error("Bad argument: " + n + " < 0");
270         } else if (n == 0) {
271             return 0;
272         }
273         int x = 1;
274         int y = 1;
275         for (int i = 3; i <= n; i++) {
276             int z = x + y;
277             x = y;
278             y = z;
279         }
280         return y;
281     }
282 
283     // Recursive version
284     static final class RecurOp implements IntUnaryOperator {
285       public int applyAsInt(int x) {
286         return fibonacci(x);
287       }
288     }
289     static int fibonacci(int n) {
290         if (n < 0) {
291             throw new Error("Bad argument: " + n + " < 0");
292         } else if ((n == 0) || (n == 1)) {
293             return n;
294         } else {
295             return fibonacci(n - 1) + (fibonacci(n - 2));
296         }
297     }
298 
299     static final class NativeOp implements IntUnaryOperator {
300       public int applyAsInt(int x) {
301         return nativeFibonacci(x);
302       }
303     }
304     static native int nativeFibonacci(int n);
305 
306     static final class TestRunnableInvokeHandler implements InvocationHandler {
307       public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
308         return null;
309       }
310     }
311 
312     static final int METHOD_TRACING_IGNORE_DEPTH = 2;
313     static boolean sMethodTracingIgnore = false;
314 
315     public static void notifyMethodEntry(Executable m) {
316         // Called by native code when a method is entered. This method is ignored by the native
317         // entry and exit hooks.
318         cnt++;
319         if ((cnt - 1) > METHOD_TRACING_IGNORE_DEPTH && sMethodTracingIgnore) {
320           return;
321         }
322         results.add(new MethodEntry(m, cnt - 1));
323     }
324 
325     public static void notifyMethodExit(Executable m, boolean exception, Object result) {
326         cnt--;
327 
328         if (cnt > METHOD_TRACING_IGNORE_DEPTH && sMethodTracingIgnore) {
329           return;
330         }
331 
332         if (exception) {
333             results.add(new MethodThrownThrough(m, cnt));
334         } else {
335             results.add(new MethodReturn(m, result, cnt));
336         }
337     }
338 
339     public static void run() throws Exception {
340         // call this here so it is linked. It doesn't actually do anything here.
341         loadAllClasses();
342         Trace.disableTracing(Thread.currentThread());
343         // Call this prior to starting tracing since its implementation is so deep into reflection
344         // that it will be changing all the time and difficult to keep up with.
345         Runnable runnable = (Runnable)Proxy.newProxyInstance(
346                     Test988.class.getClassLoader(),
347                     new Class[]{ Runnable.class },
348                     new TestRunnableInvokeHandler());
349         Trace.enableMethodTracing(
350             Test988.class,
351             Test988.class.getDeclaredMethod("notifyMethodEntry", Executable.class),
352             Test988.class.getDeclaredMethod(
353                 "notifyMethodExit", Executable.class, Boolean.TYPE, Object.class),
354             Thread.currentThread());
355         doFibTest(30, new IterOp());
356         doFibTest(5, new RecurOp());
357         doFibTest(5, new NativeOp());
358         doFibTest(-19, new IterOp());
359         doFibTest(-19, new RecurOp());
360         doFibTest(-19, new NativeOp());
361 
362         runnable.run();
363 
364         sMethodTracingIgnore = true;
365         IntrinsicsTest.doTest();
366         sMethodTracingIgnore = false;
367         // Turn off method tracing so we don't have to deal with print internals.
368         Trace.disableTracing(Thread.currentThread());
369         printResults();
370     }
371 
372     // This ensures that all classes we touch are loaded before we start recording traces. This
373     // eliminates a major source of divergence between the RI and ART.
374     public static void loadAllClasses() {
375       MethodThrownThrough.class.toString();
376       MethodEntry.class.toString();
377       MethodReturn.class.toString();
378       FibResult.class.toString();
379       FibThrow.class.toString();
380       Printable.class.toString();
381       ArrayList.class.toString();
382       RecurOp.class.toString();
383       IterOp.class.toString();
384       NativeOp.class.toString();
385       StringBuilder.class.toString();
386       Runnable.class.toString();
387       TestRunnableInvokeHandler.class.toString();
388       Proxy.class.toString();
389       Proxy.getProxyClass(
390           Test988.class.getClassLoader(), new Class[] { Runnable.class }).toString();
391       IntrinsicsTest.initialize();  // ensure <clinit> is executed prior to tracing.
392     }
393 
394     public static void printResults() {
395         for (Printable p : results) {
396             p.Print();
397         }
398     }
399 
400     public static void doFibTest(int x, IntUnaryOperator op) {
401       try {
402         int y = op.applyAsInt(x);
403         results.add(new FibResult("fibonacci(%d)=%d\n", x, y));
404       } catch (Throwable t) {
405         results.add(new FibThrow("fibonacci(%d) -> %s\n", x, t));
406       }
407     }
408 
409     static class IntrinsicsTest {
410       static int[] sSourceArray = { 0, 1, 2, 3, 4, 5 };
411       static int[] sDestArray =   { 5, 6, 7, 8, 9, 10 };
412 
413       static char[] sSourceArrayChar = { '0', '1', '2', '3', '4', '5' };
414       static char[] sDestArrayChar =   { '5', '6', '7', '8', '9', 'a' };
415 
416       static void initialize() {
417         Test988Intrinsics.initialize();
418 
419         // Pre-load all classes used in #doTest manual intrinsics.
420         java.lang.System.class.toString();
421       }
422       static void doTest() {
423         // Ensure that the ART intrinsics in intrinsics_list.h are also being traced,
424         // since in non-tracing operation they are effectively inlined by the optimizing compiler.
425 
426         // Auto-generated test file that uses null/0s as default parameters.
427         Test988Intrinsics.test();
428 
429         // Manual list here for functions that require special non-null/non-zero parameters:
430         System.arraycopy(sSourceArray, 0, sDestArray, 0, 1);
431         System.arraycopy(sSourceArrayChar, 0, sDestArrayChar, 0, 1);
432       }
433     }
434 }
435