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}