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