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