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