001package daikon.chicory; 002 003import daikon.Chicory; 004import java.io.PrintWriter; 005import java.lang.reflect.Array; 006import java.lang.reflect.Field; 007import java.lang.reflect.Member; 008import java.util.ArrayList; 009import java.util.List; 010import org.checkerframework.checker.lock.qual.GuardSatisfied; 011import org.checkerframework.checker.nullness.qual.Nullable; 012 013/** 014 * DTraceWriter writes {@code .dtrace} program points to an output stream. It uses the trees created 015 * by the {@link DeclWriter}. 016 */ 017@SuppressWarnings("nullness") 018public class DTraceWriter extends DaikonWriter { 019 // Notes: 020 // 021 // - methodExit(): handles exits from a method 022 // - printReturnValue: prints return value and related vars 023 // - checkForRuntimeClass: prints return.class and value 024 // - traceMethod(): prints out this and arguments of method 025 // - traceMethodVars: prints out this and arguments of method 026 // - traceLocalVars: prints arguments 027 // - traceLocalVar: prints one argument 028 // - checkForVarRecursion: recursive check on on argument 029 // - traceClassVars: prints fields in a class 030 031 /** instance of a nonsensical value. */ 032 private static NonsensicalObject nonsenseValue = NonsensicalObject.getInstance(); 033 034 /** instance of a nonsensical list. */ 035 private static List<Object> nonsenseList = NonsensicalList.getInstance(); 036 037 // certain class names 038 protected static final String classClassName = "java.lang.Class"; 039 protected static final String stringClassName = "java.lang.String"; 040 041 /** Where to print output. */ 042 private PrintWriter outFile; 043 044 /** Debug information about daikon variables. */ 045 private boolean debug_vars = false; 046 047 /** 048 * Initializes the DTraceWriter. 049 * 050 * @param writer stream to write to 051 */ 052 public DTraceWriter(PrintWriter writer) { 053 super(); 054 outFile = writer; 055 } 056 057 /** Prints the method entry program point in the dtrace file. */ 058 public void methodEntry( 059 @GuardSatisfied DTraceWriter this, 060 MethodInfo mi, 061 int nonceVal, 062 @Nullable Object obj, 063 Object[] args) { 064 // don't print 065 if (Runtime.dtrace_closed) { 066 return; 067 } 068 069 Member member = mi.member; 070 071 // get the root of the method's traversal pattern 072 RootInfo root = mi.traversalEnter; 073 if (root == null) { 074 throw new RuntimeException("Traversal pattern not initialized at method " + mi.method_name); 075 } 076 077 if (debug_vars) { 078 System.out.printf("Entering %s%n%s%n", DaikonWriter.methodEntryName(member), root); 079 Throwable stack = new Throwable("enter traceback"); 080 stack.fillInStackTrace(); 081 stack.printStackTrace(System.out); 082 } 083 outFile.println(DaikonWriter.methodEntryName(member)); 084 printNonce(nonceVal); 085 traverse(mi, root, args, obj, nonsenseValue); 086 087 outFile.println(); 088 089 Runtime.incrementRecords(); 090 } 091 092 /** Prints an entry program point for a static initializer in the dtrace file. */ 093 public void clinitEntry(@GuardSatisfied DTraceWriter this, String pptname, int nonceVal) { 094 // don't print 095 if (Runtime.dtrace_closed) { 096 return; 097 } 098 outFile.println(pptname); 099 printNonce(nonceVal); 100 outFile.println(); 101 Runtime.incrementRecords(); 102 } 103 104 /** Prints the method exit program point in the dtrace file. */ 105 public void methodExit( 106 @GuardSatisfied DTraceWriter this, 107 MethodInfo mi, 108 int nonceVal, 109 @Nullable Object obj, 110 Object[] args, 111 Object ret_val, 112 int lineNum) { 113 if (Runtime.dtrace_closed) { 114 return; 115 } 116 117 Member member = mi.member; 118 119 // gets the traversal pattern root for this method exit 120 RootInfo root = mi.traversalExit; 121 if (root == null) { 122 throw new RuntimeException( 123 "Traversal pattern not initialized for method " + mi.method_name + " at line " + lineNum); 124 } 125 126 // make sure the line number is valid 127 // i.e., it is one of the exit locations in the MethodInfo for this method 128 if (mi.exit_locations == null || !mi.exit_locations.contains(lineNum)) { 129 throw new RuntimeException( 130 "The line number " 131 + lineNum 132 + " is not found in the MethodInfo for method " 133 + mi.method_name 134 + DaikonWriter.lineSep 135 + "No exit locations found in exit_locations set!"); 136 } 137 138 outFile.println(DaikonWriter.methodExitName(member, lineNum)); 139 printNonce(nonceVal); 140 traverse(mi, root, args, obj, ret_val); 141 142 outFile.println(); 143 144 Runtime.incrementRecords(); 145 } 146 147 /** Prints an exit program point for a static initializer in the dtrace file. */ 148 public void clinitExit(@GuardSatisfied DTraceWriter this, String pptname, int nonceVal) { 149 // don't print 150 if (Runtime.dtrace_closed) { 151 return; 152 } 153 outFile.println(pptname); 154 printNonce(nonceVal); 155 outFile.println(); 156 Runtime.incrementRecords(); 157 } 158 159 // prints an invocation nonce entry in the dtrace 160 private void printNonce(@GuardSatisfied DTraceWriter this, int val) { 161 outFile.println("this_invocation_nonce"); 162 outFile.println(val); 163 } 164 165 /** 166 * Prints the method's return value and all relevant variables. Uses the tree of 167 * DaikonVariableInfo objects. 168 * 169 * @param mi the method whose program point we are printing 170 * @param root the root of the program point's tree 171 * @param args the arguments to the method corrsponding to mi. Must be in the same order as the 172 * .decls info is in (which is the declared order in the source code). 173 * @param thisObj the value of the "this" object at this point in the execution 174 * @param ret_val the value returned from this method, only used for exit program points 175 */ 176 private void traverse( 177 @GuardSatisfied DTraceWriter this, 178 MethodInfo mi, 179 RootInfo root, 180 Object[] args, 181 Object thisObj, 182 Object ret_val) { 183 // go through all of the node's children 184 for (DaikonVariableInfo child : root) { 185 186 Object val; 187 188 if (child instanceof ReturnInfo) { 189 val = ret_val; 190 } else if (child instanceof ThisObjInfo) { 191 val = thisObj; 192 } else if (child instanceof ParameterInfo) { 193 val = args[((ParameterInfo) child).getArgNum()]; 194 } else if (child instanceof FieldInfo) { 195 // can only occur for static fields 196 // non-static fields will appear as children of "this" 197 198 val = child.getMyValFromParentVal(null); 199 } else if (child instanceof StaticObjInfo) { 200 val = null; 201 } else { 202 throw new Error( 203 "Unknown DaikonVariableInfo subtype " 204 + child.getClass() 205 + " in traversePattern in DTraceWriter for info named " 206 + child.getName() 207 + " in class " 208 + "for method " 209 + mi); 210 } 211 212 traverseValue(mi, child, val); 213 } 214 } 215 216 // traverse from the traversal pattern data structure and recurse 217 private void traverseValue( 218 @GuardSatisfied DTraceWriter this, MethodInfo mi, DaikonVariableInfo curInfo, Object val) { 219 220 if (curInfo.dTraceShouldPrint()) { 221 if (!(curInfo instanceof StaticObjInfo)) { 222 outFile.println(curInfo.getName()); 223 outFile.println(curInfo.getDTraceValueString(val)); 224 } 225 226 if (debug_vars) { 227 String out = curInfo.getDTraceValueString(val); 228 if (out.length() > 20) { 229 out = out.substring(0, 20); 230 } 231 System.out.printf( 232 " --variable %s [%d]= %s%n", curInfo.getName(), curInfo.children.size(), out); 233 } 234 } 235 236 // go through all of the current node's children 237 // and recurse on their values 238 if (curInfo.dTraceShouldPrintChildren()) { 239 for (DaikonVariableInfo child : curInfo) { 240 Object childVal = child.getMyValFromParentVal(val); 241 traverseValue(mi, child, childVal); 242 } 243 } 244 } 245 246 /** 247 * Returns a list of values of the field for each Object in theObjects. 248 * 249 * @param theObjects list of Objects, each must have the Field field 250 * @param field which field of theObjects we are probing 251 */ 252 public static List<Object> getFieldValues(Field field, List<Object> theObjects) { 253 if (theObjects == null || theObjects instanceof NonsensicalList) { 254 return nonsenseList; 255 } 256 257 List<Object> fieldVals = new ArrayList<>(); 258 259 for (Object theObj : theObjects) { 260 if (theObj == null) { 261 fieldVals.add(nonsenseValue); 262 } else { 263 fieldVals.add(getValue(field, theObj)); 264 } 265 } 266 267 return fieldVals; 268 } 269 270 /** 271 * Get the value of a certain field in theObj. 272 * 273 * @param classField which field we are interested in 274 * @param theObj the object whose field we are examining. TheoObj must be null, Nonsensical, or of 275 * a type which contains the field classField. 276 * @return the value of the classField field in theObj 277 */ 278 @SuppressWarnings("deprecation") // in Java 9+, use canAccess instead of isAccessible 279 public static Object getValue(Field classField, Object theObj) { 280 // if we don't have a real object, return NonsensicalValue 281 if ((theObj == null) || (theObj instanceof NonsensicalObject)) { 282 return nonsenseValue; 283 } 284 285 Class<?> fieldType = classField.getType(); 286 287 if (!classField.isAccessible()) { 288 classField.setAccessible(true); 289 } 290 291 try { 292 if (fieldType.equals(int.class)) { 293 return new Runtime.IntWrap(classField.getInt(theObj)); 294 } else if (fieldType.equals(long.class)) { 295 return new Runtime.LongWrap(classField.getLong(theObj)); 296 } else if (fieldType.equals(boolean.class)) { 297 return new Runtime.BooleanWrap(classField.getBoolean(theObj)); 298 } else if (fieldType.equals(float.class)) { 299 return new Runtime.FloatWrap(classField.getFloat(theObj)); 300 } else if (fieldType.equals(byte.class)) { 301 return new Runtime.ByteWrap(classField.getByte(theObj)); 302 } else if (fieldType.equals(char.class)) { 303 return new Runtime.CharWrap(classField.getChar(theObj)); 304 } else if (fieldType.equals(short.class)) { 305 return new Runtime.ShortWrap(classField.getShort(theObj)); 306 } else if (fieldType.equals(double.class)) { 307 return new Runtime.DoubleWrap(classField.getDouble(theObj)); 308 } else { 309 return classField.get(theObj); 310 } 311 } catch (IllegalArgumentException e) { 312 throw new Error(e); 313 } catch (IllegalAccessException e) { 314 throw new Error(e); 315 } 316 } 317 318 /** 319 * Similar to {@link DTraceWriter#getValue}, but used for static fields. 320 * 321 * @param classField the field whose static value to return 322 * @return the static value of the field 323 */ 324 @SuppressWarnings("deprecation") // in Java 9+, use canAccess instead of isAccessible 325 public static Object getStaticValue(Field classField) { 326 if (!classField.isAccessible()) { 327 classField.setAccessible(true); 328 } 329 330 Class<?> fieldType = classField.getType(); 331 332 if (Chicory.checkStaticInit) { 333 // don't force initialization! 334 if (!Runtime.isInitialized(classField.getDeclaringClass().getName())) { 335 return nonsenseValue; 336 } 337 } 338 339 try { 340 if (fieldType.equals(int.class)) { 341 return new Runtime.IntWrap(classField.getInt(null)); 342 } else if (fieldType.equals(long.class)) { 343 return new Runtime.LongWrap(classField.getLong(null)); 344 } else if (fieldType.equals(boolean.class)) { 345 return new Runtime.BooleanWrap(classField.getBoolean(null)); 346 } else if (fieldType.equals(float.class)) { 347 return new Runtime.FloatWrap(classField.getFloat(null)); 348 } else if (fieldType.equals(byte.class)) { 349 return new Runtime.ByteWrap(classField.getByte(null)); 350 } else if (fieldType.equals(char.class)) { 351 return new Runtime.CharWrap(classField.getChar(null)); 352 } else if (fieldType.equals(short.class)) { 353 return new Runtime.ShortWrap(classField.getShort(null)); 354 } else if (fieldType.equals(double.class)) { 355 return new Runtime.DoubleWrap(classField.getDouble(null)); 356 } else { 357 return classField.get(null); 358 } 359 } catch (IllegalArgumentException e) { 360 throw new Error(e); 361 } catch (IllegalAccessException e) { 362 throw new Error(e); 363 } 364 } 365 366 /** 367 * Return a List derived from an aray. 368 * 369 * @param arrayVal must be an array type 370 * @return a List (with correct primitive wrappers) corresponding to the array 371 */ 372 public static List<Object> getListFromArray(Object arrayVal) { 373 if (arrayVal instanceof NonsensicalObject) { 374 return nonsenseList; 375 } 376 377 if (!arrayVal.getClass().isArray()) { 378 throw new RuntimeException( 379 String.format( 380 "The object \"%s\" of type %s is not an array", arrayVal, arrayVal.getClass())); 381 } 382 383 int len = Array.getLength(arrayVal); 384 List<Object> arrList = new ArrayList<>(len); 385 386 Class<?> arrType = arrayVal.getClass().getComponentType(); 387 388 // have to wrap primitives in our wrappers 389 // otherwise, couldn't distinguish from a wrapped object in the 390 // target app 391 if (arrType.equals(int.class)) { 392 for (int i = 0; i < len; i++) { 393 arrList.add(new Runtime.IntWrap(Array.getInt(arrayVal, i))); 394 } 395 } else if (arrType.equals(long.class)) { 396 for (int i = 0; i < len; i++) { 397 arrList.add(new Runtime.LongWrap(Array.getLong(arrayVal, i))); 398 } 399 } else if (arrType.equals(boolean.class)) { 400 for (int i = 0; i < len; i++) { 401 arrList.add(new Runtime.BooleanWrap(Array.getBoolean(arrayVal, i))); 402 } 403 } else if (arrType.equals(float.class)) { 404 for (int i = 0; i < len; i++) { 405 arrList.add(new Runtime.FloatWrap(Array.getFloat(arrayVal, i))); 406 } 407 } else if (arrType.equals(byte.class)) { 408 for (int i = 0; i < len; i++) { 409 arrList.add(new Runtime.ByteWrap(Array.getByte(arrayVal, i))); 410 } 411 } else if (arrType.equals(char.class)) { 412 for (int i = 0; i < len; i++) { 413 arrList.add(new Runtime.CharWrap(Array.getChar(arrayVal, i))); 414 } 415 } else if (arrType.equals(short.class)) { 416 for (int i = 0; i < len; i++) { 417 arrList.add(new Runtime.ShortWrap(Array.getShort(arrayVal, i))); 418 } 419 } else if (arrType.equals(double.class)) { 420 for (int i = 0; i < len; i++) { 421 arrList.add(new Runtime.DoubleWrap(Array.getDouble(arrayVal, i))); 422 } 423 } else { 424 for (int i = 0; i < len; i++) { 425 // non-primitives 426 arrList.add(Array.get(arrayVal, i)); 427 } 428 } 429 430 return arrList; 431 } 432 433 /** 434 * Returns a list of Strings which are the names of the run-time types in the theVals param. 435 * 436 * @param theVals list of ObjectReferences 437 * @return a list of Strings which are the names of the run-time types in the theVals param 438 */ 439 public static @Nullable List<String> getTypeNameList(List<Object> theVals) { 440 // Return null rather than NonsensicalList as NonsensicalList is 441 // an array of Object and not String. 442 if (theVals == null || theVals instanceof NonsensicalList) { 443 return null; 444 } 445 446 List<String> typeNames = new ArrayList<>(theVals.size()); 447 448 for (Object ref : theVals) { 449 if (ref == null) { 450 typeNames.add(null); 451 } else { 452 Class<?> type = ref.getClass(); 453 type = removeWrappers(ref, type, true); 454 typeNames.add(type.getCanonicalName()); 455 } 456 } 457 458 return typeNames; 459 } 460 461 /** 462 * Get the type of val, removing any PrimitiveWrapper if it exists For example, if we execute 463 * removeWRappers(val, boolean.class, true) where (val instanceof Runtime.PrimitiveWrapper), then 464 * the method returns boolean.class 465 * 466 * @param val the object whose type we are examining 467 * @param declared the declared type of the variable corresponding to val 468 * @param runtime should we use the run-time type or declared type? 469 * @return the variable's type, with primitive wrappers removed, or null if the value is non-null 470 */ 471 public static @Nullable Class<?> removeWrappers(Object val, Class<?> declared, boolean runtime) { 472 if (!runtime) { 473 return declared; 474 } 475 476 if (val instanceof Runtime.PrimitiveWrapper) { 477 return ((Runtime.PrimitiveWrapper) val).primitiveClass(); 478 } 479 480 if (val == null) { 481 return null; 482 } 483 484 return val.getClass(); 485 } 486}