001package daikon.chicory;
002
003import daikon.Chicory;
004import daikon.plumelib.bcelutil.InstructionListUtils;
005import daikon.plumelib.bcelutil.SimpleLog;
006import java.io.ByteArrayInputStream;
007import java.io.IOException;
008import java.lang.instrument.ClassFileTransformer;
009import java.lang.instrument.IllegalClassFormatException;
010import java.nio.file.Files;
011import java.nio.file.Path;
012import java.security.ProtectionDomain;
013import java.util.ArrayList;
014import java.util.Iterator;
015import java.util.List;
016import java.util.regex.Matcher;
017import java.util.regex.Pattern;
018import org.apache.bcel.Const;
019import org.apache.bcel.classfile.Attribute;
020import org.apache.bcel.classfile.ClassParser;
021import org.apache.bcel.classfile.Constant;
022import org.apache.bcel.classfile.ConstantUtf8;
023import org.apache.bcel.classfile.ConstantValue;
024import org.apache.bcel.classfile.Field;
025import org.apache.bcel.classfile.JavaClass;
026import org.apache.bcel.classfile.Method;
027import org.apache.bcel.classfile.StackMapEntry;
028import org.apache.bcel.classfile.StackMapType;
029import org.apache.bcel.generic.ACONST_NULL;
030import org.apache.bcel.generic.ArrayType;
031import org.apache.bcel.generic.BasicType;
032import org.apache.bcel.generic.ClassGen;
033import org.apache.bcel.generic.ConstantPoolGen;
034import org.apache.bcel.generic.Instruction;
035import org.apache.bcel.generic.InstructionFactory;
036import org.apache.bcel.generic.InstructionHandle;
037import org.apache.bcel.generic.InstructionList;
038import org.apache.bcel.generic.InstructionTargeter;
039import org.apache.bcel.generic.LineNumberGen;
040import org.apache.bcel.generic.LocalVariableGen;
041import org.apache.bcel.generic.MethodGen;
042import org.apache.bcel.generic.ObjectType;
043import org.apache.bcel.generic.PUSH;
044import org.apache.bcel.generic.Type;
045import org.checkerframework.checker.nullness.qual.Nullable;
046import org.checkerframework.checker.signature.qual.BinaryName;
047import org.checkerframework.checker.signature.qual.ClassGetName;
048import org.checkerframework.checker.signature.qual.InternalForm;
049import org.checkerframework.dataflow.qual.Pure;
050
051/**
052 * The Instrument class is responsible for modifying another class' bytecode. Specifically, its main
053 * task is to add "hooks" into the other class at method entries and exits for instrumentation
054 * purposes.
055 */
056@SuppressWarnings("nullness")
057public class Instrument extends InstructionListUtils implements ClassFileTransformer {
058
059  /** The index of this method in SharedData.methods. */
060  int cur_method_info_index = 0;
061
062  /** The location of the runtime support class. */
063  private static final String runtime_classname = "daikon.chicory.Runtime";
064
065  /** Debug information about which classes are transformed and why. */
066  public static SimpleLog debug_transform = new SimpleLog(false);
067
068  /** Create a new Instrument. Sets up debug logging. */
069  public Instrument() {
070    super();
071    debug_transform.enabled = Chicory.debug_transform;
072    debugInstrument.enabled = Chicory.debug;
073  }
074
075  /**
076   * Returns true if the given ppt should be ignored. Uses the patterns in {@link
077   * daikon.chicory.Runtime#ppt_omit_pattern} and {@link daikon.chicory.Runtime#ppt_select_pattern}.
078   * This method is used by both Chicory and Dyncomp.
079   *
080   * @param className class name to be checked
081   * @param methodName method name to be checked
082   * @param pptName ppt name to be checked
083   * @return true if the item should be filtered out
084   */
085  public static boolean shouldIgnore(String className, String methodName, String pptName) {
086
087    // Don't instrument class if it matches an excluded regular expression
088    for (Pattern pattern : Runtime.ppt_omit_pattern) {
089
090      Matcher mPpt = pattern.matcher(pptName);
091      Matcher mClass = pattern.matcher(className);
092      Matcher mMethod = pattern.matcher(methodName);
093
094      if (mPpt.find() || mClass.find() || mMethod.find()) {
095        debug_transform.log("ignoring %s, it matches ppt_omit regex %s%n", pptName, pattern);
096        return true;
097      }
098    }
099
100    // If any include regular expressions are specified, only instrument
101    // classes that match them
102    if (Runtime.ppt_select_pattern.size() > 0) {
103      for (Pattern pattern : Runtime.ppt_select_pattern) {
104
105        Matcher mPpt = pattern.matcher(pptName);
106        Matcher mClass = pattern.matcher(className);
107        Matcher mMethod = pattern.matcher(methodName);
108
109        if (mPpt.find() || mClass.find() || mMethod.find()) {
110          debug_transform.log("including %s, it matches ppt_select regex %s%n", pptName, pattern);
111          return false;
112        }
113      }
114    }
115
116    // if we're here, this ppt not explicitly included or excluded
117    // so keep unless there were items in the "include only" list
118    if (Runtime.ppt_select_pattern.size() > 0) {
119      debug_transform.log("ignoring %s, not included in ppt_select pattern(s)%n", pptName);
120      return true;
121    } else {
122      debug_transform.log("including %s, not included in ppt_omit pattern(s)%n", pptName);
123      return false;
124    }
125  }
126
127  /**
128   * Given a class, return a transformed version of the class that contains "hooks" at method
129   * entries and exits. Because Chicory is invoked as a javaagent, the transform method is called by
130   * the Java runtime each time a new class is loaded.
131   */
132  @Override
133  public byte @Nullable [] transform(
134      ClassLoader loader,
135      @InternalForm String className,
136      Class<?> classBeingRedefined,
137      ProtectionDomain protectionDomain,
138      byte[] classfileBuffer)
139      throws IllegalClassFormatException {
140
141    @BinaryName String fullClassName = className.replace("/", ".");
142    // String fullClassName = className;
143
144    // new Throwable().printStackTrace();
145
146    debug_transform.log("In chicory.Instrument.transform(): class = %s%n", className);
147
148    // Don't instrument boot classes.  They are uninteresting and will
149    // not be able to access daikon.chicory.Runtime (because it is not
150    // on the boot classpath).  Previously this code skipped classes
151    // that started with java, com, javax, or sun, but this is not
152    // correct in many cases.  Most boot classes have the null loader,
153    // but some generated classes (such as those in sun.reflect) will
154    // have a non-null loader.  Some of these have a null parent loader,
155    // but some do not.  The check for the sun.reflect package is a hack
156    // to catch all of these.  A more consistent mechanism to determine
157    // boot classes would be preferrable.
158    if (Chicory.boot_classes != null) {
159      Matcher matcher = Chicory.boot_classes.matcher(fullClassName);
160      if (matcher.find()) {
161        debug_transform.log("ignoring boot class %s, matches boot_classes regex%n", fullClassName);
162        return null;
163      }
164    } else if (loader == null) {
165      debug_transform.log("ignoring system class %s, class loader == null%n", fullClassName);
166      return null;
167    } else if (loader.getParent() == null) {
168      debug_transform.log("ignoring system class %s, parent loader == null%n", fullClassName);
169      return null;
170    } else if (fullClassName.startsWith("sun.reflect")) {
171      debug_transform.log("ignoring system class %s, in sun.reflect package%n", fullClassName);
172      return null;
173    } else if (fullClassName.startsWith("jdk.internal.reflect")) {
174      // Starting with Java 9 sun.reflect => jdk.internal.reflect.
175      debug_transform.log(
176          "ignoring system class %s, in jdk.internal.reflect package", fullClassName);
177      return null;
178    } else if (fullClassName.startsWith("com.sun")) {
179      debug_transform.log("Class from com.sun package %s with nonnull loaders%n", fullClassName);
180    }
181
182    // Don't intrument our code
183    if (is_chicory(className)) {
184      debug_transform.log("Not considering chicory class %s%n", fullClassName);
185      return null;
186    }
187
188    debug_transform.log(
189        "transforming class %s, loader %s - %s%n", className, loader, loader.getParent());
190
191    // Parse the bytes of the classfile, die on any errors
192    JavaClass c;
193    try (ByteArrayInputStream bais = new ByteArrayInputStream(classfileBuffer)) {
194      ClassParser parser = new ClassParser(bais, className);
195      c = parser.parse();
196    } catch (Exception e) {
197      throw new RuntimeException("Unexpected error", e);
198    }
199
200    try {
201      // Get the class information
202      ClassGen cg = new ClassGen(c);
203
204      // Convert reach non-void method to save its result in a local
205      // before returning
206      ClassInfo c_info = instrument_all_methods(cg, fullClassName, loader);
207
208      // get constant static fields!
209      Field[] fields = cg.getFields();
210      for (Field field : fields) {
211        if (field.isFinal() && field.isStatic() && (field.getType() instanceof BasicType)) {
212          ConstantValue value = field.getConstantValue();
213          String valString;
214
215          if (value == null) {
216            // System.out.println("WARNING FROM " + field.getName());
217            // valString = "WARNING!!!";
218            valString = null;
219          } else {
220            valString = value.toString();
221            // System.out.println("GOOD FROM " + field.getName() +
222            //                    " --- " + valString);
223          }
224
225          if (valString != null) {
226            c_info.staticMap.put(field.getName(), valString);
227          }
228        }
229      }
230
231      if (Chicory.checkStaticInit) {
232        // check for static initializer
233        boolean hasInit = false;
234        for (Method meth : cg.getMethods()) {
235          if (meth.getName().equals("<clinit>")) {
236            hasInit = true;
237          }
238        }
239
240        // if not found, add our own!
241        if (!hasInit) {
242          cg.addMethod(createClinit(cg, fullClassName));
243        }
244      }
245
246      JavaClass njc = cg.getJavaClass();
247      if (Chicory.debug) {
248        Path dir = Files.createTempDirectory("chicory-debug");
249        Path file = dir.resolve(njc.getClassName() + ".class");
250        debugInstrument.log("Dumping %s to %s%n", njc.getClassName(), file);
251        Files.createDirectories(dir);
252        njc.dump(file.toFile());
253      }
254
255      if (c_info.shouldInclude) {
256        // System.out.println ("Instrumented class " + className);
257        // String filename = "/homes/gws/mernst/tmp/" + className +
258        //                   "Transformed.class";
259        // System.out.println ("About to dump class " + className +
260        //                     " to " + filename);
261        // njc.dump(filename);
262        return njc.getBytes();
263      } else {
264        // No changes to the bytecodes
265        return null;
266      }
267
268    } catch (Throwable e) {
269      System.out.printf("Unexpected error %s in transform of %s", e, fullClassName);
270      e.printStackTrace();
271      // No changes to the bytecodes
272      return null;
273    }
274  }
275
276  // used to add a "hook" into the <clinit> static initializer
277  private Method addInvokeToClinit(ClassGen cg, MethodGen mg, String fullClassName) {
278
279    try {
280      InstructionList il = mg.getInstructionList();
281      setCurrentStackMapTable(mg, cg.getMajor());
282      MethodContext context = new MethodContext(cg, mg);
283
284      for (InstructionHandle ih = il.getStart(); ih != null; ) {
285        Instruction inst = ih.getInstruction();
286
287        // Get the translation for this instruction (if any)
288        InstructionList new_il = xform_clinit(cg.getConstantPool(), fullClassName, inst, context);
289
290        // Remember the next instruction to process
291        InstructionHandle next_ih = ih.getNext();
292
293        // will do nothing if new_il == null
294        insertBeforeHandle(mg, ih, new_il, false);
295
296        // Go on to the next instruction in the list
297        ih = next_ih;
298      }
299
300      remove_local_variable_type_table(mg);
301      createNewStackMapAttribute(mg);
302
303      // Update the max stack and Max Locals
304      mg.setMaxLocals();
305      mg.setMaxStack();
306      mg.update();
307    } catch (Exception e) {
308      System.out.printf("Unexpected exception encountered: %s", e);
309      e.printStackTrace();
310    }
311
312    return mg.getMethod();
313  }
314
315  // called by addInvokeToClinit to add in a hook at return opcodes
316  private @Nullable InstructionList xform_clinit(
317      ConstantPoolGen cp, String fullClassName, Instruction inst, MethodContext context) {
318
319    switch (inst.getOpcode()) {
320      case Const.ARETURN:
321      case Const.DRETURN:
322      case Const.FRETURN:
323      case Const.IRETURN:
324      case Const.LRETURN:
325      case Const.RETURN:
326        return call_initNotify(cp, fullClassName, context.ifact);
327
328      default:
329        return null;
330    }
331  }
332
333  // create a <clinit> method, if none exists; guarantees we have this hook
334  private Method createClinit(ClassGen cg, @BinaryName String fullClassName) {
335    InstructionFactory factory = new InstructionFactory(cg);
336
337    InstructionList il = new InstructionList();
338    il.append(call_initNotify(cg.getConstantPool(), fullClassName, factory));
339    il.append(InstructionFactory.createReturn(Type.VOID)); // need to return!
340
341    MethodGen newMethGen =
342        new MethodGen(
343            8,
344            Type.VOID,
345            new Type[0],
346            new String[0],
347            "<clinit>",
348            fullClassName,
349            il,
350            cg.getConstantPool());
351    newMethGen.update();
352
353    // Update the max stack and Max Locals
354    newMethGen.setMaxLocals();
355    newMethGen.setMaxStack();
356    newMethGen.update();
357
358    return newMethGen.getMethod();
359  }
360
361  // created the InstructionList to insert for adding the <clinit> hook
362  private InstructionList call_initNotify(
363      ConstantPoolGen cp, String fullClassName, InstructionFactory factory) {
364
365    InstructionList invokeList = new InstructionList();
366
367    invokeList.append(new PUSH(cp, fullClassName));
368    invokeList.append(
369        factory.createInvoke(
370            runtime_classname,
371            "initNotify",
372            Type.VOID,
373            new Type[] {Type.STRING},
374            Const.INVOKESTATIC));
375
376    // System.out.println(fullClassName + " --- " + invokeList.size());
377    return invokeList;
378  }
379
380  // Map<Integer, InstructionHandle> offset_map = new HashMap<>();
381  InstructionHandle[] offset_map;
382
383  /**
384   * Instrument all the methods in a class. For each method, add instrumentation code at the entry
385   * and at each return from the method. In addition, changes each return statement to first place
386   * the value being returned into a local and then return. This allows us to work around the JDI
387   * deficiency of not being able to query return values.
388   *
389   * @param fullClassName must be fully qualified: packageName.className
390   */
391  private ClassInfo instrument_all_methods(ClassGen cg, String fullClassName, ClassLoader loader) {
392
393    ClassInfo class_info = new ClassInfo(cg.getClassName(), loader);
394    List<MethodInfo> method_infos = new ArrayList<>();
395
396    if (cg.getMajor() < Const.MAJOR_1_6) {
397      System.out.printf(
398          "Chicory warning: ClassFile: %s - classfile version (%d) is out of date and may not be"
399              + " processed correctly.%n",
400          cg.getClassName(), cg.getMajor());
401    }
402
403    boolean shouldInclude = false;
404
405    try {
406      // Loop through each method in the class
407      Method[] methods = cg.getMethods();
408      for (int i = 0; i < methods.length; i++) {
409
410        // The class data in StackMapUtils is not thread safe,
411        // allow only one method at a time to be instrumented.
412        // DynComp does this by creating a new instrumentation object
413        // for each class - probably a cleaner solution.
414        synchronized (this) {
415          pool = cg.getConstantPool();
416          MethodGen mg = new MethodGen(methods[i], cg.getClassName(), pool);
417          MethodContext context = new MethodContext(cg, mg);
418
419          // check for the class static initializer method
420          if (mg.getName().equals("<clinit>")) {
421            if (Chicory.checkStaticInit) {
422              cg.replaceMethod(methods[i], addInvokeToClinit(cg, mg, fullClassName));
423              cg.update();
424            }
425            if (!Chicory.instrument_clinit) {
426              continue;
427            }
428          }
429
430          // If method is synthetic... (default constructors and <clinit> are not synthetic)
431          if ((Const.ACC_SYNTHETIC & mg.getAccessFlags()) > 0) {
432            continue;
433          }
434
435          // Get the instruction list and skip methods with no instructions
436          InstructionList il = mg.getInstructionList();
437          if (il == null) {
438            continue;
439          }
440
441          if (debugInstrument.enabled) {
442            Type[] arg_types = mg.getArgumentTypes();
443            String[] arg_names = mg.getArgumentNames();
444            LocalVariableGen[] local_vars = mg.getLocalVariables();
445            String types = "", names = "", locals = "";
446
447            for (int j = 0; j < arg_types.length; j++) {
448              types = types + arg_types[j] + " ";
449            }
450            for (int j = 0; j < arg_names.length; j++) {
451              names = names + arg_names[j] + " ";
452            }
453            for (int j = 0; j < local_vars.length; j++) {
454              locals = locals + local_vars[j].getName() + " ";
455            }
456            debugInstrument.log("%nMethod = %s%n", mg);
457            debugInstrument.log("arg_types(%d): %s%n", arg_types.length, types);
458            debugInstrument.log("arg_names(%d): %s%n", arg_names.length, names);
459            debugInstrument.log("localvars(%d): %s%n", local_vars.length, locals);
460            debugInstrument.log("Original code: %s%n", mg.getMethod().getCode());
461            debugInstrument.log("%n");
462          }
463
464          // Get existing StackMapTable (if present)
465          setCurrentStackMapTable(mg, cg.getMajor());
466
467          fixLocalVariableTable(mg);
468
469          // Create a MethodInfo that describes this methods arguments
470          // and exit line numbers (information not available via reflection)
471          // and add it to the list for this class.
472          MethodInfo mi = create_method_info(class_info, mg);
473
474          printStackMapTable("After create_method_info");
475
476          if (mi == null) { // method filtered out!
477            continue;
478          }
479
480          shouldInclude = true; // at least one method not filtered out
481
482          // Create a map of Uninitialized_variable_info offsets to
483          // InstructionHandles.  We will use this map after we
484          // complete instrumentation to update the offsets due
485          // to code modification and expansion.
486          // The offsets point to 'new' instructions; since we do
487          // not modify these, their Instruction Handles will remain
488          // unchanged throught the instrumentaion process.
489          buildUninitializedNewMap(il);
490
491          method_infos.add(mi);
492
493          synchronized (SharedData.methods) {
494            cur_method_info_index = SharedData.methods.size();
495            SharedData.methods.add(mi);
496          }
497
498          // Add nonce local to matchup enter/exits
499          add_entry_instrumentation(il, context);
500
501          printStackMapTable("After add_entry_instrumentation");
502
503          debugInstrument.log("Modified code: %s%n", mg.getMethod().getCode());
504
505          // Need to see if there are any switches after this location.
506          // If so, we may need to update the corresponding stackmap if
507          // the amount of the switch padding changed.
508          modifyStackMapsForSwitches(il.getStart(), il);
509
510          Iterator<Boolean> shouldIncIter = mi.is_included.iterator();
511          Iterator<Integer> exitIter = mi.exit_locations.iterator();
512
513          // Loop through each instruction looking for the return(s)
514          for (InstructionHandle ih = il.getStart(); ih != null; ) {
515            Instruction inst = ih.getInstruction();
516
517            // If this is a return instruction, insert method exit instrumentation
518            InstructionList new_il =
519                generate_return_instrumentation(inst, context, shouldIncIter, exitIter);
520
521            // Remember the next instruction to process
522            InstructionHandle next_ih = ih.getNext();
523
524            // If this instruction was modified, replace it with the new
525            // instruction list. If this instruction was the target of any
526            // jumps, replace it with the first instruction in the new list
527            insertBeforeHandle(mg, ih, new_il, true);
528
529            // Go on to the next instruction in the list
530            ih = next_ih;
531          }
532
533          // Update the Uninitialized_variable_info offsets before
534          // we write out the new StackMapTable.
535          updateUninitializedNewOffsets(il);
536
537          createNewStackMapAttribute(mg);
538
539          remove_local_variable_type_table(mg);
540
541          // Update the instruction list
542          mg.setInstructionList(il);
543          mg.update();
544
545          // Update the max stack
546          mg.setMaxStack();
547          mg.update();
548
549          // Update the method in the class
550          try {
551            cg.replaceMethod(methods[i], mg.getMethod());
552          } catch (Exception e) {
553            if (e.getMessage().startsWith("Branch target offset too large")) {
554              System.out.printf(
555                  "Chicory warning: ClassFile: %s - method %s is too large to instrument and is"
556                      + " being skipped.%n",
557                  cg.getClassName(), mg.getName());
558              continue;
559            } else {
560              throw e;
561            }
562          }
563
564          if (debugInstrument.enabled) {
565            debugInstrument.log("Modified code: %s%n", mg.getMethod().getCode());
566            dump_code_attributes(mg);
567          }
568          cg.update();
569        }
570      }
571    } catch (Exception e) {
572      System.out.printf("Unexpected exception encountered: %s", e);
573      e.printStackTrace();
574    }
575
576    // Add the class and method information to runtime so it is available
577    // as enter/exit ppts are processed.
578    class_info.set_method_infos(method_infos);
579
580    if (shouldInclude) {
581      debug_transform.log("Added trace info to class %s%n", class_info);
582      synchronized (SharedData.new_classes) {
583        SharedData.new_classes.add(class_info);
584      }
585      synchronized (SharedData.all_classes) {
586        SharedData.all_classes.add(class_info);
587      }
588    } else { // not included
589      debug_transform.log("Trace info not added to class %s%n", class_info);
590    }
591
592    class_info.shouldInclude = shouldInclude;
593    return class_info;
594  }
595
596  // This method exists only to suppress interning warnings
597  @Pure
598  private static boolean isVoid(Type t) {
599    return t == Type.VOID;
600  }
601
602  /**
603   * If this is a return instruction, generate new il to assign the result to a local variable
604   * (return__$trace2_val) and then call daikon.chicory.Runtime.exit(). This il wil be inserted
605   * immediately before the return.
606   */
607  private @Nullable InstructionList generate_return_instrumentation(
608      Instruction inst,
609      MethodContext c,
610      Iterator<Boolean> shouldIncIter,
611      Iterator<Integer> exitIter) {
612
613    switch (inst.getOpcode()) {
614      case Const.ARETURN:
615      case Const.DRETURN:
616      case Const.FRETURN:
617      case Const.IRETURN:
618      case Const.LRETURN:
619      case Const.RETURN:
620        break;
621
622      default:
623        return null;
624    }
625
626    if (!shouldIncIter.hasNext()) {
627      throw new RuntimeException("Not enough entries in shouldIncIter");
628    }
629
630    boolean shouldInclude = shouldIncIter.next();
631
632    if (!shouldInclude) {
633      return null;
634    }
635
636    Type type = c.mgen.getReturnType();
637    InstructionList il = new InstructionList();
638    if (!isVoid(type)) {
639      LocalVariableGen return_loc = get_return_local(c.mgen, type);
640      il.append(InstructionFactory.createDup(type.getSize()));
641      il.append(InstructionFactory.createStore(type, return_loc.getIndex()));
642    }
643
644    if (!exitIter.hasNext()) {
645      throw new RuntimeException("Not enough exit locations in the exitIter");
646    }
647
648    il.append(call_enter_exit(c, "exit", exitIter.next()));
649    return il;
650  }
651
652  /**
653   * Returns the local variable used to store the return result. If it is not present, creates it
654   * with the specified type. If the variable is known to already exist, the type can be null.
655   */
656  private LocalVariableGen get_return_local(MethodGen mg, @Nullable Type return_type) {
657
658    // Find the local used for the return value
659    LocalVariableGen return_local = null;
660    for (LocalVariableGen lv : mg.getLocalVariables()) {
661      if (lv.getName().equals("return__$trace2_val")) {
662        return_local = lv;
663        break;
664      }
665    }
666
667    // If a type was specified and the variable was found, they must match
668    if (return_local == null) {
669      assert return_type != null : " return__$trace2_val doesn't exist";
670    } else {
671      assert return_type.equals(return_local.getType())
672          : " return_type = " + return_type + "current type = " + return_local.getType();
673    }
674
675    if (return_local == null) {
676      debugInstrument.log("Adding return local of type %s%n", return_type);
677      return_local = mg.addLocalVariable("return__$trace2_val", return_type, null, null);
678    }
679
680    return return_local;
681  }
682
683  /** Finds the nonce local variable. Returns null if not present. */
684  private @Nullable LocalVariableGen get_nonce_local(MethodGen mg) {
685
686    // Find the local used for the nonce value
687    for (LocalVariableGen lv : mg.getLocalVariables()) {
688      if (lv.getName().equals("this_invocation_nonce")) {
689        return lv;
690      }
691    }
692
693    return null;
694  }
695
696  /**
697   * Inserts instrumentation code at the start of the method. This includes adding a local variable
698   * (this_invocation_nonce) that is initialized to Runtime.nonce++. This provides a unique id on
699   * each method entry/exit that allows them to be matched up from the dtrace file. Inserts code to
700   * call daikon.chicory.Runtime.enter().
701   *
702   * @param il instruction list for method
703   * @param c MethodContext for method
704   * @throws IOException if there is trouble with I/O
705   */
706  private void add_entry_instrumentation(InstructionList il, MethodContext c) throws IOException {
707
708    String atomic_int_classname = "java.util.concurrent.atomic.AtomicInteger";
709    Type atomic_int_type = new ObjectType(atomic_int_classname);
710
711    InstructionList nl = new InstructionList();
712
713    // create the local variable
714    LocalVariableGen nonce_lv =
715        create_method_scope_local(c.mgen, "this_invocation_nonce", Type.INT);
716
717    printStackMapTable("After cln");
718
719    if (debugInstrument.enabled) {
720      debugInstrument.log("Modified code: %s%n", c.mgen.getMethod().getCode());
721    }
722
723    // The following implements:
724    //     this_invocation_nonce = Runtime.nonce++;
725
726    // getstatic Runtime.nonce (load reference to AtomicInteger daikon.chicory.Runtime.nonce)
727    nl.append(c.ifact.createGetStatic(runtime_classname, "nonce", atomic_int_type));
728
729    // do an atomic get and increment of nonce value
730    // this is multi-thread safe and leaves int value of nonce on stack
731    nl.append(
732        c.ifact.createInvoke(
733            atomic_int_classname, "getAndIncrement", Type.INT, new Type[] {}, Const.INVOKEVIRTUAL));
734
735    // istore <lv> (pop original value of nonce into this_invocation_nonce)
736    nl.append(InstructionFactory.createStore(Type.INT, nonce_lv.getIndex()));
737
738    nl.setPositions();
739    InstructionHandle end = nl.getEnd();
740    int len_part1 = end.getPosition() + end.getInstruction().getLength();
741
742    // call Runtime.enter()
743    nl.append(call_enter_exit(c, "enter", -1));
744
745    nl.setPositions();
746    end = nl.getEnd();
747    int len_part2 = end.getPosition() + end.getInstruction().getLength() - len_part1;
748
749    // Add the new instructions at the start and move any LineNumbers
750    // and Local variables to point to them.  Other targeters
751    // (branches, exceptions) should still point to the old start
752    // NOTE: Don't use insert_at_method_start as it tries to update StackMaps
753    // and that will be done with special code below.
754    InstructionHandle old_start = il.getStart();
755    InstructionHandle new_start = il.insert(nl);
756    for (InstructionTargeter it : old_start.getTargeters()) {
757      if ((it instanceof LineNumberGen) || (it instanceof LocalVariableGen)) {
758        it.updateTarget(old_start, new_start);
759      }
760    }
761
762    // For Java 7 and beyond the StackMapTable is part of the
763    // verification process.  We need to create and or update it to
764    // account for instrumentation code we have inserted as well as
765    // adjustments for the new 'nonce' local.
766
767    boolean skipFirst = false;
768
769    // Modify existing StackMapTable (if present)
770    if (stackMapTable.length > 0) {
771      // Each stack map frame specifies (explicity or implicitly) an
772      // offset_delta that is used to calculate the actual bytecode
773      // offset at which the frame applies.  This is caluclated by
774      // by adding offset_delta + 1 to the bytecode offset of the
775      // previous frame, unless the previous frame is the initial
776      // frame of the method, in which case the bytecode offset is
777      // offset_delta. (From the Java Virual Machine Specification,
778      // Java SE 7 Edition, section 4.7.4)
779
780      // Since we are inserting (1 or 2) new stack map frames at the
781      // beginning of the stack map table, we need to adjust the
782      // offset_delta of the original first stack map frame due to
783      // the fact that it will no longer be the first entry.  We must
784      // subtract 1. BUT, if the original first entry has an offset
785      // of 0 (because bytecode address 0 is a branch target) then
786      // we must delete it as it will be replaced by the new frames
787      // we are adding.  (did you get all of that? - markro)
788
789      if (stackMapTable[0].getByteCodeOffset() == 0) {
790        skipFirst = true;
791      } else {
792        stackMapTable[0].updateByteCodeOffset(-1);
793      }
794    }
795
796    // Create new StackMap entries for our instrumentation code.
797    int new_table_length = stackMapTable.length + ((len_part2 > 0) ? 2 : 1) - (skipFirst ? 1 : 0);
798    StackMapEntry[] new_map = new StackMapEntry[new_table_length];
799    StackMapType nonce_type = new StackMapType(Const.ITEM_Integer, -1, pool.getConstantPool());
800    StackMapType[] old_nonce_type = {nonce_type};
801    new_map[0] =
802        new StackMapEntry(
803            Const.APPEND_FRAME, len_part1, old_nonce_type, null, pool.getConstantPool());
804
805    int new_index = 1;
806    if (len_part2 > 0) {
807      new_map[1] =
808          new StackMapEntry(
809              ((len_part2 - 1) > Const.SAME_FRAME_MAX
810                  ? Const.SAME_FRAME_EXTENDED
811                  : Const.SAME_FRAME + len_part2 - 1),
812              len_part2 - 1,
813              null,
814              null,
815              pool.getConstantPool());
816      new_index++;
817    }
818
819    // We can just copy the rest of the stack frames over as the FULL_FRAME
820    // ones were already updated when the nonce variable was allocated.
821    for (int i = (skipFirst ? 1 : 0); i < stackMapTable.length; i++) {
822      new_map[new_index++] = stackMapTable[i];
823    }
824    stackMapTable = new_map;
825  }
826
827  /**
828   * Pushes the object, nonce, parameters, and return value on the stack and calls the specified
829   * Method (normally enter or exit) in daikon.chicory.Runtime. The parameters are passed as an
830   * array of objects. Any primitive values are wrapped in the appropriate daikon.chicory.Runtime
831   * wrapper (IntWrap, FloatWrap, etc).
832   */
833  private InstructionList call_enter_exit(MethodContext c, String method_name, int line) {
834
835    InstructionList il = new InstructionList();
836    InstructionFactory ifact = c.ifact;
837    MethodGen mg = c.mgen;
838    Type[] arg_types = mg.getArgumentTypes();
839
840    // aload
841    // Push the object.  Null if this is a static method or a constructor
842    if (mg.isStatic() || (method_name.equals("enter") && is_constructor(mg))) {
843      il.append(new ACONST_NULL());
844    } else { // must be an instance method
845      il.append(InstructionFactory.createLoad(Type.OBJECT, 0));
846    }
847
848    // Determine the offset of the first parameter
849    int param_offset = 1;
850    if (mg.isStatic()) {
851      param_offset = 0;
852    }
853
854    // iload
855    // Push the nonce
856    LocalVariableGen nonce_lv = get_nonce_local(mg);
857    il.append(InstructionFactory.createLoad(Type.INT, nonce_lv.getIndex()));
858
859    // iconst
860    // Push the MethodInfo index
861    il.append(ifact.createConstant(cur_method_info_index));
862
863    // iconst
864    // anewarray
865    // Create an array of objects with elements for each parameter
866    il.append(ifact.createConstant(arg_types.length));
867    Type object_arr_typ = new ArrayType("java.lang.Object", 1);
868    il.append(ifact.createNewArray(Type.OBJECT, (short) 1));
869
870    // Put each argument into the array
871    int param_index = param_offset;
872    for (int ii = 0; ii < arg_types.length; ii++) {
873      il.append(InstructionFactory.createDup(object_arr_typ.getSize()));
874      il.append(ifact.createConstant(ii));
875      Type at = arg_types[ii];
876      if (at instanceof BasicType) {
877        il.append(create_wrapper(c, at, param_index));
878      } else { // must be reference of some sort
879        il.append(InstructionFactory.createLoad(Type.OBJECT, param_index));
880      }
881      il.append(InstructionFactory.createArrayStore(Type.OBJECT));
882      param_index += at.getSize();
883    }
884
885    // If this is an exit, push the return value and line number.
886    // The return value is stored in the local "return__$trace2_val".
887    // If the return value is a primitive, wrap it in the appropriate wrapper.
888    if (method_name.equals("exit")) {
889      Type ret_type = mg.getReturnType();
890      if (isVoid(ret_type)) {
891        il.append(new ACONST_NULL());
892      } else {
893        LocalVariableGen return_local = get_return_local(mg, ret_type);
894        if (ret_type instanceof BasicType) {
895          il.append(create_wrapper(c, ret_type, return_local.getIndex()));
896        } else {
897          il.append(InstructionFactory.createLoad(Type.OBJECT, return_local.getIndex()));
898        }
899      }
900
901      // push line number
902      // System.out.println(mg.getName() + " --> " + line);
903      il.append(ifact.createConstant(line));
904    }
905
906    // Call the specified method
907    Type[] method_args;
908    if (method_name.equals("exit")) {
909      method_args =
910          new Type[] {Type.OBJECT, Type.INT, Type.INT, object_arr_typ, Type.OBJECT, Type.INT};
911    } else {
912      method_args = new Type[] {Type.OBJECT, Type.INT, Type.INT, object_arr_typ};
913    }
914    il.append(
915        c.ifact.createInvoke(
916            runtime_classname, method_name, Type.VOID, method_args, Const.INVOKESTATIC));
917
918    return il;
919  }
920
921  /**
922   * Creates code to put the local var/param at the specified var_index into a wrapper appropriate
923   * for prim_type. prim_type should be one of the basic types (eg, Type.INT, Type.FLOAT, etc). The
924   * wrappers are those defined in daikon.chicory.Runtime.
925   *
926   * <p>The stack is left with a pointer to the newly created wrapper at the top.
927   */
928  private InstructionList create_wrapper(MethodContext c, Type prim_type, int var_index) {
929
930    String wrapper;
931    switch (prim_type.getType()) {
932      case Const.T_BOOLEAN:
933        wrapper = "BooleanWrap";
934        break;
935      case Const.T_BYTE:
936        wrapper = "ByteWrap";
937        break;
938      case Const.T_CHAR:
939        wrapper = "CharWrap";
940        break;
941      case Const.T_DOUBLE:
942        wrapper = "DoubleWrap";
943        break;
944      case Const.T_FLOAT:
945        wrapper = "FloatWrap";
946        break;
947      case Const.T_INT:
948        wrapper = "IntWrap";
949        break;
950      case Const.T_LONG:
951        wrapper = "LongWrap";
952        break;
953      case Const.T_SHORT:
954        wrapper = "ShortWrap";
955        break;
956      default:
957        throw new Error("unexpected type " + prim_type);
958    }
959
960    InstructionList il = new InstructionList();
961    String classname = runtime_classname + "$" + wrapper;
962    il.append(c.ifact.createNew(classname));
963    il.append(InstructionFactory.createDup(Type.OBJECT.getSize()));
964    il.append(InstructionFactory.createLoad(prim_type, var_index));
965    il.append(
966        c.ifact.createInvoke(
967            classname, "<init>", Type.VOID, new Type[] {prim_type}, Const.INVOKESPECIAL));
968
969    return il;
970  }
971
972  /**
973   * Returns true iff mgen is a constructor.
974   *
975   * @return true iff mgen is a constructor
976   */
977  @Pure
978  private boolean is_constructor(MethodGen mgen) {
979
980    if (mgen.getName().equals("<init>") || mgen.getName().equals("")) {
981      debugInstrument.log("method '%s' is a constructor%n", mgen.getName());
982      return true;
983    } else {
984      return false;
985    }
986  }
987
988  /**
989   * Return an array of strings, each corresponding to mgen's argument types.
990   *
991   * @return an array of strings, each corresponding to mgen's argument types
992   */
993  private @BinaryName String[] getArgTypes(MethodGen mgen) {
994
995    Type[] arg_types = mgen.getArgumentTypes();
996    @BinaryName String[] arg_type_strings = new @BinaryName String[arg_types.length];
997
998    for (int ii = 0; ii < arg_types.length; ii++) {
999      Type t = arg_types[ii];
1000      /*if (t instanceof ObjectType)
1001        arg_type_strings[ii] = ((ObjectType) t).getClassName();
1002        else {
1003        arg_type_strings[ii] = t.getSignature().replace('/', '.');
1004        }
1005      */
1006      arg_type_strings[ii] = t.toString();
1007    }
1008
1009    return arg_type_strings;
1010  }
1011
1012  // creates a MethodInfo struct corresponding to mgen
1013  @SuppressWarnings("unchecked")
1014  private @Nullable MethodInfo create_method_info(ClassInfo class_info, MethodGen mgen) {
1015
1016    // Get the argument names for this method
1017    String[] arg_names = mgen.getArgumentNames();
1018    LocalVariableGen[] lvs = mgen.getLocalVariables();
1019    int param_offset = 1;
1020    if (mgen.isStatic()) {
1021      param_offset = 0;
1022    }
1023    if (debugInstrument.enabled) {
1024      debugInstrument.log("create_method_info1 %s%n", arg_names.length);
1025      for (int ii = 0; ii < arg_names.length; ii++) {
1026        debugInstrument.log("arg: %s%n", arg_names[ii]);
1027      }
1028    }
1029
1030    int lv_start = 0;
1031    // If this is an inner class constructor, then its first parameter is
1032    // the outer class constructor.  I need to detect this and adjust the
1033    // parameter names appropriately.  This check is ugly.
1034    if (mgen.getName().equals("<init>") && mgen.getArgumentTypes().length > 0) {
1035      int dollarPos = mgen.getClassName().lastIndexOf("$");
1036      if (dollarPos >= 0
1037          &&
1038          // type of first parameter is classname up to the "$"
1039          mgen.getClassName().substring(0, dollarPos).equals(mgen.getArgumentType(0).toString())) {
1040        // As a further check, for javac-generated classfiles, the
1041        // constant pool index #1 is "this$0", and the first 5 bytes of
1042        // the bytecode are:
1043        //   0: aload_0
1044        //   1: aload_1
1045        //   2: putfield      #1
1046
1047        lv_start++;
1048        arg_names[0] = mgen.getArgumentType(0).toString() + ".this";
1049      }
1050    }
1051
1052    if (lvs != null) {
1053      for (int ii = lv_start; ii < arg_names.length; ii++) {
1054        if ((ii + param_offset) < lvs.length) {
1055          arg_names[ii] = lvs[ii + param_offset].getName();
1056        }
1057      }
1058    }
1059
1060    if (debugInstrument.enabled) {
1061      debugInstrument.log("create_method_info2 %s%n", arg_names.length);
1062      for (int ii = 0; ii < arg_names.length; ii++) {
1063        debugInstrument.log("arg: %s%n", arg_names[ii]);
1064      }
1065    }
1066
1067    boolean shouldInclude = false;
1068
1069    // see if we should track the entry point
1070    if (!shouldIgnore(
1071        class_info.class_name,
1072        mgen.getName(),
1073        DaikonWriter.methodEntryName(
1074            class_info.class_name, getArgTypes(mgen), mgen.toString(), mgen.getName()))) {
1075      shouldInclude = true;
1076    }
1077    // Get the argument types for this method
1078    Type[] arg_types = mgen.getArgumentTypes();
1079    @ClassGetName String[] arg_type_strings = new @ClassGetName String[arg_types.length];
1080    for (int ii = 0; ii < arg_types.length; ii++) {
1081      arg_type_strings[ii] = typeToClassGetName(arg_types[ii]);
1082    }
1083
1084    // Loop through each instruction and find the line number for each
1085    // return opcode
1086    List<Integer> exit_locs = new ArrayList<>();
1087
1088    // tells whether each exit loc in the method is included or not (based on filters)
1089    List<Boolean> isIncluded = new ArrayList<>();
1090
1091    debugInstrument.log("Looking for exit points in %s%n", mgen.getName());
1092    InstructionList il = mgen.getInstructionList();
1093    int line_number = 0;
1094    int last_line_number = 0;
1095    boolean foundLine;
1096
1097    for (InstructionHandle ih : il) {
1098      foundLine = false;
1099
1100      if (ih.hasTargeters()) {
1101        for (InstructionTargeter it : ih.getTargeters()) {
1102          if (it instanceof LineNumberGen) {
1103            LineNumberGen lng = (LineNumberGen) it;
1104            // debugInstrument.log("  line number at %s: %d%n", ih, lng.getSourceLine());
1105            line_number = lng.getSourceLine();
1106            foundLine = true;
1107          }
1108        }
1109      }
1110
1111      switch (ih.getInstruction().getOpcode()) {
1112        case Const.ARETURN:
1113        case Const.DRETURN:
1114        case Const.FRETURN:
1115        case Const.IRETURN:
1116        case Const.LRETURN:
1117        case Const.RETURN:
1118          debugInstrument.log("Exit at line %d%n", line_number);
1119
1120          // only do incremental lines if we don't have the line generator
1121          if (line_number == last_line_number && foundLine == false) {
1122            debugInstrument.log("Could not find line... at %d%n", line_number);
1123            line_number++;
1124          }
1125
1126          last_line_number = line_number;
1127
1128          if (!shouldIgnore(
1129              class_info.class_name,
1130              mgen.getName(),
1131              DaikonWriter.methodExitName(
1132                  class_info.class_name,
1133                  getArgTypes(mgen),
1134                  mgen.toString(),
1135                  mgen.getName(),
1136                  line_number))) {
1137            shouldInclude = true;
1138            exit_locs.add(line_number);
1139
1140            isIncluded.add(true);
1141          } else {
1142            isIncluded.add(false);
1143          }
1144
1145          break;
1146
1147        default:
1148          break;
1149      }
1150    }
1151
1152    if (shouldInclude) {
1153      return new MethodInfo(
1154          class_info, mgen.getName(), arg_names, arg_type_strings, exit_locs, isIncluded);
1155    } else {
1156      return null;
1157    }
1158  }
1159
1160  public void dump_code_attributes(MethodGen mg) {
1161    // mg.getMethod().getCode().getAttributes() forces attributes
1162    // to be instantiated; mg.getCodeAttributes() does not
1163    for (Attribute a : mg.getMethod().getCode().getAttributes()) {
1164      int con_index = a.getNameIndex();
1165      Constant c = pool.getConstant(con_index);
1166      String att_name = ((ConstantUtf8) c).getBytes();
1167      debugInstrument.log("Attribute Index: %s Name: %s%n", con_index, att_name);
1168    }
1169  }
1170
1171  /** Any information needed by InstTransform routines about the method and class. */
1172  private static class MethodContext {
1173
1174    public InstructionFactory ifact;
1175    public MethodGen mgen;
1176
1177    public MethodContext(ClassGen cg, MethodGen mgen) {
1178      ifact = new InstructionFactory(cg);
1179      this.mgen = mgen;
1180    }
1181  }
1182
1183  /**
1184   * Returns whether or not the specified class is part of Chicory itself (and thus should not be
1185   * instrumented). Some Daikon classes that are used by Chicory are included here as well.
1186   *
1187   * @param classname the name of the class to test, in internal form
1188   * @return true if the given class is part of Chicory itself
1189   */
1190  @Pure
1191  private static boolean is_chicory(@InternalForm String classname) {
1192
1193    if (classname.startsWith("daikon/chicory") && !classname.equals("daikon/chicory/ChicoryTest")) {
1194      return true;
1195    }
1196    if (classname.equals("daikon/PptTopLevel$PptType")) {
1197      return true;
1198    }
1199    if (classname.startsWith("daikon/plumelib")) {
1200      return true;
1201    }
1202    return false;
1203  }
1204}