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}