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