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