001package daikon.dcomp; 002 003import daikon.DynComp; 004import daikon.chicory.ClassInfo; 005import daikon.chicory.DaikonWriter; 006import daikon.chicory.MethodInfo; 007import daikon.plumelib.bcelutil.BcelUtil; 008import daikon.plumelib.bcelutil.InstructionListUtils; 009import daikon.plumelib.bcelutil.SimpleLog; 010import daikon.plumelib.bcelutil.StackTypes; 011import daikon.plumelib.options.Option; 012import daikon.plumelib.reflection.Signatures; 013import daikon.plumelib.util.EntryReader; 014import java.io.File; 015import java.io.IOException; 016import java.io.InputStream; 017import java.io.PrintStream; 018import java.net.URL; 019import java.util.ArrayDeque; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Deque; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Iterator; 026import java.util.LinkedHashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import java.util.concurrent.ConcurrentHashMap; 031import java.util.regex.Pattern; 032import org.apache.bcel.Const; 033import org.apache.bcel.classfile.AnnotationEntry; 034import org.apache.bcel.classfile.Annotations; 035import org.apache.bcel.classfile.Attribute; 036import org.apache.bcel.classfile.ClassParser; 037import org.apache.bcel.classfile.Constant; 038import org.apache.bcel.classfile.ConstantInterfaceMethodref; 039import org.apache.bcel.classfile.Field; 040import org.apache.bcel.classfile.JavaClass; 041import org.apache.bcel.classfile.Method; 042import org.apache.bcel.classfile.RuntimeVisibleAnnotations; 043import org.apache.bcel.classfile.StackMapEntry; 044import org.apache.bcel.classfile.StackMapType; 045import org.apache.bcel.generic.AALOAD; 046import org.apache.bcel.generic.ACONST_NULL; 047import org.apache.bcel.generic.ALOAD; 048import org.apache.bcel.generic.ASTORE; 049import org.apache.bcel.generic.ATHROW; 050import org.apache.bcel.generic.AnnotationEntryGen; 051import org.apache.bcel.generic.ArrayType; 052import org.apache.bcel.generic.BasicType; 053import org.apache.bcel.generic.BranchInstruction; 054import org.apache.bcel.generic.ClassGen; 055import org.apache.bcel.generic.ClassGenException; 056import org.apache.bcel.generic.CodeExceptionGen; 057import org.apache.bcel.generic.DUP; 058import org.apache.bcel.generic.DUP2; 059import org.apache.bcel.generic.DUP_X2; 060import org.apache.bcel.generic.FieldInstruction; 061import org.apache.bcel.generic.GETFIELD; 062import org.apache.bcel.generic.GETSTATIC; 063import org.apache.bcel.generic.IADD; 064import org.apache.bcel.generic.INVOKEDYNAMIC; 065import org.apache.bcel.generic.INVOKEINTERFACE; 066import org.apache.bcel.generic.INVOKESPECIAL; 067import org.apache.bcel.generic.INVOKEVIRTUAL; 068import org.apache.bcel.generic.Instruction; 069import org.apache.bcel.generic.InstructionFactory; 070import org.apache.bcel.generic.InstructionHandle; 071import org.apache.bcel.generic.InstructionList; 072import org.apache.bcel.generic.InstructionTargeter; 073import org.apache.bcel.generic.InvokeInstruction; 074import org.apache.bcel.generic.LDC; 075import org.apache.bcel.generic.LDC2_W; 076import org.apache.bcel.generic.LineNumberGen; 077import org.apache.bcel.generic.LoadInstruction; 078import org.apache.bcel.generic.LocalVariableGen; 079import org.apache.bcel.generic.LocalVariableInstruction; 080import org.apache.bcel.generic.MULTIANEWARRAY; 081import org.apache.bcel.generic.MethodGen; 082import org.apache.bcel.generic.NOP; 083import org.apache.bcel.generic.ObjectType; 084import org.apache.bcel.generic.PUTFIELD; 085import org.apache.bcel.generic.PUTSTATIC; 086import org.apache.bcel.generic.ReferenceType; 087import org.apache.bcel.generic.ReturnInstruction; 088import org.apache.bcel.generic.SWAP; 089import org.apache.bcel.generic.StoreInstruction; 090import org.apache.bcel.generic.Type; 091import org.apache.bcel.verifier.structurals.OperandStack; 092import org.checkerframework.checker.lock.qual.GuardSatisfied; 093import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; 094import org.checkerframework.checker.nullness.qual.KeyFor; 095import org.checkerframework.checker.nullness.qual.Nullable; 096import org.checkerframework.checker.signature.qual.BinaryName; 097import org.checkerframework.checker.signature.qual.ClassGetName; 098import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; 099import org.checkerframework.dataflow.qual.Pure; 100 101/** 102 * Instruments a class file to perform Dynamic Comparability. 103 * 104 * <p>The DCInstrument class is responsible for modifying another class's bytecodes. Specifically, 105 * its main task is to add calls into the DynComp Runtime to calculate comparability values. These 106 * added calls are sometimes referred to as "hooks". 107 */ 108@SuppressWarnings("nullness") 109public class DCInstrument extends InstructionListUtils { 110 111 /** 112 * Used when testing to continue processing if an error occurs. Currently, This flag is only used 113 * by BuildJDK. 114 */ 115 @Option("Halt if an instrumentation error occurs") 116 public static boolean quit_if_error = true; 117 118 /** Unmodified version of input class. */ 119 protected JavaClass orig_class; 120 121 /** ClassGen for the current class. */ 122 protected ClassGen gen; 123 124 /** MethodGen for the current method. */ 125 protected MethodGen mgen; 126 127 /** Is the current class a member of the JDK? */ 128 protected boolean in_jdk; 129 130 /** The BCEL InstructionFactory for generating byte code instructions. */ 131 protected InstructionFactory ifact; 132 133 /** The loader that loaded the Class to instrument. */ 134 protected @Nullable ClassLoader loader; 135 136 /** Has an {@code <init>} method completed initialization? */ 137 protected boolean constructor_is_initialized; 138 139 /** Local that stores the tag frame for the current method. */ 140 protected LocalVariableGen tag_frame_local; 141 142 // Argument descriptors 143 /** Type array with two objects. */ 144 protected static Type[] two_objects = new Type[] {Type.OBJECT, Type.OBJECT}; 145 146 /** Type array with an object and an int. */ 147 protected static Type[] object_int = new Type[] {Type.OBJECT, Type.INT}; 148 149 /** Type array with a string. */ 150 protected static Type[] string_arg = new Type[] {Type.STRING}; 151 152 /** Type array with an int. */ 153 protected static Type[] integer_arg = new Type[] {Type.INT}; 154 155 /** Type array with an object. */ 156 protected static Type[] object_arg = new Type[] {Type.OBJECT}; 157 158 /** ObjectType for "java.lang.Class". */ 159 protected static Type javalangClass = new ObjectType("java.lang.Class"); 160 161 // Type descriptors 162 protected static Type object_arr = new ArrayType(Type.OBJECT, 1); 163 // private Type int_arr = new ArrayType (Type.INT, 1); 164 protected static ObjectType throwable = new ObjectType("java.lang.Throwable"); 165 protected ObjectType dcomp_marker; 166 protected static ObjectType javalangObject = new ObjectType("java.lang.Object"); 167 168 // Debug loggers 169 /** Log file if debug_native is enabled. */ 170 protected static SimpleLog debug_native = new SimpleLog(false); 171 172 /** Log file if debug_dup is enabled. */ 173 protected static SimpleLog debug_dup = new SimpleLog(false); 174 175 /** 176 * Debug information about which classes and/or methods are transformed and why. Use 177 * debugInstrument for actual instrumentation details. 178 */ 179 protected static SimpleLog debug_transform = new SimpleLog(false); 180 181 // Flags to enable additional console output for debugging 182 /** If true, enable JUnit analysis debugging. */ 183 protected static final boolean debugJUnitAnalysis = false; 184 185 /** If true, enable {@link #getDefiningInterface} debugging. */ 186 protected static final boolean debugGetDefiningInterface = false; 187 188 /** If true, enable {@link #handleInvoke} debugging. */ 189 protected static final boolean debugHandleInvoke = false; 190 191 /** Keeps track of the methods that were not successfully instrumented. */ 192 protected List<String> skipped_methods = new ArrayList<>(); 193 194 /** Either "java.lang" or "daikon.dcomp". */ 195 protected @DotSeparatedIdentifiers String dcomp_prefix; 196 197 /** Either "daikon.dcomp.DCRuntime" or "java.lang.DCRuntime". */ 198 protected @DotSeparatedIdentifiers String dcompRuntimeClassName = "daikon.dcomp.DCRuntime"; 199 200 /** Set of JUnit test classes. */ 201 protected static Set<String> junitTestClasses = new HashSet<>(); 202 203 /** Possible states of JUnit test discovery. */ 204 protected enum JUnitState { 205 NOT_SEEN, 206 STARTING, 207 TEST_DISCOVERY, 208 RUNNING 209 }; 210 211 /** Current state of JUnit test discovery. */ 212 protected static JUnitState junit_state = JUnitState.NOT_SEEN; 213 214 /** Have we seen 'JUnitCommandLineParseResult.parse'? */ 215 protected static boolean junit_parse_seen = false; 216 217 /** 218 * Map from each static field name to its unique integer id. Note that while it's intuitive to 219 * think that each static should show up exactly once, that is not the case. A static defined in a 220 * superclass can be accessed through each of its subclasses. Tag accessor methods must be added 221 * in each subclass and each should return the same id. We thus will lookup the same name multiple 222 * times. 223 */ 224 static Map<String, Integer> static_field_id = new LinkedHashMap<>(); 225 226 /** 227 * Map from class name to its access_flags. Used to cache the results of the lookup done in {@link 228 * #getAccessFlags}. If a class is marked ACC_ANNOTATION then it will not have been instrumented. 229 */ 230 static Map<String, Integer> accessFlags = new HashMap<>(); 231 232 /** Integer constant of access_flag value of ACC_ANNOTATION. */ 233 static Integer Integer_ACC_ANNOTATION = Integer.valueOf(Const.ACC_ANNOTATION); 234 235 /** 236 * Array of classes whose fields are not initialized from java. Since the fields are not 237 * initialized from java, their tag storage is not allocated as part of a store, but rather must 238 * be allocated as part of a load. We call a special runtime method for this so that we can check 239 * for this in other cases. 240 */ 241 protected static String[] uninit_classes = 242 new String[] { 243 "java.lang.String", 244 "java.lang.Class", 245 "java.lang.StringBuilder", 246 "java.lang.AbstractStringBuilder", 247 }; 248 249 /** 250 * List of Object methods. Since we can't instrument Object, none of these can be instrumented, 251 * and most of them don't provide useful comparability information anyway. The equals method and 252 * the clone method are special-cased in the {@link #handleInvoke} routine. 253 */ 254 protected static MethodDef[] obj_methods = 255 new MethodDef[] { 256 new MethodDef("finalize", new Type[0]), 257 new MethodDef("getClass", new Type[0]), 258 new MethodDef("hashCode", new Type[0]), 259 new MethodDef("notify", new Type[0]), 260 new MethodDef("notifyall", new Type[0]), 261 new MethodDef("toString", new Type[0]), 262 new MethodDef("wait", new Type[0]), 263 new MethodDef("wait", new Type[] {Type.LONG}), 264 new MethodDef("wait", new Type[] {Type.LONG, Type.INT}), 265 }; 266 267 protected static InstructionList global_catch_il = null; 268 protected static CodeExceptionGen global_exception_handler = null; 269 private InstructionHandle insertion_placeholder; 270 271 /** Class that defines a method (by its name and argument types) */ 272 static class MethodDef { 273 String name; 274 Type[] arg_types; 275 276 MethodDef(String name, Type[] arg_types) { 277 this.name = name; 278 this.arg_types = arg_types; 279 } 280 281 @EnsuresNonNullIf(result = true, expression = "#1") 282 boolean equals(@GuardSatisfied MethodDef this, String name, Type[] arg_types) { 283 if (!name.equals(this.name)) { 284 return false; 285 } 286 if (this.arg_types.length != arg_types.length) { 287 return false; 288 } 289 for (int ii = 0; ii < arg_types.length; ii++) { 290 if (!arg_types[ii].equals(this.arg_types[ii])) { 291 return false; 292 } 293 } 294 return true; 295 } 296 297 @EnsuresNonNullIf(result = true, expression = "#1") 298 @Pure 299 @Override 300 public boolean equals(@GuardSatisfied MethodDef this, @GuardSatisfied @Nullable Object obj) { 301 if (!(obj instanceof MethodDef)) { 302 return false; 303 } 304 MethodDef md = (MethodDef) obj; 305 return equals(md.name, md.arg_types); 306 } 307 308 @Pure 309 @Override 310 public int hashCode(@GuardSatisfied MethodDef this) { 311 int code = name.hashCode(); 312 for (Type arg : arg_types) { 313 code += arg.hashCode(); 314 } 315 return code; 316 } 317 } 318 319 /** Initialize with the original class and whether or not the class is part of the JDK. */ 320 @SuppressWarnings("StaticAssignmentInConstructor") // instrumentation_interface 321 public DCInstrument(JavaClass orig_class, boolean in_jdk, @Nullable ClassLoader loader) { 322 super(); 323 this.orig_class = orig_class; 324 this.in_jdk = in_jdk; 325 this.loader = loader; 326 gen = new ClassGen(orig_class); 327 pool = gen.getConstantPool(); 328 ifact = new InstructionFactory(gen); 329 constructor_is_initialized = false; 330 if (Premain.jdk_instrumented) { 331 dcomp_prefix = "java.lang"; 332 } else { 333 dcomp_prefix = "daikon.dcomp"; 334 } 335 dcomp_marker = new ObjectType(Signatures.addPackage(dcomp_prefix, "DCompMarker")); 336 if (BcelUtil.javaVersion == 8) { 337 dcomp_prefix = "daikon.dcomp"; 338 } 339 DCRuntime.instrumentation_interface = Signatures.addPackage(dcomp_prefix, "DCompInstrumented"); 340 341 // System.out.printf("DCInstrument %s%n", orig_class.getClassName()); 342 // Turn on some of the logging based on debug option. 343 debugInstrument.enabled = DynComp.debug || Premain.debug_dcinstrument; 344 debug_native.enabled = DynComp.debug; 345 debug_transform.enabled = daikon.dcomp.Instrument.debug_transform.enabled; 346 } 347 348 /** 349 * Instruments the original class to perform dynamic comparabilty and returns the new class 350 * definition. 351 * 352 * @return the modified JavaClass 353 */ 354 public JavaClass instrument() { 355 356 @BinaryName String classname = gen.getClassName(); 357 358 // Don't know where I got this idea. They are executed. Don't remember why 359 // adding dcomp marker causes problems. 360 // Don't instrument annotations. They aren't executed and adding 361 // the marker argument causes subtle errors 362 if ((gen.getModifiers() & Const.ACC_ANNOTATION) != 0) { 363 debug_transform.log("Not instrumenting annotation %s%n", classname); 364 // WHY NOT RETURN NULL? 365 return gen.getJavaClass().copy(); 366 } 367 368 // If a class has an EvoSuite annotation it may be instrumented by Evosuite; 369 // thus, we should not instrument it before Evosuite does. 370 for (final Attribute attribute : orig_class.getAttributes()) { 371 if (attribute instanceof RuntimeVisibleAnnotations) { 372 for (final AnnotationEntry item : ((Annotations) attribute).getAnnotationEntries()) { 373 if (item.toString().startsWith("@Lorg/evosuite/runtime")) { 374 debug_transform.log("Not instrumenting possible Evosuite target: %s%n", classname); 375 // WHY NOT RETURN NULL? 376 return gen.getJavaClass().copy(); 377 } 378 } 379 } 380 } 381 382 debug_transform.log("Instrumenting class %s%n", classname); 383 debug_transform.indent(); 384 385 // Create the ClassInfo for this class and its list of methods 386 ClassInfo class_info = new ClassInfo(classname, loader); 387 boolean track_class = false; 388 389 // Handle object methods for this class 390 handle_object(gen); 391 392 // Have all top-level classes implement our interface 393 if (gen.getSuperclassName().equals("java.lang.Object")) { 394 // Add equals method if it doesn't already exist. This ensures 395 // that an instrumented version, equals(Object, DCompMarker), 396 // will be created in this class. 397 Method eq = gen.containsMethod("equals", "(Ljava/lang/Object;)Z"); 398 if (eq == null) { 399 debugInstrument.log("Added equals method%n"); 400 add_equals_method(gen); 401 } 402 403 // Add DCompInstrumented interface and the required 404 // equals_dcomp_instrumented method. 405 add_dcomp_interface(gen); 406 } 407 408 // A very tricky special case: If JUnit is running and the current 409 // class has been passed to JUnit on the command line, then this 410 // is a JUnit test class and our normal instrumentation will 411 // cause JUnit to complain about multiple constructors and 412 // methods that should have no arguments. To work around these 413 // restrictions, we replace rather than duplicate each method 414 // we instrument and we do not add the dcomp marker argument. 415 // We must also remember the class name so if we see a subsequent 416 // call to one of its methods we do not add the dcomp argument. 417 418 debugInstrument.log("junit_state: %s%n", junit_state); 419 420 StackTraceElement[] stack_trace; 421 422 switch (junit_state) { 423 case NOT_SEEN: 424 if (classname.startsWith("org.junit")) { 425 junit_state = JUnitState.STARTING; 426 } 427 break; 428 429 case STARTING: 430 // Now check to see if JUnit is looking for test class(es). 431 stack_trace = Thread.currentThread().getStackTrace(); 432 // [0] is getStackTrace 433 for (int i = 1; i < stack_trace.length; i++) { 434 if (debugJUnitAnalysis) { 435 System.out.printf( 436 "%s : %s%n", stack_trace[i].getClassName(), stack_trace[i].getMethodName()); 437 } 438 if (isJunitTrigger(stack_trace[i].getClassName(), stack_trace[i].getMethodName())) { 439 junit_parse_seen = true; 440 junit_state = JUnitState.TEST_DISCOVERY; 441 break; 442 } 443 } 444 break; 445 446 case TEST_DISCOVERY: 447 // Now check to see if JUnit is done looking for test class(es). 448 boolean local_junit_parse_seen = false; 449 stack_trace = Thread.currentThread().getStackTrace(); 450 // [0] is getStackTrace 451 for (int i = 1; i < stack_trace.length; i++) { 452 if (debugJUnitAnalysis) { 453 System.out.printf( 454 "%s : %s%n", stack_trace[i].getClassName(), stack_trace[i].getMethodName()); 455 } 456 if (isJunitTrigger(stack_trace[i].getClassName(), stack_trace[i].getMethodName())) { 457 local_junit_parse_seen = true; 458 break; 459 } 460 } 461 if (junit_parse_seen && !local_junit_parse_seen) { 462 junit_parse_seen = false; 463 junit_state = JUnitState.RUNNING; 464 } else if (!junit_parse_seen && local_junit_parse_seen) { 465 junit_parse_seen = true; 466 } 467 break; 468 469 case RUNNING: 470 if (debugJUnitAnalysis) { 471 stack_trace = Thread.currentThread().getStackTrace(); 472 // [0] is getStackTrace 473 for (int i = 1; i < stack_trace.length; i++) { 474 System.out.printf( 475 "%s : %s%n", stack_trace[i].getClassName(), stack_trace[i].getMethodName()); 476 } 477 } 478 // nothing to do 479 break; 480 481 default: 482 throw new Error("invalid junit_state"); 483 } 484 485 debugInstrument.log("junit_state: %s%n", junit_state); 486 487 boolean junit_test_class = false; 488 if (junit_state == JUnitState.TEST_DISCOVERY) { 489 // We have a possible JUnit test class. We need to verify by 490 // one of two methods. Either the class is a subclass of 491 // junit.framework.TestCase or one of its methods has a 492 // RuntimeVisibleAnnotation of org/junit/Test. 493 Deque<String> classnameStack = new ArrayDeque<>(); 494 String super_class; 495 String this_class = classname; 496 while (true) { 497 super_class = getSuperclassName(this_class); 498 if (super_class == null) { 499 // something has gone wrong 500 break; 501 } 502 if (debugJUnitAnalysis) { 503 System.out.printf("this_class: %s%n", this_class); 504 System.out.printf("super_class: %s%n", super_class); 505 } 506 if (super_class.equals("junit.framework.TestCase")) { 507 // This is a junit test class and so are the 508 // elements of classnameStack. 509 junit_test_class = true; 510 junitTestClasses.add(this_class); 511 while (!classnameStack.isEmpty()) { 512 junitTestClasses.add(classnameStack.pop()); 513 } 514 break; 515 } else if (super_class.equals("java.lang.Object")) { 516 // We're done; not a junit test class. 517 // Ignore items on classnameStack. 518 break; 519 } 520 // Recurse and check the super_class. 521 classnameStack.push(this_class); 522 this_class = super_class; 523 } 524 } 525 526 // Even if we have not detected that JUnit is active, any class that 527 // contains a method with a RuntimeVisibleAnnotation of org/junit/Test 528 // needs to be marked as a JUnit test class. (Daikon issue #536) 529 530 if (!junit_test_class) { 531 // need to check for junit Test annotation on a method 532 searchloop: 533 for (Method m : gen.getMethods()) { 534 for (final Attribute attribute : m.getAttributes()) { 535 if (attribute instanceof RuntimeVisibleAnnotations) { 536 if (debugJUnitAnalysis) { 537 System.out.printf("attribute: %s%n", attribute.toString()); 538 } 539 for (final AnnotationEntry item : ((Annotations) attribute).getAnnotationEntries()) { 540 if (debugJUnitAnalysis) { 541 System.out.printf("item: %s%n", item.toString()); 542 } 543 if (item.toString().endsWith("org/junit/Test;") // JUnit 4 544 || item.toString().endsWith("org/junit/jupiter/api/Test;") // JUnit 5 545 ) { 546 junit_test_class = true; 547 junitTestClasses.add(classname); 548 break searchloop; 549 } 550 } 551 } 552 } 553 } 554 } 555 556 if (junit_test_class) { 557 debugInstrument.log("JUnit test class: %s%n", classname); 558 } else { 559 debugInstrument.log("Not a JUnit test class: %s%n", classname); 560 } 561 562 // Process each method 563 for (Method m : gen.getMethods()) { 564 565 tag_frame_local = null; 566 try { 567 // Note whether we want to track the daikon variables in this method 568 boolean track = should_track(classname, m.getName(), methodEntryName(classname, m)); 569 570 // We do not want to track bridge methods the compiler has synthesized as 571 // they are overloaded on return type which normal Java does not support. 572 if ((m.getAccessFlags() & Const.ACC_BRIDGE) != 0) { 573 track = false; 574 } 575 576 // If any one method is tracked, then the class is tracked. 577 if (track) { 578 track_class = true; 579 } 580 581 // If we are tracking variables, make sure the class is public 582 if (track && !gen.isPublic()) { 583 gen.isPrivate(false); 584 gen.isProtected(false); 585 gen.isPublic(true); 586 } 587 588 debug_transform.log(" Processing method %s, track=%b%n", simplify_method_name(m), track); 589 debug_transform.indent(); 590 591 MethodGen mg = new MethodGen(m, classname, pool); 592 mgen = mg; // copy to global 593 594 InstructionList il = mg.getInstructionList(); 595 boolean has_code = (il != null); 596 if (has_code) { 597 setCurrentStackMapTable(mg, gen.getMajor()); 598 buildUninitializedNewMap(il); 599 } 600 601 fixLocalVariableTable(mg); 602 603 // If the method is native 604 if (mg.isNative()) { 605 606 // Create Java code that cleans up the tag stack and calls the real native method. 607 fix_native(gen, mg); 608 has_code = true; 609 setCurrentStackMapTable(mg, gen.getMajor()); 610 611 // Add the DCompMarker argument to distinguish our version 612 add_dcomp_arg(mg); 613 614 } else { // normal method 615 616 if (!junit_test_class) { 617 // Add the DCompMarker argument to distinguish our version 618 add_dcomp_arg(mg); 619 } 620 621 // Create a MethodInfo that describes this method's arguments 622 // and exit line numbers (information not available via reflection) 623 // and add it to the list for this class. 624 MethodInfo mi = null; 625 if (track && has_code) { 626 mi = create_method_info(class_info, mg); 627 class_info.method_infos.add(mi); 628 DCRuntime.methods.add(mi); 629 } 630 631 // Instrument the method 632 if (has_code) { 633 // Create the local to store the tag frame for this method 634 tag_frame_local = create_tag_frame_local(mg); 635 build_exception_handler(mg); 636 instrument_method(mg); 637 if (track) { 638 add_enter(mg, mi, DCRuntime.methods.size() - 1); 639 add_exit(mg, mi, DCRuntime.methods.size() - 1); 640 } 641 install_exception_handler(mg); 642 } 643 } 644 645 if (has_code) { 646 updateUninitializedNewOffsets(mg.getInstructionList()); 647 createNewStackMapAttribute(mg); 648 mg.setMaxLocals(); 649 mg.setMaxStack(); 650 } else { 651 mg.removeCodeAttributes(); 652 mg.removeLocalVariables(); 653 } 654 655 remove_local_variable_type_table(mg); 656 657 // We do not want to copy the @HotSpotIntrinsicCandidate annotations from 658 // the original method to our instrumented method as the signature will 659 // not match anything in the JVM's list. This won't cause an execution 660 // problem but will produce a massive number of warnings. 661 // JDK 11: @HotSpotIntrinsicCandidate 662 // JDK 17: @IntrinsicCandidate 663 AnnotationEntryGen[] aes = mg.getAnnotationEntries(); 664 for (AnnotationEntryGen item : aes) { 665 String type = item.getTypeName(); 666 if (type.endsWith("IntrinsicCandidate;")) { 667 mg.removeAnnotationEntry(item); 668 } 669 } 670 671 // Can't duplicate 'main' or 'clinit' or a JUnit test. 672 boolean replacingMethod = BcelUtil.isMain(mg) || BcelUtil.isClinit(mg) || junit_test_class; 673 try { 674 if (has_code) { 675 il = mg.getInstructionList(); 676 InstructionHandle end = il.getEnd(); 677 int length = end.getPosition() + end.getInstruction().getLength(); 678 if (length >= Const.MAX_CODE_SIZE) { 679 throw new ClassGenException( 680 "Code array too big: must be smaller than " + Const.MAX_CODE_SIZE + " bytes."); 681 } 682 } 683 if (replacingMethod) { 684 gen.replaceMethod(m, mg.getMethod()); 685 if (BcelUtil.isMain(mg)) { 686 gen.addMethod(create_dcomp_stub(mg).getMethod()); 687 } 688 } else { 689 gen.addMethod(mg.getMethod()); 690 } 691 } catch (Exception e) { 692 String s = e.getMessage(); 693 if (s == null) { 694 throw e; 695 } 696 if (s.startsWith("Branch target offset too large") 697 || s.startsWith("Code array too big")) { 698 System.err.printf( 699 "DynComp warning: ClassFile: %s - method %s is too large to instrument and is" 700 + " being skipped.%n", 701 classname, mg.getName()); 702 // Build a dummy instrumented method that has DCompMarker 703 // argument and no instrumentation. 704 // first, restore unmodified method 705 mg = new MethodGen(m, classname, pool); 706 // restore StackMapTable 707 setCurrentStackMapTable(mg, gen.getMajor()); 708 // Add the DCompMarker argument 709 add_dcomp_arg(mg); 710 remove_local_variable_type_table(mg); 711 // try again 712 if (replacingMethod) { 713 gen.replaceMethod(m, mg.getMethod()); 714 if (BcelUtil.isMain(mg)) { 715 gen.addMethod(create_dcomp_stub(mg).getMethod()); 716 } 717 } else { 718 gen.addMethod(mg.getMethod()); 719 } 720 } else { 721 throw e; 722 } 723 } 724 debug_transform.exdent(); 725 } catch (Throwable t) { 726 // debug code 727 // t.printStackTrace(); 728 if (debugInstrument.enabled) { 729 t.printStackTrace(); 730 } 731 throw new Error("Unexpected error processing " + classname + "." + m.getName(), t); 732 } 733 } 734 735 // Add tag accessor methods for each primitive in the class 736 create_tag_accessors(gen); 737 738 // Keep track of when the class is initialized (so we don't look 739 // for fields in uninitialized classes) 740 track_class_init(); 741 debug_transform.exdent(); 742 743 // The code that builds the list of daikon variables for each ppt 744 // needs to know what classes are instrumented. Its looks in the 745 // Chicory runtime for this information. 746 if (track_class) { 747 debug_transform.log("DCInstrument adding %s to all class list%n", class_info); 748 synchronized (daikon.chicory.SharedData.all_classes) { 749 daikon.chicory.SharedData.all_classes.add(class_info); 750 } 751 } 752 debug_transform.log("Instrumentation complete: %s%n", classname); 753 754 return gen.getJavaClass().copy(); 755 } 756 757 /** 758 * Returns true if the specified classname.method_name is the root of JUnit startup code. 759 * 760 * @param classname class to be checked 761 * @param method_name method to be checked 762 * @return true if the given method is a JUnit trigger 763 */ 764 boolean isJunitTrigger(String classname, String method_name) { 765 if ((classname.contains("JUnitCommandLineParseResult") 766 && method_name.equals("parse")) // JUnit 4 767 || (classname.contains("EngineDiscoveryRequestResolution") 768 && method_name.equals("resolve")) // JUnit 5 769 ) { 770 return true; 771 } 772 return false; 773 } 774 775 // General Java Runtime instrumentation strategy: 776 // 777 // <p>It is a bit of a misnomer, but the Daikon code and documentation uses the term JDK to refer 778 // to the Java Runtime Environment class libraries. In Java 8 and earlier, they were usually found 779 // in {@code <your java installation>/jre/lib/rt.jar}. For these versions of Java, we 780 // pre-instrumented the entire rt.jar. 781 // 782 // <p>In Java 9 and later, the Java Runtime classes have been divided into modules that are 783 // usually found in: {@code <your java installation>/jmods/*.jmod}. 784 // 785 // <p>With the conversion to modules for Java 9 and beyond, we have elected to pre-instrument only 786 // java.base.jmod and instrument all other Java Runtime (aka JDK) classes dynamically as they are 787 // loaded. 788 // 789 // <p>Post Java 8 there are increased security checks when loading JDK classes. In particular, the 790 // core classes contained in the java.base module may not reference anything outside of java.base. 791 // This means we cannot pre-instrument classes in the same manner as was done for Java 8 as this 792 // would introduce external references to the DynComp runtime (DCRuntime.java). 793 // 794 // <p>However, we can get around this restriction in the following manner: We create a shadow 795 // DynComp runtime called java.lang.DCRuntime that contains all the public methods of 796 // daikon.dcomp.DCRuntime, but with method bodies that contain only a return statement. We 797 // pre-instrument java.base the same as we would for JDK 8, but change all references to 798 // daikon.dcomp.DCRuntime to refer to java.lang.DCRuntime instead, and thus pass the security load 799 // test. During DynComp initialization (in Premain) we use java.lang.instrument.redefineClasses to 800 // replace the dummy java.lang.DCRuntime with a version where each method calls the corresponding 801 // method in daikon.dcomp.DCRuntime. The Java runtime does not enforce the security check in this 802 // case. 803 804 /** 805 * Instruments a JDK class to perform dynamic comparability and returns the new class definition. 806 * A second version of each method in the class is created which is instrumented for 807 * comparability. 808 * 809 * @return the modified JavaClass 810 */ 811 public JavaClass instrument_jdk() { 812 813 String classname = gen.getClassName(); 814 815 // Don't instrument annotations. They aren't executed and adding 816 // the marker argument causes subtle errors 817 if ((gen.getModifiers() & Const.ACC_ANNOTATION) != 0) { 818 debug_transform.log("Not instrumenting annotation %s%n", classname); 819 // MUST NOT RETURN NULL 820 return gen.getJavaClass().copy(); 821 } 822 823 int i = classname.lastIndexOf('.'); 824 if (i > 0) { 825 // Don't instrument problem packages. 826 // See Premain.java for a list and explainations. 827 String packageName = classname.substring(0, i); 828 if (Premain.problem_packages.contains(packageName)) { 829 debug_transform.log("Skipping problem package %s%n", packageName); 830 return gen.getJavaClass().copy(); 831 } 832 } 833 834 if (BcelUtil.javaVersion > 8) { 835 // Don't instrument problem classes. 836 // See Premain.java for a list and explainations. 837 if (Premain.problem_classes.contains(classname)) { 838 debug_transform.log("Skipping problem class %s%n", classname); 839 return gen.getJavaClass().copy(); 840 } 841 dcompRuntimeClassName = "java.lang.DCRuntime"; 842 } 843 844 debug_transform.log("Instrumenting class(JDK) %s%n", classname); 845 debug_transform.indent(); 846 847 // Handle object methods for this class 848 handle_object(gen); 849 850 // Have all top-level classes implement our interface 851 if (gen.getSuperclassName().equals("java.lang.Object")) { 852 // Add equals method if it doesn't already exist. This ensures 853 // that an instrumented version, equals(Object, DCompMarker), 854 // will be created in this class. 855 Method eq = gen.containsMethod("equals", "(Ljava/lang/Object;)Z"); 856 if (eq == null) { 857 debugInstrument.log("Added equals method%n"); 858 add_equals_method(gen); 859 } 860 // Add DCompInstrumented interface and the required 861 // equals_dcomp_instrumented method. 862 add_dcomp_interface(gen); 863 } 864 865 // Process each method 866 for (Method m : gen.getMethods()) { 867 868 tag_frame_local = null; 869 try { 870 // Don't modify class initialization methods. They can't affect 871 // user comparability and there isn't any way to get a second 872 // copy of them. 873 if (BcelUtil.isClinit(m)) { 874 continue; 875 } 876 877 debug_transform.log(" Processing method %s%n", simplify_method_name(m)); 878 debug_transform.indent(); 879 880 MethodGen mg = new MethodGen(m, classname, pool); 881 mgen = mg; // copy to global 882 883 InstructionList il = mg.getInstructionList(); 884 boolean has_code = (il != null); 885 if (has_code) { 886 setCurrentStackMapTable(mg, gen.getMajor()); 887 buildUninitializedNewMap(il); 888 } 889 890 fixLocalVariableTable(mg); 891 892 // If the method is native 893 if (mg.isNative()) { 894 895 // Create Java code that cleans up the tag stack and calls the real native method. 896 fix_native(gen, mg); 897 has_code = true; 898 setCurrentStackMapTable(mg, gen.getMajor()); 899 900 // Add the DCompMarker argument to distinguish our version 901 add_dcomp_arg(mg); 902 903 } else { // normal method 904 905 // Add the DCompMarker argument to distinguish our version 906 add_dcomp_arg(mg); 907 908 // Instrument the method 909 if (has_code) { 910 // Create the local to store the tag frame for this method 911 tag_frame_local = create_tag_frame_local(mg); 912 build_exception_handler(mg); 913 instrument_method(mg); 914 install_exception_handler(mg); 915 } 916 } 917 918 if (has_code) { 919 updateUninitializedNewOffsets(mg.getInstructionList()); 920 createNewStackMapAttribute(mg); 921 mg.setMaxLocals(); 922 mg.setMaxStack(); 923 } else { 924 mg.removeCodeAttributes(); 925 mg.removeLocalVariables(); 926 } 927 928 remove_local_variable_type_table(mg); 929 930 // We do not want to copy the @HotSpotIntrinsicCandidate annotations from 931 // the original method to our instrumented method as the signature will 932 // not match anything in the JVM's list. This won't cause an execution 933 // problem but will produce a massive number of warnings. 934 // JDK 11: @HotSpotIntrinsicCandidate 935 // JDK 17: @IntrinsicCandidate 936 AnnotationEntryGen[] aes = mg.getAnnotationEntries(); 937 for (AnnotationEntryGen item : aes) { 938 String type = item.getTypeName(); 939 if (type.endsWith("IntrinsicCandidate;")) { 940 mg.removeAnnotationEntry(item); 941 } 942 } 943 944 try { 945 if (has_code) { 946 il = mg.getInstructionList(); 947 InstructionHandle end = il.getEnd(); 948 int length = end.getPosition() + end.getInstruction().getLength(); 949 if (length >= Const.MAX_CODE_SIZE) { 950 throw new ClassGenException( 951 "Code array too big: must be smaller than " + Const.MAX_CODE_SIZE + " bytes."); 952 } 953 } 954 gen.addMethod(mg.getMethod()); 955 } catch (Exception e) { 956 String s = e.getMessage(); 957 if (s == null) { 958 throw e; 959 } 960 if (s.startsWith("Branch target offset too large") 961 || s.startsWith("Code array too big")) { 962 System.err.printf( 963 "DynComp warning: ClassFile: %s - method %s is too large to instrument and is" 964 + " being skipped.%n", 965 classname, mg.getName()); 966 // Build a dummy instrumented method that has DCompMarker 967 // argument and no instrumentation. 968 // first, restore unmodified method 969 mg = new MethodGen(m, classname, pool); 970 // restore StackMapTable 971 setCurrentStackMapTable(mg, gen.getMajor()); 972 // Add the DCompMarker argument 973 add_dcomp_arg(mg); 974 remove_local_variable_type_table(mg); 975 // try again 976 gen.addMethod(mg.getMethod()); 977 } else { 978 throw e; 979 } 980 } 981 982 debug_transform.exdent(); 983 } catch (Throwable t) { 984 if (debugInstrument.enabled) { 985 t.printStackTrace(); 986 } 987 skip_method(mgen); 988 if (quit_if_error) { 989 throw new Error("Unexpected error processing " + classname + "." + m.getName(), t); 990 } else { 991 System.err.printf("Unexpected error processing %s.%s: %s%n", classname, m.getName(), t); 992 System.err.printf("Method is NOT instrumented.%n"); 993 } 994 } 995 } 996 997 // Add tag accessor methods for each primitive in the class 998 create_tag_accessors(gen); 999 1000 // We don't need to track class initialization in the JDK because 1001 // that is only used when printing comparability which is only done 1002 // for client classes 1003 // track_class_init(); 1004 1005 debug_transform.exdent(); 1006 debug_transform.log("Instrumentation complete: %s%n", classname); 1007 1008 return gen.getJavaClass().copy(); 1009 } 1010 1011 /** 1012 * Instrument the specified method for dynamic comparability. 1013 * 1014 * @param mg MethodGen for the method to be instrumented 1015 */ 1016 public void instrument_method(MethodGen mg) { 1017 1018 // Because the tag_frame_local is active for the entire method 1019 // and its creation will change the state of the locals layout, 1020 // we need to insert the code to initialize it now so that the 1021 // stack anaylsis we are about to do is correct for potential 1022 // code replacements we might make later. 1023 InstructionHandle orig_start = mg.getInstructionList().getStart(); 1024 add_create_tag_frame(mg); 1025 // Calculate the operand stack value(s) for revised code. 1026 mg.setMaxStack(); 1027 // Calculate stack types information 1028 StackTypes stack_types = bcelCalcStackTypes(mg); 1029 if (stack_types == null) { 1030 skip_method(mg); 1031 return; 1032 } 1033 1034 InstructionList il = mg.getInstructionList(); 1035 OperandStack stack = null; 1036 1037 // Prior to adding support for Stack Maps, the position field 1038 // of each InstructionHandle was not updated until the modified 1039 // method was written out. Hence, it could be used as the 1040 // index into stack_types. To support StackMaps we need to 1041 // update the position field as we modify the code bytes. So 1042 // we need a mapping from InstructionHandle to orignal offset. 1043 // I beleive we always visit the InstructionHandle nodes of 1044 // the method's InstructionList in order - hence, we will use 1045 // a simple array for now. If this turns out to not be the 1046 // case we will need to use a hash map. 1047 1048 int[] handle_offsets = new int[il.getLength()]; 1049 InstructionHandle ih = orig_start; 1050 int index = 0; 1051 // Loop through each instruction, building up offset map. 1052 while (ih != null) { 1053 handle_offsets[index++] = ih.getPosition(); 1054 1055 if (debugInstrument.enabled) { 1056 debugInstrument.log("inst: %s %n", ih); 1057 for (InstructionTargeter it : ih.getTargeters()) { 1058 debugInstrument.log("targeter: %s %n", it); 1059 } 1060 } 1061 1062 ih = ih.getNext(); 1063 } 1064 1065 index = 0; 1066 // Loop through each instruction, making substitutions 1067 for (ih = orig_start; ih != null; ) { 1068 debugInstrument.log("instrumenting instruction %s%n", ih); 1069 InstructionList new_il = null; 1070 1071 // Remember the next instruction to process 1072 InstructionHandle next_ih = ih.getNext(); 1073 1074 // Get the stack information 1075 stack = stack_types.get(handle_offsets[index++]); 1076 1077 // Get the translation for this instruction (if any) 1078 new_il = xform_inst(mg, ih, stack); 1079 1080 // If this instruction was modified, replace it with the new 1081 // instruction list. If this instruction was the target of any 1082 // jumps or line numbers, replace them with the first 1083 // instruction in the new list. 1084 replaceInstructions(mg, il, ih, new_il); 1085 1086 // If the modified method is now too large, we quit instrumenting the method 1087 // and will rediscover the problem in the main instrumentation loop above 1088 // and deal with it there. 1089 if (ih.getPosition() >= Const.MAX_CODE_SIZE) { 1090 break; 1091 } 1092 1093 ih = next_ih; 1094 } 1095 } 1096 1097 /** 1098 * Adds the method name and containing class name to {@code skip_methods}, the list of 1099 * uninstrumented methods. 1100 * 1101 * @param mg method to add to skipped_methods list 1102 */ 1103 void skip_method(MethodGen mg) { 1104 skipped_methods.add(mg.getClassName() + "." + mg.getName()); 1105 } 1106 1107 /** 1108 * Returns the list of uninstrumented methods. (Note: instrument_jdk() needs to have been called 1109 * first.) 1110 */ 1111 public List<String> get_skipped_methods() { 1112 return new ArrayList<String>(skipped_methods); 1113 } 1114 1115 /** 1116 * Adds a try/catch block around the entire method. If an exception occurs, the tag stack is 1117 * cleaned up and the exception is rethrown. 1118 */ 1119 public void build_exception_handler(MethodGen mg) { 1120 1121 if (mg.getName().equals("main")) { 1122 global_catch_il = null; 1123 global_exception_handler = null; 1124 return; 1125 } 1126 1127 InstructionList il = new InstructionList(); 1128 il.append(new DUP()); 1129 il.append( 1130 ifact.createInvoke( 1131 dcompRuntimeClassName, "exception_exit", Type.VOID, object_arg, Const.INVOKESTATIC)); 1132 il.append(new ATHROW()); 1133 1134 add_exception_handler(mg, il); 1135 } 1136 1137 /** Adds a try/catch block around the entire method. */ 1138 public void add_exception_handler(MethodGen mg, InstructionList catch_il) { 1139 1140 // <init> methods (constructors) turn out to be problematic 1141 // for adding a whole method exception handler. The start of 1142 // the exception handler should be after the primary object is 1143 // initialized - but this is hard to determine without a full 1144 // analysis of the code. Hence, we just skip these methods. 1145 if (!mg.isStatic()) { 1146 if (BcelUtil.isConstructor(mg)) { 1147 global_catch_il = null; 1148 global_exception_handler = null; 1149 return; 1150 } 1151 } 1152 1153 InstructionList cur_il = mg.getInstructionList(); 1154 InstructionHandle start = cur_il.getStart(); 1155 InstructionHandle end = cur_il.getEnd(); 1156 1157 // This is just a temporary handler to get the start and end 1158 // address tracked as we make code modifications. 1159 global_catch_il = catch_il; 1160 global_exception_handler = new CodeExceptionGen(start, end, null, throwable); 1161 } 1162 1163 /** Adds a try/catch block around the entire method. */ 1164 public void install_exception_handler(MethodGen mg) { 1165 1166 if (global_catch_il == null) { 1167 return; 1168 } 1169 1170 InstructionList cur_il = mg.getInstructionList(); 1171 InstructionHandle start = global_exception_handler.getStartPC(); 1172 InstructionHandle end = global_exception_handler.getEndPC(); 1173 InstructionHandle exc = cur_il.append(global_catch_il); 1174 cur_il.setPositions(); 1175 mg.addExceptionHandler(start, end, exc, throwable); 1176 // discard temporary handler 1177 global_catch_il = null; 1178 global_exception_handler = null; 1179 1180 if (!needStackMap) { 1181 return; 1182 } 1183 1184 int exc_offset = exc.getPosition(); 1185 1186 debugInstrument.log( 1187 "New ExceptionHandler: %x %x %x %n", start.getPosition(), end.getPosition(), exc_offset); 1188 1189 // This is a trick to get runningOffset set to 1190 // value of last stack map entry. 1191 updateStackMapOffset(exc_offset, 0); 1192 int map_offset = exc_offset - runningOffset - 1; 1193 1194 // Get the argument types for this method 1195 Type[] arg_types = mg.getArgumentTypes(); 1196 1197 int arg_index = (mg.isStatic() ? 0 : 1); 1198 StackMapType[] arg_map_types = new StackMapType[arg_types.length + arg_index]; 1199 if (!mg.isStatic()) { 1200 arg_map_types[0] = 1201 new StackMapType( 1202 Const.ITEM_Object, pool.addClass(mg.getClassName()), pool.getConstantPool()); 1203 } 1204 for (int ii = 0; ii < arg_types.length; ii++) { 1205 arg_map_types[arg_index++] = generateStackMapTypeFromType(arg_types[ii]); 1206 } 1207 1208 StackMapEntry map_entry; 1209 StackMapType stack_map_type = 1210 new StackMapType( 1211 Const.ITEM_Object, pool.addClass(throwable.getClassName()), pool.getConstantPool()); 1212 StackMapType[] stack_map_types = {stack_map_type}; 1213 map_entry = 1214 new StackMapEntry( 1215 Const.FULL_FRAME, map_offset, arg_map_types, stack_map_types, pool.getConstantPool()); 1216 1217 int orig_size = stackMapTable.length; 1218 StackMapEntry[] new_stack_map_table = new StackMapEntry[orig_size + 1]; 1219 System.arraycopy(stackMapTable, 0, new_stack_map_table, 0, orig_size); 1220 new_stack_map_table[orig_size] = map_entry; 1221 stackMapTable = new_stack_map_table; 1222 } 1223 1224 /** 1225 * Adds the code to create the tag frame to the beginning of the method. This needs to be before 1226 * the call to DCRuntime.enter (since it passed to that method). 1227 */ 1228 public void add_create_tag_frame(MethodGen mg) { 1229 1230 InstructionList nl = create_tag_frame(mg, tag_frame_local); 1231 1232 // We add a temporary NOP at the end of the create_tag_frame 1233 // code that we will replace with runtime initization code 1234 // later. We do this so that any existing stack map at 1235 // instruction offset 0 is not replaced by the one we are 1236 // about to add for initializing the tag_frame variable. 1237 insertion_placeholder = nl.append(new NOP()); 1238 1239 byte[] code = nl.getByteCode(); 1240 // -1 because of the NOP we inserted. 1241 int len_code = code.length - 1; 1242 1243 insertAtMethodStart(mg, nl); 1244 1245 if (!needStackMap) { 1246 return; 1247 } 1248 1249 // For Java 7 and beyond the StackMapTable is part of the 1250 // verification process. We need to create and or update it to 1251 // account for instrumentation code we have inserted as well as 1252 // adjustments for the new 'tag_frame' local. 1253 1254 // Get existing StackMapTable (if present) 1255 if (stackMapTable.length > 0) { 1256 // Each stack map frame specifies (explicity or implicitly) an 1257 // offset_delta that is used to calculate the actual bytecode 1258 // offset at which the frame applies. This is caluclated by 1259 // by adding offset_delta + 1 to the bytecode offset of the 1260 // previous frame, unless the previous frame is the initial 1261 // frame of the method, in which case the bytecode offset is 1262 // offset_delta. (From the Java Virual Machine Specification, 1263 // Java SE 7 Edition, section 4.7.4) 1264 1265 // Since we are inserting a new stack map frame at the 1266 // beginning of the stack map table, we need to adjust the 1267 // offset_delta of the original first stack map frame due to 1268 // the fact that it will no longer be the first entry. We 1269 // must subtract (len_code + 1). 1270 // (We don't have to worry about the special case of the 1271 // original first entry having an offset of 0 because of the 1272 // NOP we inserted above. 1273 1274 stackMapTable[0].updateByteCodeOffset(-(len_code + 1)); 1275 } 1276 1277 int new_table_length = stackMapTable.length + 1; 1278 StackMapEntry[] new_stack_map_table = new StackMapEntry[new_table_length]; 1279 1280 // Insert a new StackMapEntry at the beginning of the table 1281 // that adds the tag_frame variable. 1282 StackMapType tag_frame_type = generateStackMapTypeFromType(object_arr); 1283 StackMapType[] stack_map_type_arr = {tag_frame_type}; 1284 new_stack_map_table[0] = 1285 new StackMapEntry( 1286 Const.APPEND_FRAME, len_code, stack_map_type_arr, null, pool.getConstantPool()); 1287 1288 // We can just copy the rest of the stack frames over as the FULL_FRAME 1289 // ones were already updated when the tag_frame variable was allocated. 1290 for (int i = 0; i < stackMapTable.length; i++) { 1291 new_stack_map_table[i + 1] = stackMapTable[i]; 1292 } 1293 stackMapTable = new_stack_map_table; 1294 // print_stackMapTable ("add_create_tag_frame"); 1295 } 1296 1297 /** 1298 * Adds the call to DCRuntime.enter to the beginning of the method. 1299 * 1300 * @param mg method to modify 1301 * @param mi MethodInfo for method 1302 * @param method_info_index index for MethodInfo 1303 */ 1304 public void add_enter(MethodGen mg, MethodInfo mi, int method_info_index) { 1305 InstructionList il = mg.getInstructionList(); 1306 replaceInstructions( 1307 mg, il, insertion_placeholder, callEnterOrExit(mg, method_info_index, "enter", -1)); 1308 } 1309 1310 /** 1311 * Creates the local used to store the tag frame and returns it. 1312 * 1313 * @param mg method to modify 1314 * @return LocalVariableGen for the tag_frame local 1315 */ 1316 LocalVariableGen create_tag_frame_local(MethodGen mg) { 1317 return create_method_scope_local(mg, "dcomp_tag_frame$5a", object_arr); 1318 } 1319 1320 /** 1321 * Creates code to create the tag frame for this method and store it in tag_frame_local. 1322 * 1323 * @param mg method to modify 1324 * @param tag_frame_local LocalVariableGen for the tag_frame local 1325 * @return InstructionList for tag_frame setup code 1326 */ 1327 InstructionList create_tag_frame(MethodGen mg, LocalVariableGen tag_frame_local) { 1328 1329 Type arg_types[] = mg.getArgumentTypes(); 1330 1331 // Determine the offset of the first argument in the frame 1332 int offset = 1; 1333 if (mg.isStatic()) { 1334 offset = 0; 1335 } 1336 1337 // allocate an extra slot to save the tag frame depth for debugging 1338 int frame_size = mg.getMaxLocals() + 1; 1339 1340 // unsigned byte max = 255. minus the character '0' (decimal 48) 1341 // Largest frame size noted so far is 123. 1342 assert frame_size < 207 : frame_size + " " + mg.getClassName() + "." + mg.getName(); 1343 String params = "" + (char) (frame_size + '0'); 1344 // Character.forDigit (frame_size, Character.MAX_RADIX); 1345 List<Integer> plist = new ArrayList<>(); 1346 for (Type argType : arg_types) { 1347 if (argType instanceof BasicType) { 1348 plist.add(offset); 1349 } 1350 offset += argType.getSize(); 1351 } 1352 for (int ii = plist.size() - 1; ii >= 0; ii--) { 1353 char tmpChar = (char) (plist.get(ii) + '0'); 1354 params += tmpChar; 1355 // Character.forDigit (plist.get(ii), Character.MAX_RADIX); 1356 } 1357 1358 // Create code to create/init the tag frame and store in tag_frame_local 1359 InstructionList il = new InstructionList(); 1360 il.append(ifact.createConstant(params)); 1361 il.append( 1362 ifact.createInvoke( 1363 dcompRuntimeClassName, "create_tag_frame", object_arr, string_arg, Const.INVOKESTATIC)); 1364 il.append(InstructionFactory.createStore(object_arr, tag_frame_local.getIndex())); 1365 debugInstrument.log("Store Tag frame local at index %d%n", tag_frame_local.getIndex()); 1366 1367 return il; 1368 } 1369 1370 /** 1371 * Pushes the object, method info index, parameters, and return value on the stack and calls the 1372 * specified Method (normally enter or exit) in DCRuntime. The parameters are passed as an array 1373 * of objects. 1374 * 1375 * @param mg method to modify 1376 * @param method_info_index index for MethodInfo 1377 * @param method_name "enter" or "exit" 1378 * @param line source line number if type is exit 1379 * @return InstructionList for the enter or exit code 1380 */ 1381 InstructionList callEnterOrExit( 1382 MethodGen mg, int method_info_index, String method_name, int line) { 1383 1384 InstructionList il = new InstructionList(); 1385 Type[] arg_types = mg.getArgumentTypes(); 1386 1387 // Push the tag frame 1388 il.append(InstructionFactory.createLoad(object_arr, tag_frame_local.getIndex())); 1389 1390 // Push the object. Null if this is a static method or a constructor 1391 if (mg.isStatic() || (method_name.equals("enter") && BcelUtil.isConstructor(mg))) { 1392 il.append(new ACONST_NULL()); 1393 } else { // must be an instance method 1394 il.append(InstructionFactory.createLoad(Type.OBJECT, 0)); 1395 } 1396 1397 // Determine the offset of the first parameter 1398 int param_offset = 1; 1399 if (mg.isStatic()) { 1400 param_offset = 0; 1401 } 1402 1403 // Push the MethodInfo index 1404 il.append(ifact.createConstant(method_info_index)); 1405 1406 // Create an array of objects with elements for each parameter 1407 il.append(ifact.createConstant(arg_types.length)); 1408 il.append(ifact.createNewArray(Type.OBJECT, (short) 1)); 1409 1410 // Put each argument into the array 1411 int param_index = param_offset; 1412 for (int ii = 0; ii < arg_types.length; ii++) { 1413 il.append(InstructionFactory.createDup(object_arr.getSize())); 1414 il.append(ifact.createConstant(ii)); 1415 Type at = arg_types[ii]; 1416 if (at instanceof BasicType) { 1417 il.append(new ACONST_NULL()); 1418 // il.append (createPrimitiveWrapper (c, at, param_index)); 1419 } else { // must be reference of some sort 1420 il.append(InstructionFactory.createLoad(Type.OBJECT, param_index)); 1421 } 1422 il.append(InstructionFactory.createArrayStore(Type.OBJECT)); 1423 param_index += at.getSize(); 1424 } 1425 1426 // If this is an exit, push the return value and line number. 1427 // The return value 1428 // is stored in the local "return__$trace2_val" If the return 1429 // value is a primitive, wrap it in the appropriate run-time wrapper 1430 if (method_name.equals("exit")) { 1431 Type returnType = mg.getReturnType(); 1432 if (returnType == Type.VOID) { 1433 il.append(new ACONST_NULL()); 1434 } else { 1435 LocalVariableGen return_local = get_return_local(mg, returnType); 1436 if (returnType instanceof BasicType) { 1437 il.append(new ACONST_NULL()); 1438 // il.append (createPrimitiveWrapper (c, returnType, return_local.getIndex())); 1439 } else { 1440 il.append(InstructionFactory.createLoad(Type.OBJECT, return_local.getIndex())); 1441 } 1442 } 1443 1444 // push line number 1445 il.append(ifact.createConstant(line)); 1446 } 1447 1448 // Call the specified method 1449 Type[] method_args; 1450 if (method_name.equals("exit")) { 1451 method_args = 1452 new Type[] {object_arr, Type.OBJECT, Type.INT, object_arr, Type.OBJECT, Type.INT}; 1453 } else { 1454 method_args = new Type[] {object_arr, Type.OBJECT, Type.INT, object_arr}; 1455 } 1456 il.append( 1457 ifact.createInvoke( 1458 dcompRuntimeClassName, method_name, Type.VOID, method_args, Const.INVOKESTATIC)); 1459 1460 return il; 1461 } 1462 1463 /** 1464 * Transforms instructions to track comparability. Returns a list of instructions that replaces 1465 * the specified instruction. Returns null if the instruction should not be replaced. 1466 * 1467 * @param mg method being instrumented 1468 * @param ih handle of Instruction to translate 1469 * @param stack current contents of the stack 1470 */ 1471 @Nullable InstructionList xform_inst(MethodGen mg, InstructionHandle ih, OperandStack stack) { 1472 1473 Instruction inst = ih.getInstruction(); 1474 1475 switch (inst.getOpcode()) { 1476 1477 // Replace the object comparison instructions with a call to 1478 // DCRuntime.object_eq or DCRuntime.object_ne. Those methods 1479 // return a boolean which is used in a ifeq/ifne instruction 1480 case Const.IF_ACMPEQ: 1481 return object_comparison((BranchInstruction) inst, "object_eq", Const.IFNE); 1482 case Const.IF_ACMPNE: 1483 return object_comparison((BranchInstruction) inst, "object_ne", Const.IFNE); 1484 1485 // These instructions compare the integer on the top of the stack 1486 // to zero. Nothing is made comparable by this, so we need only 1487 // discard the tag on the top of the stack. 1488 case Const.IFEQ: 1489 case Const.IFNE: 1490 case Const.IFLT: 1491 case Const.IFGE: 1492 case Const.IFGT: 1493 case Const.IFLE: 1494 { 1495 return discard_tag_code(inst, 1); 1496 } 1497 1498 // Instanceof pushes either 0 or 1 on the stack depending on whether 1499 // the object on top of stack is of the specified type. We push a 1500 // tag for a constant, since nothing is made comparable by this. 1501 case Const.INSTANCEOF: 1502 return build_il(dcr_call("push_const", Type.VOID, Type.NO_ARGS), inst); 1503 1504 // Duplicates the item on the top of stack. If the value on the 1505 // top of the stack is a primitive, we need to do the same on the 1506 // tag stack. Otherwise, we need do nothing. 1507 case Const.DUP: 1508 { 1509 return dup_tag(inst, stack); 1510 } 1511 1512 // Duplicates the item on the top of the stack and inserts it 2 1513 // values down in the stack. If the value at the top of the stack 1514 // is not a primitive, there is nothing to do here. If the second 1515 // value is not a primitive, then we need only to insert the duped 1516 // value down 1 on the tag stack (which contains only primitives) 1517 case Const.DUP_X1: 1518 { 1519 return dup_x1_tag(inst, stack); 1520 } 1521 1522 // Duplicates either the top 2 category 1 values or a single 1523 // category 2 value and inserts it 2 or 3 values down on the 1524 // stack. 1525 case Const.DUP2_X1: 1526 { 1527 return dup2_x1_tag(inst, stack); 1528 } 1529 1530 // Duplicate either one category 2 value or two category 1 values. 1531 case Const.DUP2: 1532 { 1533 return dup2_tag(inst, stack); 1534 } 1535 1536 // Dup the category 1 value on the top of the stack and insert it either 1537 // two or three values down on the stack. 1538 case Const.DUP_X2: 1539 { 1540 return dup_x2(inst, stack); 1541 } 1542 1543 case Const.DUP2_X2: 1544 { 1545 return dup2_x2(inst, stack); 1546 } 1547 1548 // Pop instructions discard the top of the stack. We want to discard 1549 // the top of the tag stack iff the item on the top of the stack is a 1550 // primitive. 1551 case Const.POP: 1552 { 1553 return pop_tag(inst, stack); 1554 } 1555 1556 // Pops either the top 2 category 1 values or a single category 2 value 1557 // from the top of the stack. We must do the same to the tag stack 1558 // if the values are primitives. 1559 case Const.POP2: 1560 { 1561 return pop2_tag(inst, stack); 1562 } 1563 1564 // Swaps the two category 1 types on the top of the stack. We need 1565 // to swap the top of the tag stack if the two top elements on the 1566 // real stack are primitives. 1567 case Const.SWAP: 1568 { 1569 return swap_tag(inst, stack); 1570 } 1571 1572 case Const.IF_ICMPEQ: 1573 case Const.IF_ICMPGE: 1574 case Const.IF_ICMPGT: 1575 case Const.IF_ICMPLE: 1576 case Const.IF_ICMPLT: 1577 case Const.IF_ICMPNE: 1578 { 1579 return build_il(dcr_call("cmp_op", Type.VOID, Type.NO_ARGS), inst); 1580 } 1581 1582 case Const.GETFIELD: 1583 { 1584 return load_store_field(mg, (GETFIELD) inst); 1585 } 1586 1587 case Const.PUTFIELD: 1588 { 1589 return load_store_field(mg, (PUTFIELD) inst); 1590 } 1591 1592 case Const.GETSTATIC: 1593 { 1594 return load_store_field(mg, ((GETSTATIC) inst)); 1595 } 1596 1597 case Const.PUTSTATIC: 1598 { 1599 return load_store_field(mg, ((PUTSTATIC) inst)); 1600 } 1601 1602 case Const.DLOAD: 1603 case Const.DLOAD_0: 1604 case Const.DLOAD_1: 1605 case Const.DLOAD_2: 1606 case Const.DLOAD_3: 1607 case Const.FLOAD: 1608 case Const.FLOAD_0: 1609 case Const.FLOAD_1: 1610 case Const.FLOAD_2: 1611 case Const.FLOAD_3: 1612 case Const.ILOAD: 1613 case Const.ILOAD_0: 1614 case Const.ILOAD_1: 1615 case Const.ILOAD_2: 1616 case Const.ILOAD_3: 1617 case Const.LLOAD: 1618 case Const.LLOAD_0: 1619 case Const.LLOAD_1: 1620 case Const.LLOAD_2: 1621 case Const.LLOAD_3: 1622 { 1623 return load_store_local((LoadInstruction) inst, tag_frame_local, "push_local_tag"); 1624 } 1625 1626 case Const.DSTORE: 1627 case Const.DSTORE_0: 1628 case Const.DSTORE_1: 1629 case Const.DSTORE_2: 1630 case Const.DSTORE_3: 1631 case Const.FSTORE: 1632 case Const.FSTORE_0: 1633 case Const.FSTORE_1: 1634 case Const.FSTORE_2: 1635 case Const.FSTORE_3: 1636 case Const.ISTORE: 1637 case Const.ISTORE_0: 1638 case Const.ISTORE_1: 1639 case Const.ISTORE_2: 1640 case Const.ISTORE_3: 1641 case Const.LSTORE: 1642 case Const.LSTORE_0: 1643 case Const.LSTORE_1: 1644 case Const.LSTORE_2: 1645 case Const.LSTORE_3: 1646 { 1647 return load_store_local((StoreInstruction) inst, tag_frame_local, "pop_local_tag"); 1648 } 1649 1650 case Const.LDC: 1651 case Const.LDC_W: 1652 case Const.LDC2_W: 1653 { 1654 return ldc_tag(inst, stack); 1655 } 1656 1657 // Push the tag for the array onto the tag stack. This causes 1658 // anything comparable to the length to be comparable to the array 1659 // as an index. 1660 case Const.ARRAYLENGTH: 1661 { 1662 return array_length(inst); 1663 } 1664 1665 case Const.BIPUSH: 1666 case Const.SIPUSH: 1667 case Const.DCONST_0: 1668 case Const.DCONST_1: 1669 case Const.FCONST_0: 1670 case Const.FCONST_1: 1671 case Const.FCONST_2: 1672 case Const.ICONST_0: 1673 case Const.ICONST_1: 1674 case Const.ICONST_2: 1675 case Const.ICONST_3: 1676 case Const.ICONST_4: 1677 case Const.ICONST_5: 1678 case Const.ICONST_M1: 1679 case Const.LCONST_0: 1680 case Const.LCONST_1: 1681 { 1682 return build_il(dcr_call("push_const", Type.VOID, Type.NO_ARGS), inst); 1683 } 1684 1685 // Primitive Binary operators. Each is augmented with a call to 1686 // DCRuntime.binary_tag_op that merges the tags and updates the tag 1687 // Stack. 1688 case Const.DADD: 1689 case Const.DCMPG: 1690 case Const.DCMPL: 1691 case Const.DDIV: 1692 case Const.DMUL: 1693 case Const.DREM: 1694 case Const.DSUB: 1695 case Const.FADD: 1696 case Const.FCMPG: 1697 case Const.FCMPL: 1698 case Const.FDIV: 1699 case Const.FMUL: 1700 case Const.FREM: 1701 case Const.FSUB: 1702 case Const.IADD: 1703 case Const.IAND: 1704 case Const.IDIV: 1705 case Const.IMUL: 1706 case Const.IOR: 1707 case Const.IREM: 1708 case Const.ISHL: 1709 case Const.ISHR: 1710 case Const.ISUB: 1711 case Const.IUSHR: 1712 case Const.IXOR: 1713 case Const.LADD: 1714 case Const.LAND: 1715 case Const.LCMP: 1716 case Const.LDIV: 1717 case Const.LMUL: 1718 case Const.LOR: 1719 case Const.LREM: 1720 case Const.LSHL: 1721 case Const.LSHR: 1722 case Const.LSUB: 1723 case Const.LUSHR: 1724 case Const.LXOR: 1725 return build_il(dcr_call("binary_tag_op", Type.VOID, Type.NO_ARGS), inst); 1726 1727 // Computed jump based on the int on the top of stack. Since that int 1728 // is not made comparable to anything, we just discard its tag. One 1729 // might argue that the key should be made comparable to each value in 1730 // the jump table. But the tags for those values are not available. 1731 // And since they are all constants, its not clear how interesting it 1732 // would be anyway. 1733 case Const.LOOKUPSWITCH: 1734 case Const.TABLESWITCH: 1735 return discard_tag_code(inst, 1); 1736 1737 // Make the integer argument to ANEWARRAY comparable to the new 1738 // array's index. 1739 case Const.ANEWARRAY: 1740 case Const.NEWARRAY: 1741 { 1742 return new_array(inst); 1743 } 1744 1745 // If the new array has 2 dimensions, make the integer arguments 1746 // comparable to the corresponding indices of the new array. 1747 // For any other number of dimensions, discard the tags for the 1748 // arguments. 1749 case Const.MULTIANEWARRAY: 1750 { 1751 return multi_newarray_dc(inst); 1752 } 1753 1754 // Mark the array and its index as comparable. Also for primitives, 1755 // push the tag of the array element on the tag stack 1756 case Const.AALOAD: 1757 case Const.BALOAD: 1758 case Const.CALOAD: 1759 case Const.DALOAD: 1760 case Const.FALOAD: 1761 case Const.IALOAD: 1762 case Const.LALOAD: 1763 case Const.SALOAD: 1764 { 1765 return array_load(inst); 1766 } 1767 1768 // Mark the array and its index as comparable. For primitives, store 1769 // the tag for the value on the top of the stack in the tag storage 1770 // for the array. 1771 case Const.AASTORE: 1772 return array_store(inst, "aastore", Type.OBJECT); 1773 case Const.BASTORE: 1774 // The JVM uses bastore for both byte and boolean. 1775 // We need to differentiate. 1776 Type arr_type = stack.peek(2); 1777 if (arr_type.getSignature().equals("[Z")) { 1778 return array_store(inst, "zastore", Type.BOOLEAN); 1779 } else { 1780 return array_store(inst, "bastore", Type.BYTE); 1781 } 1782 case Const.CASTORE: 1783 return array_store(inst, "castore", Type.CHAR); 1784 case Const.DASTORE: 1785 return array_store(inst, "dastore", Type.DOUBLE); 1786 case Const.FASTORE: 1787 return array_store(inst, "fastore", Type.FLOAT); 1788 case Const.IASTORE: 1789 return array_store(inst, "iastore", Type.INT); 1790 case Const.LASTORE: 1791 return array_store(inst, "lastore", Type.LONG); 1792 case Const.SASTORE: 1793 return array_store(inst, "sastore", Type.SHORT); 1794 1795 // Prefix the return with a call to the correct normal_exit method 1796 // to handle the tag stack 1797 case Const.ARETURN: 1798 case Const.DRETURN: 1799 case Const.FRETURN: 1800 case Const.IRETURN: 1801 case Const.LRETURN: 1802 case Const.RETURN: 1803 { 1804 return return_tag(mg, inst); 1805 } 1806 1807 // Handle subroutine calls. Calls to instrumented code are modified 1808 // to call the instrumented version (with the DCompMarker argument). 1809 // Calls to uninstrumented code (rare) discard primitive arguments 1810 // from the tag stack and produce an arbitrary return tag. 1811 case Const.INVOKESTATIC: 1812 case Const.INVOKEVIRTUAL: 1813 case Const.INVOKESPECIAL: 1814 case Const.INVOKEINTERFACE: 1815 case Const.INVOKEDYNAMIC: 1816 return handleInvoke((InvokeInstruction) inst); 1817 1818 // Throws an exception. This clears the operand stack of the current 1819 // frame. We need to clear the tag stack as well. 1820 case Const.ATHROW: 1821 return build_il(dcr_call("throw_op", Type.VOID, Type.NO_ARGS), inst); 1822 1823 // Opcodes that don't need any modifications. Here for reference 1824 case Const.ACONST_NULL: 1825 case Const.ALOAD: 1826 case Const.ALOAD_0: 1827 case Const.ALOAD_1: 1828 case Const.ALOAD_2: 1829 case Const.ALOAD_3: 1830 case Const.ASTORE: 1831 case Const.ASTORE_0: 1832 case Const.ASTORE_1: 1833 case Const.ASTORE_2: 1834 case Const.ASTORE_3: 1835 case Const.CHECKCAST: 1836 case Const.D2F: // double to float 1837 case Const.D2I: // double to integer 1838 case Const.D2L: // double to long 1839 case Const.DNEG: // Negate double on top of stack 1840 case Const.F2D: // float to double 1841 case Const.F2I: // float to integer 1842 case Const.F2L: // float to long 1843 case Const.FNEG: // Negate float on top of stack 1844 case Const.GOTO: 1845 case Const.GOTO_W: 1846 case Const.I2B: // integer to byte 1847 case Const.I2C: // integer to char 1848 case Const.I2D: // integer to double 1849 case Const.I2F: // integer to float 1850 case Const.I2L: // integer to long 1851 case Const.I2S: // integer to short 1852 case Const.IFNONNULL: 1853 case Const.IFNULL: 1854 case Const.IINC: // increment local variable by a constant 1855 case Const.INEG: // negate integer on top of stack 1856 case Const.JSR: // pushes return address on the stack, but that 1857 // is thought of as an object, so we don't need 1858 // a tag for it. 1859 case Const.JSR_W: 1860 case Const.L2D: // long to double 1861 case Const.L2F: // long to float 1862 case Const.L2I: // long to int 1863 case Const.LNEG: // negate long on top of stack 1864 case Const.MONITORENTER: 1865 case Const.MONITOREXIT: 1866 case Const.NEW: 1867 case Const.NOP: 1868 case Const.RET: // this is the internal JSR return 1869 return null; 1870 1871 // Make sure we didn't miss anything 1872 default: 1873 throw new Error("instruction " + inst + " unsupported"); 1874 } 1875 } 1876 1877 /** 1878 * Adds a call to DCruntime.exit() at each return from the method. This call calculates 1879 * comparability on the daikon variables. It is only necessary if we are tracking comparability 1880 * for the variables of this method. 1881 * 1882 * @param mg method to modify 1883 * @param mi MethodInfo for method 1884 * @param method_info_index index for MethodInfo 1885 */ 1886 void add_exit(MethodGen mg, MethodInfo mi, int method_info_index) { 1887 1888 // Iterator over all of the exit line numbers for this method, in order. 1889 // We will read one element from it each time that we encounter a 1890 // return instruction. 1891 Iterator<Integer> exit_iter = mi.exit_locations.iterator(); 1892 1893 // Loop through each instruction, looking for return instructions. 1894 InstructionList il = mg.getInstructionList(); 1895 for (InstructionHandle ih = il.getStart(); ih != null; ) { 1896 1897 // Remember the next instruction to process 1898 InstructionHandle next_ih = ih.getNext(); 1899 1900 // If this is a return instruction, Call DCRuntime.exit to calculate 1901 // comparability on Daikon variables 1902 Instruction inst = ih.getInstruction(); 1903 if (inst instanceof ReturnInstruction) { 1904 Type type = mg.getReturnType(); 1905 InstructionList new_il = new InstructionList(); 1906 if (type != Type.VOID) { 1907 LocalVariableGen return_loc = get_return_local(mg, type); 1908 new_il.append(InstructionFactory.createDup(type.getSize())); 1909 new_il.append(InstructionFactory.createStore(type, return_loc.getIndex())); 1910 } 1911 new_il.append(callEnterOrExit(mg, method_info_index, "exit", exit_iter.next())); 1912 new_il.append(inst); 1913 replaceInstructions(mg, il, ih, new_il); 1914 } 1915 1916 ih = next_ih; 1917 } 1918 } 1919 1920 /** 1921 * Return the interface class containing the implementation of the given method. The interfaces of 1922 * {@code startClass} are recursively searched. 1923 * 1924 * @param startClass the JavaClass whose interfaces are to be searched 1925 * @param methodName the target method to search for 1926 * @param argTypes the target method's argument types 1927 * @return the name of the interface class containing target method, or null if not found 1928 */ 1929 private @Nullable @ClassGetName String getDefiningInterface( 1930 JavaClass startClass, String methodName, Type[] argTypes) { 1931 1932 if (debugGetDefiningInterface) { 1933 System.out.println("searching interfaces of: " + startClass.getClassName()); 1934 } 1935 for (@ClassGetName String interfaceName : startClass.getInterfaceNames()) { 1936 if (debugGetDefiningInterface) { 1937 System.out.println("interface: " + interfaceName); 1938 } 1939 JavaClass ji; 1940 try { 1941 ji = getJavaClass(interfaceName); 1942 } catch (Throwable e) { 1943 throw new Error(String.format("Unable to load class: %s", interfaceName), e); 1944 } 1945 for (Method jm : ji.getMethods()) { 1946 if (debugGetDefiningInterface) { 1947 System.out.println(" " + jm.getName() + Arrays.toString(jm.getArgumentTypes())); 1948 } 1949 if (jm.getName().equals(methodName) && Arrays.equals(jm.getArgumentTypes(), argTypes)) { 1950 // We have a match. 1951 return interfaceName; 1952 } 1953 } 1954 // no match found; does this interface extend other interfaces? 1955 @ClassGetName String foundAbove = getDefiningInterface(ji, methodName, argTypes); 1956 if (foundAbove != null) { 1957 // We have a match. 1958 return foundAbove; 1959 } 1960 } 1961 // nothing found 1962 return null; 1963 } 1964 1965 /** 1966 * Process an Invoke instruction. There are three cases: 1967 * 1968 * <ul> 1969 * <li>convert calls to Object.equals to calls to dcomp_equals or dcomp_super_equals 1970 * <li>convert calls to Object.clone to calls to dcomp_clone or dcomp_super_clone 1971 * <li>otherwise, determine whether the target of the invoke is instrumented or not (this is the 1972 * {@code callee_instrumented} variable) 1973 * <ul> 1974 * <li>If the target method is instrumented, add a DCompMarker argument to the end of the 1975 * argument list. 1976 * <li>If the target method is not instrumented, we must account for the fact that the 1977 * instrumentation code generated up to this point has assumed that the target method 1978 * is instrumented. Hence, generate code to discard a primitive tag from the 1979 * DCRuntime's per-thread comparability data stack for each primitive argument. If the 1980 * return type of the target method is a primitive, add code to push a tag onto the 1981 * runtime comparability data stack to represent the primitive return value. 1982 * </ul> 1983 * </ul> 1984 * 1985 * @param invoke a method invocation bytecode instruction 1986 * @return instructions to replace the given instruction 1987 */ 1988 private InstructionList handleInvoke(InvokeInstruction invoke) { 1989 1990 // Get information about the call 1991 String methodName = invoke.getMethodName(pool); 1992 // getClassName does not work properly if invoke is INVOKEDYNAMIC. 1993 // We will deal with this later. 1994 @ClassGetName String classname = invoke.getClassName(pool); 1995 Type returnType = invoke.getReturnType(pool); 1996 Type[] argTypes = invoke.getArgumentTypes(pool); 1997 1998 if (is_object_equals(methodName, returnType, argTypes)) { 1999 2000 // Replace calls to Object's equals method with calls to our 2001 // replacement, a static method in DCRuntime. 2002 2003 Type[] new_arg_types = new Type[] {javalangObject, javalangObject}; 2004 2005 InstructionList il = new InstructionList(); 2006 il.append( 2007 ifact.createInvoke( 2008 dcompRuntimeClassName, 2009 (invoke.getOpcode() == Const.INVOKESPECIAL) ? "dcomp_super_equals" : "dcomp_equals", 2010 returnType, 2011 new_arg_types, 2012 Const.INVOKESTATIC)); 2013 return il; 2014 } 2015 2016 if (is_object_clone(methodName, returnType, argTypes)) { 2017 2018 // Replace calls to Object's clone method with calls to our 2019 // replacement, a static method in DCRuntime. 2020 2021 InstructionList il = instrument_clone_call(invoke); 2022 return il; 2023 } 2024 2025 boolean callee_instrumented = isTargetInstrumented(invoke, classname, methodName, argTypes); 2026 2027 if (debugHandleInvoke) { 2028 System.out.printf("handleInvoke(%s)%n", invoke); 2029 System.out.printf(" invoke host: %s%n", gen.getClassName() + "." + mgen.getName()); 2030 System.out.printf(" invoke targ: %s%n", classname + "." + methodName); 2031 System.out.printf(" callee_instrumented: %s%n", callee_instrumented); 2032 } 2033 2034 if (callee_instrumented) { 2035 2036 InstructionList il = new InstructionList(); 2037 // Add the DCompMarker argument so that it calls the instrumented version. 2038 il.append(new ACONST_NULL()); 2039 Type[] new_arg_types = BcelUtil.postpendToArray(argTypes, dcomp_marker); 2040 Constant methodref = pool.getConstant(invoke.getIndex()); 2041 il.append( 2042 ifact.createInvoke( 2043 classname, 2044 methodName, 2045 returnType, 2046 new_arg_types, 2047 invoke.getOpcode(), 2048 methodref instanceof ConstantInterfaceMethodref)); 2049 return il; 2050 2051 } else { // not instrumented, discard the tags before making the call 2052 2053 InstructionList il = new InstructionList(); 2054 // JUnit test classes are a bit strange. They are marked as not being callee_instrumented 2055 // because they do not have the dcomp_marker added to the argument list, but 2056 // they actually contain instrumentation code. So we do not want to discard 2057 // the primitive tags prior to the call. 2058 if (!junitTestClasses.contains(classname)) { 2059 il.append(discard_primitive_tags(argTypes)); 2060 } 2061 2062 // Add a tag for the return type if it is primitive. 2063 if ((returnType instanceof BasicType) && (returnType != Type.VOID)) { 2064 if (debugHandleInvoke) { 2065 System.out.printf("push tag for return type of %s%n", invoke.getReturnType(pool)); 2066 } 2067 il.append(dcr_call("push_const", Type.VOID, Type.NO_ARGS)); 2068 } 2069 il.append(invoke); 2070 return il; 2071 } 2072 } 2073 2074 /** 2075 * Returns instructions that will discard any primitive tags corresponding to the specified 2076 * arguments. Returns an empty instruction list if there are no primitive arguments to discard. 2077 * 2078 * @param argTypes argument types of target method 2079 * @return an instruction list that discards primitive tags from DCRuntime's per-thread 2080 * comparability data stack 2081 */ 2082 private InstructionList discard_primitive_tags(Type[] argTypes) { 2083 2084 InstructionList il = new InstructionList(); 2085 int primitive_cnt = 0; 2086 for (Type argType : argTypes) { 2087 if (argType instanceof BasicType) { 2088 primitive_cnt++; 2089 } 2090 } 2091 if (primitive_cnt > 0) { 2092 il.append(discard_tag_code(new NOP(), primitive_cnt)); 2093 } 2094 return il; 2095 } 2096 2097 /** 2098 * Returns true if the invoke target is instrumented. 2099 * 2100 * @param invoke instruction whose target is to be checked 2101 * @param classname target class of the invoke 2102 * @param methodName target method of the invoke 2103 * @param argTypes argument types of target method 2104 * @return true if the target is instrumented 2105 */ 2106 private boolean isTargetInstrumented( 2107 InvokeInstruction invoke, 2108 @ClassGetName String classname, 2109 String methodName, 2110 Type[] argTypes) { 2111 boolean targetInstrumented; 2112 2113 if (invoke instanceof INVOKEDYNAMIC) { 2114 // We don't instrument lambda methods. 2115 // BUG: BCEL doesn't know how to get classname from an INVOKEDYNAMIC instruction. 2116 if (debugHandleInvoke) { 2117 System.out.printf("invokedynamic NOT the classname: %s%n", classname); 2118 } 2119 targetInstrumented = false; 2120 } else if (is_object_method(methodName, invoke.getArgumentTypes(pool))) { 2121 targetInstrumented = false; 2122 } else { 2123 targetInstrumented = isClassnameInstrumented(classname, methodName); 2124 2125 if (debugHandleInvoke) { 2126 System.out.printf("invoke host: %s%n", gen.getClassName() + "." + mgen.getName()); 2127 System.out.printf("invoke targ: %s%n", classname + "." + methodName); 2128 } 2129 2130 if (Premain.problem_methods.contains(classname + "." + methodName)) { 2131 debugInstrument.log( 2132 "Don't call instrumented version of problem method %s.%n", 2133 classname + "." + methodName); 2134 targetInstrumented = false; 2135 } 2136 2137 // targetInstrumented (the return value of this method) has been set. 2138 // Now, adjust it for some special cases. 2139 // Every adjustment is from `true` to `false`. 2140 2141 // There are two special cases we need to detect: 2142 // calls to annotations 2143 // calls to functional interfaces 2144 // 2145 // Annotation classes are never instrumented so we must set 2146 // the targetInstrumented flag to false. 2147 // 2148 // Functional interfaces are a bit more complicated. These are primarily (only?) 2149 // used by Lambda functions. Lambda methods are generated dynamically at 2150 // run time via the InvokeDynamic instruction. They are not seen by our 2151 // ClassFileTransformer so are never instrumented. Thus we must set the 2152 // targetInstrumented flag to false when we see a call to a Lambda method. 2153 // The heuristic we use is to assume that any InvokeInterface or InvokeVirtual 2154 // call to a functional interface is a call to a Lambda method. 2155 // 2156 // The Java compiler detects functional interfaces automatically, but the 2157 // user can declare their intent with the @FunctionInterface annotation. 2158 // The Java runtime is annotated in this manner. Hence, we look for this 2159 // annotation to detect a call to a functional interface. In practice, we 2160 // could detect functional interfaces in a manner similar to the Java 2161 // compiler, but for now we will go with this simpler method. 2162 // 2163 // Note that to simplify our code we set the access flags for a functional 2164 // interface to ANNOTATION in our accessFlags map. 2165 // 2166 if (targetInstrumented == true 2167 && (invoke instanceof INVOKEINTERFACE || invoke instanceof INVOKEVIRTUAL)) { 2168 Integer access = getAccessFlags(classname); 2169 2170 if ((access.intValue() & Const.ACC_ANNOTATION) != 0) { 2171 targetInstrumented = false; 2172 } 2173 2174 // UNDONE: New code added above should handle the case below. Need to find a test 2175 // case and verify this code is no longer needed. 2176 // This is a bit of a hack. An invokeinterface instruction with a 2177 // a target of "java.util.stream.<something>" might be calling a 2178 // Lambda method in which case we don't want to add the dcomp_marker. 2179 // Might lose something in 'normal' cases, but no easy way to detect. 2180 if (classname.startsWith("java.util.stream")) { 2181 targetInstrumented = false; 2182 } 2183 2184 // In a similar fashion, when the Java runtime is processing annotations, there might 2185 // be an invoke (via reflection) of a member of the java.lang.annotation package; this 2186 // too should not have the dcomp_marker added. 2187 if (classname.startsWith("java.lang.annotation")) { 2188 targetInstrumented = false; 2189 } 2190 } 2191 2192 // If we are not using the instrumented JDK, then we need to track down the 2193 // actual target of an INVOKEVIRTUAL to see if it has been instrumented or not. 2194 if (targetInstrumented == true && invoke instanceof INVOKEVIRTUAL) { 2195 if (!Premain.jdk_instrumented && !mgen.getName().equals("equals_dcomp_instrumented")) { 2196 2197 if (debugHandleInvoke) { 2198 System.out.println("method: " + methodName); 2199 System.out.println("argTypes: " + Arrays.toString(argTypes)); 2200 System.out.printf("invoke host: %s%n", gen.getClassName() + "." + mgen.getName()); 2201 } 2202 2203 @ClassGetName String targetClassname = classname; 2204 // Search this class for the target method. If not found, set targetClassname to 2205 // its superclass and try again. 2206 mainloop: 2207 while (true) { 2208 // Check that the class exists 2209 JavaClass targetClass; 2210 try { 2211 targetClass = getJavaClass(targetClassname); 2212 } catch (Throwable e) { 2213 targetClass = null; 2214 } 2215 if (targetClass == null) { 2216 // We cannot locate or read the .class file, better assume not instrumented. 2217 if (debugHandleInvoke) { 2218 System.out.printf("Unable to locate class: %s%n%n", targetClassname); 2219 } 2220 targetInstrumented = false; 2221 break; 2222 } 2223 if (debugHandleInvoke) { 2224 System.out.println("target class: " + targetClassname); 2225 } 2226 2227 for (Method m : targetClass.getMethods()) { 2228 if (debugHandleInvoke) { 2229 System.out.println(" " + m.getName() + Arrays.toString(m.getArgumentTypes())); 2230 } 2231 if (m.getName().equals(methodName) && Arrays.equals(m.getArgumentTypes(), argTypes)) { 2232 // We have a match. 2233 if (debugHandleInvoke) { 2234 System.out.printf("we have a match%n%n"); 2235 } 2236 if (BcelUtil.inJdk(targetClassname)) { 2237 targetInstrumented = false; 2238 } 2239 break mainloop; 2240 } 2241 } 2242 2243 { 2244 // no methods match - search this class's interfaces 2245 @ClassGetName String found; 2246 try { 2247 found = getDefiningInterface(targetClass, methodName, argTypes); 2248 } catch (Throwable e) { 2249 // We cannot locate or read the .class file, better assume it is not instrumented. 2250 targetInstrumented = false; 2251 break; 2252 } 2253 if (found != null) { 2254 // We have a match. 2255 if (debugHandleInvoke) { 2256 System.out.printf("we have a match%n%n"); 2257 } 2258 if (BcelUtil.inJdk(found)) { 2259 targetInstrumented = false; 2260 } 2261 break; 2262 } 2263 } 2264 2265 // Method not found; perhaps inherited from superclass. 2266 // Cannot use "targetClass = targetClass.getSuperClass()" because the superclass might 2267 // not have been loaded into BCEL yet. 2268 if (targetClass.getSuperclassNameIndex() == 0) { 2269 // The target class is Object; the search completed without finding a matching method. 2270 if (debugHandleInvoke) { 2271 System.out.printf("Unable to locate method: %s%n%n", methodName); 2272 } 2273 targetInstrumented = false; 2274 break; 2275 } 2276 // Recurse looking in the superclass. 2277 targetClassname = targetClass.getSuperclassName(); 2278 } 2279 } 2280 } 2281 } 2282 2283 if (invoke instanceof INVOKESPECIAL) { 2284 if (classname.equals(gen.getSuperclassName()) && methodName.equals("<init>")) { 2285 this.constructor_is_initialized = true; 2286 } 2287 } 2288 2289 return targetInstrumented; 2290 } 2291 2292 /** 2293 * Returns the access flags for the given class. 2294 * 2295 * @param classname the class whose access flags to return 2296 * @return the access flags for the given class 2297 */ 2298 private Integer getAccessFlags(String classname) { 2299 Integer access = accessFlags.get(classname); 2300 if (access == null) { 2301 // We have not seen this class before. Check to see if the target class is 2302 // an Annotation or a FunctionalInterface. 2303 JavaClass c = getJavaClass(classname); 2304 if (c != null) { 2305 access = c.getAccessFlags(); 2306 2307 // Now check for FunctionalInterface 2308 for (final AnnotationEntry item : c.getAnnotationEntries()) { 2309 if (item.getAnnotationType().endsWith("FunctionalInterface;")) { 2310 access = Integer_ACC_ANNOTATION; 2311 if (debugHandleInvoke) { 2312 System.out.println(item.getAnnotationType()); 2313 } 2314 break; 2315 } 2316 } 2317 } else { 2318 // We cannot locate or read the .class file, better pretend it is an Annotation. 2319 if (debugHandleInvoke) { 2320 System.out.printf("Unable to locate class: %s%n", classname); 2321 } 2322 access = Integer_ACC_ANNOTATION; 2323 } 2324 accessFlags.put(classname, access); 2325 } 2326 return access; 2327 } 2328 2329 /** 2330 * Returns true if the specified classname is instrumented. 2331 * 2332 * @param classname class to be checked 2333 * @param methodName method to be checked (currently unused) 2334 * @return true if classname is instrumented 2335 */ 2336 private boolean isClassnameInstrumented(@ClassGetName String classname, String methodName) { 2337 2338 if (debugHandleInvoke) { 2339 System.out.printf("Checking callee instrumented on %s%n", classname); 2340 } 2341 2342 // Our copy of daikon.plumelib is not instrumented. It would be odd, though, 2343 // to see calls to this. 2344 if (classname.startsWith("daikon.plumelib")) { 2345 return false; 2346 } 2347 2348 // Special-case JUnit test classes. 2349 if (junitTestClasses.contains(classname)) { 2350 return false; 2351 } 2352 2353 if (daikon.dcomp.Instrument.is_transformer(classname.replace('.', '/'))) { 2354 return false; 2355 } 2356 2357 // Special case the execution trace tool. 2358 if (classname.startsWith("minst.Minst")) { 2359 return false; 2360 } 2361 2362 // We should probably change the interface to include method name 2363 // and use "classname.methodname" as arg to pattern matcher. 2364 // If any of the omit patterns match, use the uninstrumented version of the method 2365 for (Pattern p : DynComp.ppt_omit_pattern) { 2366 if (p.matcher(classname).find()) { 2367 if (debugHandleInvoke) { 2368 System.out.printf("callee instrumented = false: %s.%s%n", classname, methodName); 2369 } 2370 return false; 2371 } 2372 } 2373 2374 // If its not a JDK class, presume its instrumented. 2375 if (!BcelUtil.inJdk(classname)) { 2376 return true; 2377 } 2378 2379 int i = classname.lastIndexOf('.'); 2380 if (i > 0) { 2381 if (Premain.problem_packages.contains(classname.substring(0, i))) { 2382 debugInstrument.log( 2383 "Don't call instrumented member of problem package %s%n", classname.substring(0, i)); 2384 return false; 2385 } 2386 } 2387 2388 if (Premain.problem_classes.contains(classname)) { 2389 debugInstrument.log("Don't call instrumented member of problem class %s%n", classname); 2390 return false; 2391 } 2392 2393 // We have decided not to use the instrumented version of Random as 2394 // the method generates values based on an initial seed value. 2395 // (Typical of random() algorithms.) This has the undesirable side 2396 // effect of putting all the generated values in the same comparison 2397 // set when they should be distinct. 2398 // NOTE: If we find other classes that should not use the instrumented 2399 // versions, we should consider making this a searchable list. 2400 if (classname.equals("java.util.Random")) { 2401 return false; 2402 } 2403 2404 // If using the instrumented JDK, then everthing but object is instrumented 2405 if (Premain.jdk_instrumented && !classname.equals("java.lang.Object")) { 2406 return true; 2407 } 2408 2409 return false; 2410 } 2411 2412 /** 2413 * Given a classname return it's superclass name. Note that BCEL reports that the superclass of 2414 * 'java.lang.Object' is 'java.lang.Object' rather than saying there is no superclass. 2415 * 2416 * @param classname the fully qualified name of the class in binary form. E.g., "java.util.List" 2417 * @return superclass name of classname or null if there is an error 2418 */ 2419 private @ClassGetName String getSuperclassName(String classname) { 2420 JavaClass jc = getJavaClass(classname); 2421 if (jc != null) { 2422 return jc.getSuperclassName(); 2423 } else { 2424 return null; 2425 } 2426 } 2427 2428 /** Cache for {@link #getJavaClass} method. */ 2429 private static Map<String, JavaClass> javaClasses = new ConcurrentHashMap<String, JavaClass>(); 2430 2431 /** 2432 * There are times when it is useful to inspect a class file other than the one we are currently 2433 * instrumenting. Note we cannot use classForName to do this as it might trigger a recursive call 2434 * to Instrument which would not work at this point. 2435 * 2436 * <p>Given a class name, we treat it as a system resource and try to open it as an input stream 2437 * that we can pass to BCEL to read and convert to a JavaClass object. 2438 * 2439 * @param classname the fully qualified name of the class in binary form, e.g., "java.util.List" 2440 * @return the JavaClass of the corresponding classname or null 2441 */ 2442 private @Nullable JavaClass getJavaClass(String classname) { 2443 JavaClass cached = javaClasses.get(classname); 2444 if (cached != null) { 2445 return cached; 2446 } 2447 2448 URL class_url = ClassLoader.getSystemResource(classname.replace('.', '/') + ".class"); 2449 if (class_url != null) { 2450 try (InputStream inputStream = class_url.openStream()) { 2451 if (inputStream != null) { 2452 // Parse the bytes of the classfile, die on any errors 2453 ClassParser parser = new ClassParser(inputStream, classname + "<internal>"); 2454 JavaClass result = parser.parse(); 2455 javaClasses.put(classname, result); 2456 return result; 2457 } 2458 } catch (Throwable t) { 2459 throw new Error("Unexpected error reading " + class_url, t); 2460 } 2461 } 2462 // Do not cache a null result, because a subsequent invocation might return non-null. 2463 return null; 2464 } 2465 2466 /** 2467 * Returns whether or not the method is Object.equals(). 2468 * 2469 * @param methodName method to check 2470 * @param returnType return type of method 2471 * @param args array of argument types to method 2472 * @return true if method is Object.equals() 2473 */ 2474 @Pure 2475 boolean is_object_equals(String methodName, Type returnType, Type[] args) { 2476 return (methodName.equals("equals") 2477 && returnType == Type.BOOLEAN 2478 && args.length == 1 2479 && args[0].equals(javalangObject)); 2480 } 2481 2482 /** 2483 * Returns true if the specified method is Object.clone(). 2484 * 2485 * @param methodName method to check 2486 * @param returnType return type of method 2487 * @param args array of argument types to method 2488 * @return true if method is Object.clone() 2489 */ 2490 @Pure 2491 boolean is_object_clone(String methodName, Type returnType, Type[] args) { 2492 return methodName.equals("clone") && returnType.equals(javalangObject) && (args.length == 0); 2493 } 2494 2495 /** 2496 * Instrument calls to the Object method clone. An instrumented version is called if it exists, 2497 * the non-instrumented version if it does not. 2498 * 2499 * @param invoke invoke instruction to inspect and replace 2500 * @return InstructionList to call the correct version of clone or toString 2501 */ 2502 InstructionList instrument_clone_call(InvokeInstruction invoke) { 2503 2504 InstructionList il = new InstructionList(); 2505 Type returnType = invoke.getReturnType(pool); 2506 String classname = invoke.getClassName(pool); 2507 ReferenceType ref_type = invoke.getReferenceType(pool); 2508 if (ref_type instanceof ArrayType) { 2509 // <array>.clone() is never instrumented, return original invoke. 2510 il.append(invoke); 2511 return il; 2512 } 2513 2514 // push the target class 2515 il.append(new LDC(pool.addClass(classname))); 2516 2517 // if this is a super call 2518 if (invoke.getOpcode() == Const.INVOKESPECIAL) { 2519 2520 // Runtime will discover if the object's superclass has an instrumented clone method. 2521 // If so, call it; otherwise call the uninstrumented version. 2522 il.append(dcr_call("dcomp_super_clone", returnType, new Type[] {Type.OBJECT, javalangClass})); 2523 2524 } else { // a regular (non-super) clone() call 2525 2526 // Runtime will discover if the object has an instrumented clone method. 2527 // If so, call it; otherwise call the uninstrumented version. 2528 il.append(dcr_call("dcomp_clone", returnType, new Type[] {Type.OBJECT, javalangClass})); 2529 } 2530 2531 return il; 2532 } 2533 2534 /** 2535 * Create the instructions that replace the object eq or ne branch instruction. They are replaced 2536 * by a call to the specified compare_method (which returns a boolean) followed by the specified 2537 * boolean ifeq or ifne instruction. 2538 */ 2539 InstructionList object_comparison( 2540 BranchInstruction branch, String compare_method, short boolean_if) { 2541 2542 InstructionList il = new InstructionList(); 2543 il.append( 2544 ifact.createInvoke( 2545 dcompRuntimeClassName, compare_method, Type.BOOLEAN, two_objects, Const.INVOKESTATIC)); 2546 assert branch.getTarget() != null; 2547 il.append(InstructionFactory.createBranchInstruction(boolean_if, branch.getTarget())); 2548 return il; 2549 } 2550 2551 /** 2552 * Handles load and store field instructions. The instructions must be augmented to either push 2553 * (load) or pop (store) the tag on the tag stack. This is accomplished by calling the tag get/set 2554 * method for this field. 2555 */ 2556 InstructionList load_store_field(MethodGen mg, FieldInstruction f) { 2557 2558 Type field_type = f.getFieldType(pool); 2559 if (field_type instanceof ReferenceType) { 2560 return null; 2561 } 2562 ObjectType obj_type = (ObjectType) f.getReferenceType(pool); 2563 InstructionList il = new InstructionList(); 2564 String classname = obj_type.getClassName(); 2565 2566 // If this class doesn't support tag fields, don't load/store them 2567 if (!tag_fields_ok(mg, classname)) { 2568 if ((f instanceof GETFIELD) || (f instanceof GETSTATIC)) { 2569 il.append(dcr_call("push_const", Type.VOID, Type.NO_ARGS)); 2570 } else { 2571 il.append(ifact.createConstant(1)); 2572 il.append(dcr_call("discard_tag", Type.VOID, integer_arg)); 2573 } 2574 2575 // Perform the normal field command 2576 il.append(f); 2577 return il; 2578 } 2579 2580 if (f instanceof GETSTATIC) { 2581 il.append( 2582 ifact.createInvoke( 2583 classname, 2584 Premain.tag_method_name(Premain.GET_TAG, classname, f.getFieldName(pool)), 2585 Type.VOID, 2586 Type.NO_ARGS, 2587 Const.INVOKESTATIC)); 2588 } else if (f instanceof PUTSTATIC) { 2589 il.append( 2590 ifact.createInvoke( 2591 classname, 2592 Premain.tag_method_name(Premain.SET_TAG, classname, f.getFieldName(pool)), 2593 Type.VOID, 2594 Type.NO_ARGS, 2595 Const.INVOKESTATIC)); 2596 } else if (f instanceof GETFIELD) { 2597 il.append(InstructionFactory.createDup(obj_type.getSize())); 2598 il.append( 2599 ifact.createInvoke( 2600 classname, 2601 Premain.tag_method_name(Premain.GET_TAG, classname, f.getFieldName(pool)), 2602 Type.VOID, 2603 Type.NO_ARGS, 2604 Const.INVOKEVIRTUAL)); 2605 } else { // must be put field 2606 if (field_type.getSize() == 2) { 2607 LocalVariableGen lv = get_tmp2_local(mg, field_type); 2608 il.append(InstructionFactory.createStore(field_type, lv.getIndex())); 2609 il.append(InstructionFactory.createDup(obj_type.getSize())); 2610 il.append( 2611 ifact.createInvoke( 2612 classname, 2613 Premain.tag_method_name(Premain.SET_TAG, classname, f.getFieldName(pool)), 2614 Type.VOID, 2615 Type.NO_ARGS, 2616 Const.INVOKEVIRTUAL)); 2617 il.append(InstructionFactory.createLoad(field_type, lv.getIndex())); 2618 } else { 2619 il.append(new SWAP()); 2620 il.append(InstructionFactory.createDup(obj_type.getSize())); 2621 il.append( 2622 ifact.createInvoke( 2623 classname, 2624 Premain.tag_method_name(Premain.SET_TAG, classname, f.getFieldName(pool)), 2625 Type.VOID, 2626 Type.NO_ARGS, 2627 Const.INVOKEVIRTUAL)); 2628 il.append(new SWAP()); 2629 } 2630 } 2631 2632 // Perform the normal field command 2633 il.append(f); 2634 2635 return il; 2636 } 2637 2638 /** 2639 * Handles load and store local instructions. The instructions must be augmented to either push 2640 * (load) or pop (store) the tag on the tag stack. This is accomplished by calling the specified 2641 * method in DCRuntime and passing that method the tag frame and the offset of local/parameter. 2642 */ 2643 InstructionList load_store_local( 2644 LocalVariableInstruction lvi, LocalVariableGen tag_frame_local, String method) { 2645 2646 // Don't need tags for objects 2647 assert !(lvi instanceof ALOAD) && !(lvi instanceof ASTORE) : "lvi " + lvi; 2648 2649 InstructionList il = new InstructionList(); 2650 2651 // Push the tag frame and the index of this local 2652 il.append(InstructionFactory.createLoad(object_arr, tag_frame_local.getIndex())); 2653 debugInstrument.log("CreateLoad %s %d%n", object_arr, tag_frame_local.getIndex()); 2654 il.append(ifact.createConstant(lvi.getIndex())); 2655 2656 // Call the runtime method to handle loading/storing the local/parameter 2657 il.append( 2658 ifact.createInvoke( 2659 dcompRuntimeClassName, 2660 method, 2661 Type.VOID, 2662 new Type[] {object_arr, Type.INT}, 2663 Const.INVOKESTATIC)); 2664 il.append(lvi); 2665 return il; 2666 } 2667 2668 /** Returns the number of the specified field in the primitive fields of obj_type. */ 2669 int get_field_num(String name, ObjectType obj_type) { 2670 2671 // If this is the current class, get the information directly 2672 if (obj_type.getClassName().equals(orig_class.getClassName())) { 2673 int fcnt = 0; 2674 for (Field f : orig_class.getFields()) { 2675 if (f.getName().equals(name)) { 2676 return fcnt; 2677 } 2678 if (f.getType() instanceof BasicType) { 2679 fcnt++; 2680 } 2681 } 2682 throw new Error("Can't find " + name + " in " + obj_type); 2683 } 2684 2685 // Look up the class using this classes class loader. This may 2686 // not be the best way to accomplish this. 2687 Class<?> obj_class; 2688 try { 2689 obj_class = Class.forName(obj_type.getClassName(), false, loader); 2690 } catch (Exception e) { 2691 throw new Error("can't find class " + obj_type.getClassName(), e); 2692 } 2693 2694 // Loop through all of the fields, counting the number of primitive fields 2695 int fcnt = 0; 2696 for (java.lang.reflect.Field f : obj_class.getDeclaredFields()) { 2697 if (f.getName().equals(name)) { 2698 return fcnt; 2699 } 2700 if (f.getType().isPrimitive()) { 2701 fcnt++; 2702 } 2703 } 2704 throw new Error("Can't find " + name + " in " + obj_class); 2705 } 2706 2707 /** 2708 * Gets the local variable used to store a category2 temporary. This is used in the PUTFIELD code 2709 * to temporarily store the value being placed in the field. 2710 */ 2711 LocalVariableGen get_tmp2_local(MethodGen mg, Type typ) { 2712 2713 String name = "dcomp_$tmp_" + typ; 2714 // System.out.printf("local var name = %s%n", name); 2715 2716 // See if the local has already been created 2717 for (LocalVariableGen lv : mg.getLocalVariables()) { 2718 if (lv.getName().equals(name)) { 2719 assert lv.getType().equals(typ) : lv + " " + typ; 2720 return lv; 2721 } 2722 } 2723 2724 // Create the variable 2725 return mg.addLocalVariable(name, typ, null, null); 2726 } 2727 2728 /** 2729 * Returns the local variable used to store the return result. If it is not present, creates it 2730 * with the specified type. If the variable is known to already exist, the type can be null. 2731 */ 2732 LocalVariableGen get_return_local(MethodGen mg, @Nullable Type return_type) { 2733 2734 // Find the local used for the return value 2735 LocalVariableGen return_local = null; 2736 for (LocalVariableGen lv : mg.getLocalVariables()) { 2737 if (lv.getName().equals("return__$trace2_val")) { 2738 return_local = lv; 2739 break; 2740 } 2741 } 2742 2743 // If a type was specified and the variable was found, they must match 2744 if (return_local == null) { 2745 assert (return_type != null) : " return__$trace2_val doesn't exist"; 2746 } else { 2747 assert return_type.equals(return_local.getType()) 2748 : " return_type = " + return_type + "; current type = " + return_local.getType(); 2749 } 2750 2751 if (return_local == null) { 2752 // log ("Adding return local of type %s%n", return_type); 2753 return_local = mg.addLocalVariable("return__$trace2_val", return_type, null, null); 2754 } 2755 2756 return return_local; 2757 } 2758 2759 /** 2760 * Creates a MethodInfo corresponding to the specified method. The exit locations are filled in, 2761 * but the reflection information is not generated. Returns null if there are no instructions. 2762 * 2763 * @param class_info class containing the method 2764 * @param mg method to inspect 2765 * @return MethodInfo for the method 2766 */ 2767 @Nullable MethodInfo create_method_info(ClassInfo class_info, MethodGen mg) { 2768 2769 // if (mg.getName().equals("<clinit>")) { 2770 // // This case DOES occur at run time. -MDE 1/22/2010 2771 // } 2772 2773 // Get the argument names for this method 2774 String[] argNames = mg.getArgumentNames(); 2775 LocalVariableGen[] lvs = mg.getLocalVariables(); 2776 int param_offset = 1; 2777 if (mg.isStatic()) { 2778 param_offset = 0; 2779 } 2780 if (lvs != null) { 2781 for (int ii = 0; ii < argNames.length; ii++) { 2782 if ((ii + param_offset) < lvs.length) { 2783 argNames[ii] = lvs[ii + param_offset].getName(); 2784 } 2785 } 2786 } 2787 2788 // Get the argument types for this method 2789 Type[] argTypes = mg.getArgumentTypes(); 2790 @ClassGetName String[] arg_type_strings = new @ClassGetName String[argTypes.length]; 2791 for (int ii = 0; ii < argTypes.length; ii++) { 2792 arg_type_strings[ii] = typeToClassGetName(argTypes[ii]); 2793 // System.out.printf("DCI arg types: %s %s%n", argTypes[ii], arg_type_strings[ii]); 2794 } 2795 2796 // Loop through each instruction and find the line number for each 2797 // return opcode 2798 List<Integer> exit_locs = new ArrayList<>(); 2799 2800 // Tells whether each exit loc in the method is included or not 2801 // (based on filters) 2802 List<Boolean> isIncluded = new ArrayList<>(); 2803 2804 // log ("Looking for exit points in %s%n", mg.getName()); 2805 InstructionList il = mg.getInstructionList(); 2806 int line_number = 0; 2807 int last_line_number = 0; 2808 boolean foundLine; 2809 2810 if (il == null) { 2811 return null; 2812 } 2813 2814 for (InstructionHandle ih = il.getStart(); ih != null; ih = ih.getNext()) { 2815 foundLine = false; 2816 2817 if (ih.hasTargeters()) { 2818 for (InstructionTargeter it : ih.getTargeters()) { 2819 if (it instanceof LineNumberGen) { 2820 LineNumberGen lng = (LineNumberGen) it; 2821 // log (" line number at %s: %d%n", ih, lng.getSourceLine()); 2822 // System.out.printf(" line number at %s: %d%n", ih, 2823 // lng.getSourceLine()); 2824 line_number = lng.getSourceLine(); 2825 foundLine = true; 2826 } 2827 } 2828 } 2829 2830 switch (ih.getInstruction().getOpcode()) { 2831 case Const.ARETURN: 2832 case Const.DRETURN: 2833 case Const.FRETURN: 2834 case Const.IRETURN: 2835 case Const.LRETURN: 2836 case Const.RETURN: 2837 // log ("Exit at line %d%n", line_number); 2838 // only do incremental lines if we don't have the line generator 2839 if (line_number == last_line_number && foundLine == false) { 2840 line_number++; 2841 } 2842 last_line_number = line_number; 2843 2844 exit_locs.add(line_number); 2845 isIncluded.add(true); 2846 break; 2847 2848 default: 2849 break; 2850 } 2851 } 2852 2853 return new MethodInfo( 2854 class_info, mg.getName(), argNames, arg_type_strings, exit_locs, isIncluded); 2855 } 2856 2857 /** 2858 * Adds a call to DCRuntime.set_class_initialized (String classname) to the class initializer for 2859 * this class. Creates a class initializer if one is not currently present. 2860 */ 2861 void track_class_init() { 2862 2863 // Look for the class init method. If not found, create an empty one. 2864 Method cinit = null; 2865 for (Method m : gen.getMethods()) { 2866 if (m.getName().equals("<clinit>")) { 2867 cinit = m; 2868 break; 2869 } 2870 } 2871 if (cinit == null) { 2872 InstructionList il = new InstructionList(); 2873 il.append(InstructionFactory.createReturn(Type.VOID)); 2874 MethodGen cinit_gen = 2875 new MethodGen( 2876 Const.ACC_STATIC, 2877 Type.VOID, 2878 Type.NO_ARGS, 2879 new String[0], 2880 "<clinit>", 2881 gen.getClassName(), 2882 il, 2883 pool); 2884 cinit_gen.setMaxLocals(); 2885 cinit_gen.setMaxStack(); 2886 cinit_gen.update(); 2887 cinit = cinit_gen.getMethod(); 2888 gen.addMethod(cinit); 2889 } 2890 2891 try { 2892 MethodGen cinit_gen = new MethodGen(cinit, gen.getClassName(), pool); 2893 setCurrentStackMapTable(cinit_gen, gen.getMajor()); 2894 2895 // Add a call to DCRuntime.set_class_initialized to the beginning of the method 2896 InstructionList il = new InstructionList(); 2897 il.append(ifact.createConstant(gen.getClassName())); 2898 il.append( 2899 ifact.createInvoke( 2900 dcompRuntimeClassName, 2901 "set_class_initialized", 2902 Type.VOID, 2903 string_arg, 2904 Const.INVOKESTATIC)); 2905 2906 insertAtMethodStart(cinit_gen, il); 2907 createNewStackMapAttribute(cinit_gen); 2908 cinit_gen.setMaxLocals(); 2909 cinit_gen.setMaxStack(); 2910 gen.replaceMethod(cinit, cinit_gen.getMethod()); 2911 } catch (Throwable t) { 2912 if (debugInstrument.enabled) { 2913 t.printStackTrace(); 2914 } 2915 throw new Error( 2916 "Unexpected error processing " + gen.getClassName() + "." + cinit.getName(), t); 2917 } 2918 } 2919 2920 /** 2921 * Creates code that makes the index comparable (for indexing purposes) with the array in array 2922 * load instructions. First the arrayref and its index are duplicated on the stack. Then the 2923 * appropriate array load method is called to mark them as comparable and update the tag stack. 2924 * Finally the original load instruction is performed. 2925 * 2926 * @param inst an array load instruction 2927 * @return instruction list that calls the runtime to handle the array load instruction 2928 */ 2929 InstructionList array_load(Instruction inst) { 2930 2931 InstructionList il = new InstructionList(); 2932 2933 // Duplicate the array ref and index and pass them to DCRuntime 2934 // which will make the index comparable with the array. In the case 2935 // of primtives it will also get the tag for the primitive and push 2936 // it on the tag stack. 2937 il.append(new DUP2()); 2938 String method = "primitive_array_load"; 2939 if (inst instanceof AALOAD) { 2940 method = "ref_array_load"; 2941 } else if (is_uninit_class(gen.getClassName())) { 2942 method = "primitive_array_load_null_ok"; 2943 } 2944 2945 il.append(dcr_call(method, Type.VOID, new Type[] {Type.OBJECT, Type.INT})); 2946 2947 // Perform the original instruction 2948 il.append(inst); 2949 2950 return il; 2951 } 2952 2953 /** 2954 * Creates code to make the index comparable (for indexing purposes) with the array in the array 2955 * store instruction. This is accomplished by calling the specified method and passing it the 2956 * array reference, index, and value (of base_type). The method will mark the array and index as 2957 * comparable and perform the array store. 2958 * 2959 * @param inst an array store instruction 2960 * @param method runtime method to call 2961 * @param base_type type of array store 2962 * @return instruction list that calls the runtime to handle the array store instruction 2963 */ 2964 InstructionList array_store(Instruction inst, String method, Type base_type) { 2965 2966 InstructionList il = new InstructionList(); 2967 Type arr_type = new ArrayType(base_type, 1); 2968 il.append(dcr_call(method, Type.VOID, new Type[] {arr_type, Type.INT, base_type})); 2969 return il; 2970 } 2971 2972 /** 2973 * Creates code that pushes the array's tag onto the tag stack, so that the index is comparable to 2974 * the array length. First, the arrayref is duplicated on the stack. Then a method is called to 2975 * push the array's tag onto the tag stack. Finally the original arraylength instruction is 2976 * performed. 2977 * 2978 * @param inst an arraylength instruction 2979 * @return instruction list that calls the runtime to handle the arraylength instruction 2980 */ 2981 InstructionList array_length(Instruction inst) { 2982 2983 InstructionList il = new InstructionList(); 2984 2985 // Duplicate the array ref and pass it to DCRuntime which will push 2986 // it onto the tag stack. 2987 il.append(new DUP()); 2988 il.append(dcr_call("push_array_tag", Type.VOID, new Type[] {Type.OBJECT})); 2989 2990 // Perform the original instruction 2991 il.append(inst); 2992 2993 return il; 2994 } 2995 2996 /** 2997 * Creates code to make the declared length of a new array comparable to its index. 2998 * 2999 * @param inst a anewarray or newarray instruction 3000 * @return instruction list that calls the runtime to handle the newarray instruction 3001 */ 3002 InstructionList new_array(Instruction inst) { 3003 InstructionList il = new InstructionList(); 3004 3005 // Perform the original instruction 3006 il.append(inst); 3007 3008 // Duplicate the array ref from the top of the stack and pass it 3009 // to DCRuntime which will push it onto the tag stack. 3010 il.append(new DUP()); 3011 il.append(dcr_call("push_array_tag", Type.VOID, new Type[] {Type.OBJECT})); 3012 3013 // Make the array and the count comparable. Also, pop the tags for 3014 // the array and the count off the tag stack. 3015 il.append(dcr_call("cmp_op", Type.VOID, Type.NO_ARGS)); 3016 3017 return il; 3018 } 3019 3020 /** 3021 * Creates code to make the declared lengths of a new two-dimensional array comparable to the 3022 * corresponding indices. 3023 * 3024 * @param inst a multianewarray instruction 3025 * @return instruction list that calls the runtime to handle the multianewarray instruction 3026 */ 3027 InstructionList multiarray2(Instruction inst) { 3028 InstructionList il = new InstructionList(); 3029 3030 // Duplicate both count arguments 3031 il.append(new DUP2()); 3032 3033 // Perform the original instruction 3034 il.append(inst); 3035 3036 // Duplicate the new arrayref and put it below the count arguments 3037 // Stack is now: ..., arrayref, count1, count2, arrayref 3038 il.append(new DUP_X2()); 3039 3040 Type objArray = new ArrayType(Type.OBJECT, 1); 3041 il.append(dcr_call("multianewarray2", Type.VOID, new Type[] {Type.INT, Type.INT, objArray})); 3042 3043 return il; 3044 } 3045 3046 /** 3047 * Returns whether or not this ppt should be included. A ppt is included if it matches ones of the 3048 * select patterns and doesn't match any of the omit patterns. 3049 * 3050 * @param className class to test 3051 * @param methodName method to test 3052 * @param pptName ppt to look for 3053 * @return true if this ppt should be included 3054 */ 3055 boolean should_track(@BinaryName String className, String methodName, String pptName) { 3056 3057 debugInstrument.log("Considering tracking ppt: %s, %s, %s%n", className, methodName, pptName); 3058 3059 // Don't track any JDK classes 3060 if (BcelUtil.inJdk(className)) { 3061 debug_transform.log("ignoring %s, is a JDK class%n", className); 3062 return false; 3063 } 3064 3065 // Don't track toString methods because we call them in 3066 // our debug statements. 3067 if (pptName.contains("toString")) { 3068 debug_transform.log("ignoring %s, is a toString method%n", pptName); 3069 return false; 3070 } 3071 3072 // call shouldIgnore to check ppt-omit-pattern(s) and ppt-select-pattern(s) 3073 return !daikon.chicory.Instrument.shouldIgnore(className, methodName, pptName); 3074 } 3075 3076 /** 3077 * Constructs a ppt entry name from a Method. 3078 * 3079 * @param fullClassName class name 3080 * @param m method 3081 * @return corresponding ppt name 3082 */ 3083 static String methodEntryName(String fullClassName, Method m) { 3084 3085 // System.out.printf("classname = %s, method = %s, short_name = %s%n", 3086 // fullClassName, m, m.getName()); 3087 3088 // Get an array of the type names 3089 Type[] argTypes = m.getArgumentTypes(); 3090 String[] type_names = new String[argTypes.length]; 3091 for (int ii = 0; ii < argTypes.length; ii++) { 3092 type_names[ii] = argTypes[ii].toString(); 3093 } 3094 3095 // Remove exceptions from the name 3096 String full_name = m.toString(); 3097 full_name = full_name.replaceFirst("\\s*throws.*", ""); 3098 3099 return fullClassName 3100 + "." 3101 + DaikonWriter.methodEntryName(fullClassName, type_names, full_name, m.getName()); 3102 } 3103 3104 /** 3105 * Convenience function to construct a call to a static method in DCRuntime. 3106 * 3107 * @param methodName method to call 3108 * @param returnType type of method return 3109 * @param argTypes array of method argument types 3110 * @return InvokeInstruction for the call 3111 */ 3112 InvokeInstruction dcr_call(String methodName, Type returnType, Type[] argTypes) { 3113 3114 return ifact.createInvoke( 3115 dcompRuntimeClassName, methodName, returnType, argTypes, Const.INVOKESTATIC); 3116 } 3117 3118 /** 3119 * Create the code to call discard_tag(tag_count) and append inst to the end of that code. 3120 * 3121 * @param inst instruction to be replaced 3122 * @param tag_count number of tags to discard 3123 * @return InstructionList 3124 */ 3125 InstructionList discard_tag_code(Instruction inst, int tag_count) { 3126 InstructionList il = new InstructionList(); 3127 il.append(ifact.createConstant(tag_count)); 3128 il.append(dcr_call("discard_tag", Type.VOID, integer_arg)); 3129 append_inst(il, inst); 3130 return il; 3131 } 3132 3133 /** 3134 * Duplicates the item on the top of stack. If the value on the top of the stack is a primitive, 3135 * we need to do the same on the tag stack. Otherwise, we need do nothing. 3136 */ 3137 InstructionList dup_tag(Instruction inst, OperandStack stack) { 3138 Type top = stack.peek(); 3139 if (debug_dup.enabled) { 3140 debug_dup.log("DUP -> %s [... %s]%n", "dup", stack_contents(stack, 2)); 3141 } 3142 if (is_primitive(top)) { 3143 return build_il(dcr_call("dup", Type.VOID, Type.NO_ARGS), inst); 3144 } 3145 return null; 3146 } 3147 3148 /** 3149 * Duplicates the item on the top of the stack and inserts it 2 values down in the stack. If the 3150 * value at the top of the stack is not a primitive, there is nothing to do here. If the second 3151 * value is not a primitive, then we need only to insert the duped value down 1 on the tag stack 3152 * (which contains only primitives). 3153 */ 3154 InstructionList dup_x1_tag(Instruction inst, OperandStack stack) { 3155 Type top = stack.peek(); 3156 if (debug_dup.enabled) { 3157 debug_dup.log("DUP -> %s [... %s]%n", "dup_x1", stack_contents(stack, 2)); 3158 } 3159 if (!is_primitive(top)) { 3160 return null; 3161 } 3162 String method = "dup_x1"; 3163 if (!is_primitive(stack.peek(1))) { 3164 method = "dup"; 3165 } 3166 return build_il(dcr_call(method, Type.VOID, Type.NO_ARGS), inst); 3167 } 3168 3169 /** 3170 * Duplicates either the top 2 category 1 values or a single category 2 value and inserts it 2 or 3171 * 3 values down on the stack. 3172 */ 3173 InstructionList dup2_x1_tag(Instruction inst, OperandStack stack) { 3174 String op; 3175 Type top = stack.peek(); 3176 if (is_category2(top)) { 3177 if (is_primitive(stack.peek(1))) { 3178 op = "dup_x1"; 3179 } else { // not a primitive, so just dup 3180 op = "dup"; 3181 } 3182 } else if (is_primitive(top)) { 3183 if (is_primitive(stack.peek(1)) && is_primitive(stack.peek(2))) op = "dup2_x1"; 3184 else if (is_primitive(stack.peek(1))) op = "dup2"; 3185 else if (is_primitive(stack.peek(2))) op = "dup_x1"; 3186 else { 3187 // neither value 1 nor value 2 is primitive 3188 op = "dup"; 3189 } 3190 } else { // top is not primitive 3191 if (is_primitive(stack.peek(1)) && is_primitive(stack.peek(2))) { 3192 op = "dup_x1"; 3193 } else if (is_primitive(stack.peek(1))) { 3194 op = "dup"; 3195 } else { // neither of the top two values is primitive 3196 op = null; 3197 } 3198 } 3199 if (debug_dup.enabled) { 3200 debug_dup.log("DUP2_X1 -> %s [... %s]%n", op, stack_contents(stack, 3)); 3201 } 3202 3203 if (op != null) { 3204 return build_il(dcr_call(op, Type.VOID, Type.NO_ARGS), inst); 3205 } 3206 return null; 3207 } 3208 3209 /** 3210 * Duplicate either one category 2 value or two category 1 values. The instruction is implemented 3211 * as necessary on the tag stack. 3212 */ 3213 InstructionList dup2_tag(Instruction inst, OperandStack stack) { 3214 Type top = stack.peek(); 3215 String op; 3216 if (is_category2(top)) { 3217 op = "dup"; 3218 } else if (is_primitive(top) && is_primitive(stack.peek(1))) op = "dup2"; 3219 else if (is_primitive(top) || is_primitive(stack.peek(1))) op = "dup"; 3220 else { 3221 // both of the top two items are not primitive, nothing to dup 3222 op = null; 3223 } 3224 if (debug_dup.enabled) { 3225 debug_dup.log("DUP2 -> %s [... %s]%n", op, stack_contents(stack, 2)); 3226 } 3227 if (op != null) { 3228 return build_il(dcr_call(op, Type.VOID, Type.NO_ARGS), inst); 3229 } 3230 return null; 3231 } 3232 3233 /** 3234 * Dup the category 1 value on the top of the stack and insert it either two or three values down 3235 * on the stack. 3236 */ 3237 InstructionList dup_x2(Instruction inst, OperandStack stack) { 3238 Type top = stack.peek(); 3239 String op = null; 3240 if (is_primitive(top)) { 3241 if (is_category2(stack.peek(1))) op = "dup_x1"; 3242 else if (is_primitive(stack.peek(1)) && is_primitive(stack.peek(2))) op = "dup_x2"; 3243 else if (is_primitive(stack.peek(1)) || is_primitive(stack.peek(2))) op = "dup_x1"; 3244 else { 3245 op = "dup"; 3246 } 3247 } 3248 if (debug_dup.enabled) { 3249 debug_dup.log("DUP_X2 -> %s [... %s]%n", op, stack_contents(stack, 3)); 3250 } 3251 if (op != null) { 3252 return build_il(dcr_call(op, Type.VOID, Type.NO_ARGS), inst); 3253 } 3254 return null; 3255 } 3256 3257 /** 3258 * Duplicate the top one or two operand stack values and insert two, three, or four values down. 3259 */ 3260 InstructionList dup2_x2(Instruction inst, OperandStack stack) { 3261 Type top = stack.peek(); 3262 String op; 3263 if (is_category2(top)) { 3264 if (is_category2(stack.peek(1))) op = "dup_x1"; 3265 else if (is_primitive(stack.peek(1)) && is_primitive(stack.peek(2))) op = "dup_x2"; 3266 else if (is_primitive(stack.peek(1)) || is_primitive(stack.peek(2))) op = "dup_x1"; 3267 else { 3268 // both values are references 3269 op = "dup"; 3270 } 3271 } else if (is_primitive(top)) { 3272 if (is_category2(stack.peek(1))) { 3273 throw new Error("not supposed to happen " + stack_contents(stack, 3)); 3274 } else if (is_category2(stack.peek(2))) { 3275 if (is_primitive(stack.peek(1))) { 3276 op = "dup2_x1"; 3277 } else { 3278 op = "dup_x1"; 3279 } 3280 } else if (is_primitive(stack.peek(1))) { 3281 if (is_primitive(stack.peek(2)) && is_primitive(stack.peek(3))) op = "dup2_x2"; 3282 else if (is_primitive(stack.peek(2)) || is_primitive(stack.peek(3))) op = "dup2_x1"; 3283 else { 3284 // both 2 and 3 are references 3285 op = "dup2"; 3286 } 3287 } else { // 1 is a reference 3288 if (is_primitive(stack.peek(2)) && is_primitive(stack.peek(3))) op = "dup_x2"; 3289 else if (is_primitive(stack.peek(2)) || is_primitive(stack.peek(3))) op = "dup_x1"; 3290 else { 3291 // both 2 and 3 are references 3292 op = "dup"; 3293 } 3294 } 3295 } else { // top is a reference 3296 if (is_category2(stack.peek(1))) { 3297 throw new Error("not supposed to happen " + stack_contents(stack, 3)); 3298 } else if (is_category2(stack.peek(2))) { 3299 if (is_primitive(stack.peek(1))) { 3300 op = "dup_x1"; 3301 } else { 3302 op = null; // nothing to dup 3303 } 3304 } else if (is_primitive(stack.peek(1))) { 3305 if (is_primitive(stack.peek(2)) && is_primitive(stack.peek(3))) op = "dup_x2"; 3306 else if (is_primitive(stack.peek(2)) || is_primitive(stack.peek(3))) op = "dup_x1"; 3307 else { 3308 // both 2 and 3 are references 3309 op = "dup"; 3310 } 3311 } else { // 1 is a reference 3312 op = null; // nothing to dup 3313 } 3314 } 3315 if (debug_dup.enabled) { 3316 debug_dup.log("DUP_X2 -> %s [... %s]%n", op, stack_contents(stack, 3)); 3317 } 3318 if (op != null) { 3319 return build_il(dcr_call(op, Type.VOID, Type.NO_ARGS), inst); 3320 } 3321 return null; 3322 } 3323 3324 /** 3325 * Pop instructions discard the top of the stack. We want to discard the top of the tag stack iff 3326 * the item on the top of the stack is a primitive. 3327 */ 3328 InstructionList pop_tag(Instruction inst, OperandStack stack) { 3329 Type top = stack.peek(); 3330 if (is_primitive(top)) { 3331 return discard_tag_code(inst, 1); 3332 } 3333 return null; 3334 } 3335 3336 /** 3337 * Pops either the top 2 category 1 values or a single category 2 value from the top of the stack. 3338 * We must do the same to the tag stack if the values are primitives. 3339 */ 3340 InstructionList pop2_tag(Instruction inst, OperandStack stack) { 3341 Type top = stack.peek(); 3342 if (is_category2(top)) { 3343 return discard_tag_code(inst, 1); 3344 } else { 3345 int cnt = 0; 3346 if (is_primitive(top)) { 3347 cnt++; 3348 } 3349 if (is_primitive(stack.peek(1))) { 3350 cnt++; 3351 } 3352 if (cnt > 0) { 3353 return discard_tag_code(inst, cnt); 3354 } 3355 } 3356 return null; 3357 } 3358 3359 /** 3360 * Swaps the two category 1 types on the top of the stack. We need to swap the top of the tag 3361 * stack if the two top elements on the real stack are primitives. 3362 */ 3363 InstructionList swap_tag(Instruction inst, OperandStack stack) { 3364 Type type1 = stack.peek(); 3365 Type type2 = stack.peek(1); 3366 if (is_primitive(type1) && is_primitive(type2)) { 3367 return build_il(dcr_call("swap", Type.VOID, Type.NO_ARGS), inst); 3368 } 3369 return null; 3370 } 3371 3372 /** 3373 * Adjusts the tag stack for load constant opcodes. If the constant is a primitive, pushes its tag 3374 * on the tag stack. If the constant is a reference (string, class), does nothing. 3375 */ 3376 @Nullable InstructionList ldc_tag(Instruction inst, OperandStack stack) { 3377 Type type; 3378 if (inst instanceof LDC) { // LDC_W extends LDC 3379 type = ((LDC) inst).getType(pool); 3380 } else { 3381 type = ((LDC2_W) inst).getType(pool); 3382 } 3383 if (!(type instanceof BasicType)) { 3384 return null; 3385 } 3386 return build_il(dcr_call("push_const", Type.VOID, Type.NO_ARGS), inst); 3387 } 3388 3389 /** 3390 * Handle the instruction that allocates multi-dimensional arrays. If the new array has 2 3391 * dimensions, make the integer arguments comparable to the corresponding indices of the new 3392 * array. For any other number of dimensions, discard the tags for the arguments. Higher 3393 * dimensions should really be handled as well, but there are very few cases of this and the 3394 * resulting code would be quite complex (see multiarray2 for details). 3395 */ 3396 InstructionList multi_newarray_dc(Instruction inst) { 3397 int dims = ((MULTIANEWARRAY) inst).getDimensions(); 3398 if (dims == 2) { 3399 return multiarray2(inst); 3400 } else { 3401 return discard_tag_code(inst, dims); 3402 } 3403 } 3404 3405 /** 3406 * Create an instruction list that calls the runtime to handle returns for the tag stack follow by 3407 * the original return instruction. 3408 * 3409 * @param mg method to modify 3410 * @param inst return instruction to be replaced 3411 * @return the instruction list 3412 */ 3413 InstructionList return_tag(MethodGen mg, Instruction inst) { 3414 Type type = mg.getReturnType(); 3415 InstructionList il = new InstructionList(); 3416 3417 // Push the tag frame 3418 il.append(InstructionFactory.createLoad(object_arr, tag_frame_local.getIndex())); 3419 3420 if ((type instanceof BasicType) && (type != Type.VOID)) { 3421 il.append(dcr_call("normal_exit_primitive", Type.VOID, new Type[] {object_arr})); 3422 } else { 3423 il.append(dcr_call("normal_exit", Type.VOID, new Type[] {object_arr})); 3424 } 3425 il.append(inst); 3426 return il; 3427 } 3428 3429 /** 3430 * Returns whether or not the specified type is a primitive (int, float, double, etc). 3431 * 3432 * @param type type to check 3433 * @return true if type is primitive 3434 */ 3435 @Pure 3436 boolean is_primitive(Type type) { 3437 return (type instanceof BasicType) && (type != Type.VOID); 3438 } 3439 3440 /** 3441 * Returns whether or not the specified type is a category 2 (8 byte) type. 3442 * 3443 * @param type type to check 3444 * @return true if type requires 8 bytes 3445 */ 3446 @Pure 3447 boolean is_category2(Type type) { 3448 return (type == Type.DOUBLE) || (type == Type.LONG); 3449 } 3450 3451 /** 3452 * Converts a BCEL type to a Class. The class referenced will be loaded but not initialized. The 3453 * specified loader must be able to find it. If load is null, the default loader will be used. 3454 * 3455 * @param t type to get class for 3456 * @param loader to use to locate class 3457 * @return instance of class 3458 */ 3459 static Class<?> type_to_class(Type t, ClassLoader loader) { 3460 3461 if (loader == null) { 3462 loader = DCInstrument.class.getClassLoader(); 3463 } 3464 3465 if (t == Type.BOOLEAN) { 3466 return Boolean.TYPE; 3467 } else if (t == Type.BYTE) { 3468 return Byte.TYPE; 3469 } else if (t == Type.CHAR) { 3470 return Character.TYPE; 3471 } else if (t == Type.DOUBLE) { 3472 return Double.TYPE; 3473 } else if (t == Type.FLOAT) { 3474 return Float.TYPE; 3475 } else if (t == Type.INT) { 3476 return Integer.TYPE; 3477 } else if (t == Type.LONG) { 3478 return Long.TYPE; 3479 } else if (t == Type.SHORT) { 3480 return Short.TYPE; 3481 } else if (t instanceof ObjectType || t instanceof ArrayType) { 3482 @ClassGetName String sig = typeToClassGetName(t); 3483 try { 3484 return Class.forName(sig, false, loader); 3485 } catch (Exception e) { 3486 throw new Error("can't get class " + sig, e); 3487 } 3488 } else { 3489 throw new Error("unexpected type " + t); 3490 } 3491 } 3492 3493 /** 3494 * Modify a doubled native method to call its original method. It pops all of the parameter tags 3495 * off of the tag stack. If there is a primitive return value it puts a new tag value on the stack 3496 * for it. 3497 * 3498 * <p>TODO: add a way to provide a synopsis for native methods that affect comparability. 3499 * 3500 * @param gen current class 3501 * @param mg the interface method. Must be native. 3502 */ 3503 void fix_native(ClassGen gen, MethodGen mg) { 3504 3505 InstructionList il = new InstructionList(); 3506 Type[] argTypes = mg.getArgumentTypes(); 3507 String[] argNames = mg.getArgumentNames(); 3508 3509 debug_native.log("Native call %s%n", mg); 3510 3511 // Build local variables for each argument to the method 3512 if (!mg.isStatic()) { 3513 mg.addLocalVariable("this", new ObjectType(mg.getClassName()), null, null); 3514 } 3515 for (int ii = 0; ii < argTypes.length; ii++) { 3516 mg.addLocalVariable(argNames[ii], argTypes[ii], null, null); 3517 } 3518 3519 // Discard the tags for any primitive arguments passed to system 3520 // methods 3521 int primitive_cnt = 0; 3522 for (Type argType : argTypes) { 3523 if (argType instanceof BasicType) { 3524 primitive_cnt++; 3525 } 3526 } 3527 if (primitive_cnt > 0) { 3528 il.append(discard_tag_code(new NOP(), primitive_cnt)); 3529 } 3530 3531 // push a tag if there is a primitive return value 3532 Type returnType = mg.getReturnType(); 3533 if ((returnType instanceof BasicType) && (returnType != Type.VOID)) { 3534 il.append(dcr_call("push_const", Type.VOID, Type.NO_ARGS)); 3535 } 3536 3537 // If the method is not static, push the instance on the stack 3538 if (!mg.isStatic()) { 3539 il.append(InstructionFactory.createLoad(new ObjectType(gen.getClassName()), 0)); 3540 } 3541 3542 // System.out.printf("%s: atc = %d, anc = %d%n", mg.getName(), argTypes.length, 3543 // argNames.length); 3544 3545 // if call is sun.reflect.Reflection.getCallerClass (realFramesToSkip) 3546 if (mg.getName().equals("getCallerClass") 3547 && (argTypes.length == 1) 3548 && gen.getClassName().equals("sun.reflect.Reflection")) { 3549 3550 // The call returns the class realFramesToSkip up on the stack. Since we 3551 // have added this call in between, we need to increment that number by 1. 3552 il.append(InstructionFactory.createLoad(Type.INT, 0)); 3553 il.append(ifact.createConstant(1)); 3554 il.append(new IADD()); 3555 // System.out.printf("adding 1 in %s.%s%n", gen.getClassName(), 3556 // mg.getName()); 3557 3558 } else { // normal call 3559 3560 // push each argument on the stack 3561 int param_index = 1; 3562 if (mg.isStatic()) { 3563 param_index = 0; 3564 } 3565 for (Type argType : argTypes) { 3566 il.append(InstructionFactory.createLoad(argType, param_index)); 3567 param_index += argType.getSize(); 3568 } 3569 } 3570 3571 // Call the method 3572 il.append( 3573 ifact.createInvoke( 3574 gen.getClassName(), 3575 mg.getName(), 3576 mg.getReturnType(), 3577 argTypes, 3578 (mg.isStatic() ? Const.INVOKESTATIC : Const.INVOKEVIRTUAL))); 3579 3580 // If there is a return value, return it 3581 il.append(InstructionFactory.createReturn(mg.getReturnType())); 3582 3583 // We've created new il; we need to set the instruction handle positions. 3584 il.setPositions(); 3585 3586 // Add the instructions to the method 3587 mg.setInstructionList(il); 3588 mg.setMaxStack(); 3589 mg.setMaxLocals(); 3590 3591 // turn off the native flag 3592 mg.setAccessFlags(mg.getAccessFlags() & ~Const.ACC_NATIVE); 3593 } 3594 3595 /** 3596 * Returns whether or not tag fields are used within the specified method of the specified class. 3597 * We can safely use class fields except in Object, String, and Class. 3598 * 3599 * @param mg method to check 3600 * @param classname class to check 3601 * @return true if tag fields may be used in class for method 3602 */ 3603 boolean tag_fields_ok(MethodGen mg, @ClassGetName String classname) { 3604 3605 // Prior to Java 8 an interface could not contain any implementations. 3606 if (gen.isInterface()) { 3607 if (gen.getMajor() < Const.MAJOR_1_8) { 3608 return false; 3609 } 3610 } 3611 3612 if (BcelUtil.isConstructor(mg)) { 3613 if (!this.constructor_is_initialized) { 3614 return false; 3615 } 3616 } 3617 3618 if (!Premain.jdk_instrumented) { 3619 if (BcelUtil.inJdk(classname)) { 3620 return false; 3621 } 3622 } 3623 3624 if (!classname.startsWith("java.lang")) { 3625 return true; 3626 } 3627 3628 if (classname.equals("java.lang.String") 3629 || classname.equals("java.lang.Class") 3630 || classname.equals("java.lang.Object") 3631 || classname.equals("java.lang.ClassLoader")) { 3632 return false; 3633 } 3634 3635 return true; 3636 } 3637 3638 /** 3639 * Returns a string describing the top max_items items on the stack. 3640 * 3641 * @param stack OperandStack 3642 * @param max_items number of items to describe 3643 * @return string describing the top max_items on the operand stack 3644 */ 3645 static String stack_contents(OperandStack stack, int max_items) { 3646 String contents = ""; 3647 if (max_items >= stack.size()) { 3648 max_items = stack.size() - 1; 3649 } 3650 for (int ii = max_items; ii >= 0; ii--) { 3651 if (contents.length() != 0) { 3652 contents += ", "; 3653 } 3654 contents += stack.peek(ii); 3655 } 3656 return contents; 3657 } 3658 3659 /** 3660 * Creates tag get and set accessor methods for each field in gen. An accessor is created for each 3661 * field (including final, static, and private fields). The accessors share the modifiers of their 3662 * field (except that all are final). Accessors are named {@code <field>_<class>__$get_tag} and 3663 * {@code <field>_<class>__$set_tag}. The class name must be included because field names can 3664 * shadow one another. 3665 * 3666 * <p>If tag_fields_ok is true for the class, then tag fields are created and the accessor uses 3667 * the tag fields. If not, tag storage is created separately and accessed via the field number. 3668 * ISSUE? This flag is not currently tested. (markro) 3669 * 3670 * <p>Accessors are also created for each visible superclass field that is not hidden by a field 3671 * in this class. These accessors just call the superclasses accessor. 3672 * 3673 * <p>Any accessors created are added to the class. 3674 * 3675 * @param gen class to check for fields 3676 */ 3677 void create_tag_accessors(ClassGen gen) { 3678 3679 String classname = gen.getClassName(); 3680 3681 // If this class doesn't support tag fields, don't create them 3682 if (!tag_fields_ok(mgen, classname)) return; 3683 3684 Set<String> field_set = new HashSet<>(); 3685 Map<Field, Integer> field_map = build_field_map(gen.getJavaClass()); 3686 3687 // Build accessors for all fields declared in this class 3688 for (Field f : gen.getFields()) { 3689 3690 assert !field_set.contains(f.getName()) : f.getName() + "-" + classname; 3691 field_set.add(f.getName()); 3692 3693 // skip primitive fields 3694 if (!is_primitive(f.getType())) { 3695 continue; 3696 } 3697 3698 MethodGen get_method; 3699 MethodGen set_method; 3700 if (f.isStatic()) { 3701 String full_name = full_name(orig_class, f); 3702 get_method = create_get_tag(gen, f, static_field_id.get(full_name)); 3703 set_method = create_set_tag(gen, f, static_field_id.get(full_name)); 3704 } else { 3705 get_method = create_get_tag(gen, f, field_map.get(f)); 3706 set_method = create_set_tag(gen, f, field_map.get(f)); 3707 } 3708 gen.addMethod(get_method.getMethod()); 3709 gen.addMethod(set_method.getMethod()); 3710 } 3711 3712 // Build accessors for each field declared in a superclass that is 3713 // is not shadowed in a subclass 3714 JavaClass[] super_classes; 3715 try { 3716 super_classes = gen.getJavaClass().getSuperClasses(); 3717 } catch (Exception e) { 3718 throw new Error(e); 3719 } 3720 for (JavaClass super_class : super_classes) { 3721 for (Field f : super_class.getFields()) { 3722 if (f.isPrivate()) { 3723 continue; 3724 } 3725 if (field_set.contains(f.getName())) { 3726 continue; 3727 } 3728 if (!is_primitive(f.getType())) { 3729 continue; 3730 } 3731 3732 field_set.add(f.getName()); 3733 MethodGen get_method; 3734 MethodGen set_method; 3735 if (f.isStatic()) { 3736 String full_name = full_name(super_class, f); 3737 get_method = create_get_tag(gen, f, static_field_id.get(full_name)); 3738 set_method = create_set_tag(gen, f, static_field_id.get(full_name)); 3739 } else { 3740 get_method = create_get_tag(gen, f, field_map.get(f)); 3741 set_method = create_set_tag(gen, f, field_map.get(f)); 3742 } 3743 gen.addMethod(get_method.getMethod()); 3744 gen.addMethod(set_method.getMethod()); 3745 } 3746 } 3747 } 3748 3749 /** 3750 * Builds a Map that relates each field in jc and each of its superclasses to a unique offset. The 3751 * offset can be used to index into a tag array for this class. Instance fields are placed in the 3752 * returned map and static fields are placed in static map (shared between all classes). 3753 * 3754 * @param jc class to check for fields 3755 * @return field offset map 3756 */ 3757 Map<Field, Integer> build_field_map(JavaClass jc) { 3758 3759 // Object doesn't have any primitive fields 3760 if (jc.getClassName().equals("java.lang.Object")) { 3761 return new LinkedHashMap<>(); 3762 } 3763 3764 // Get the offsets for each field in the superclasses. 3765 JavaClass super_jc; 3766 try { 3767 super_jc = jc.getSuperClass(); 3768 } catch (Exception e) { 3769 throw new Error("can't get superclass for " + jc, e); 3770 } 3771 Map<Field, Integer> field_map = build_field_map(super_jc); 3772 int offset = field_map.size(); 3773 3774 // Determine the offset for each primitive field in the class 3775 // Also make sure the static_tags list is large enough for 3776 // of the tags. 3777 for (Field f : jc.getFields()) { 3778 if (!is_primitive(f.getType())) { 3779 continue; 3780 } 3781 if (f.isStatic()) { 3782 if (!in_jdk) { 3783 int min_size = static_field_id.size() + DCRuntime.max_jdk_static; 3784 while (DCRuntime.static_tags.size() <= min_size) DCRuntime.static_tags.add(null); 3785 static_field_id.put(full_name(jc, f), min_size); 3786 } else { // building jdk 3787 String full_name = full_name(jc, f); 3788 if (static_field_id.containsKey(full_name)) { 3789 // System.out.printf("Reusing static field %s value %d%n", 3790 // full_name, static_field_id.get(full_name)); 3791 } else { 3792 // System.out.printf("Allocating new static field %s%n", 3793 // full_name); 3794 static_field_id.put(full_name, static_field_id.size() + 1); 3795 } 3796 } 3797 } else { 3798 field_map.put(f, offset); 3799 offset++; 3800 } 3801 } 3802 3803 return field_map; 3804 } 3805 3806 /** 3807 * Creates a get tag method for field f. The tag corresponding to field f will be pushed on the 3808 * tag stack. 3809 * 3810 * <pre>{@code 3811 * void <field>_<class>__$get_tag() { 3812 * #if f.isStatic() 3813 * DCRuntime.push_static_tag (tag_offset) 3814 * #else 3815 * DCRuntime.push_field_tag (this, tag_offset); 3816 * } 3817 * }</pre> 3818 * 3819 * @param gen class whose accessors are being built. Not necessarily the class declaring f (if f 3820 * is inherited). 3821 * @param f field to build an accessor for 3822 * @param tag_offset offset of f in the tag storage for this field 3823 * @return the get tag method 3824 */ 3825 MethodGen create_get_tag(ClassGen gen, Field f, int tag_offset) { 3826 3827 // Determine the method to call in DCRuntime. Instance fields and static 3828 // fields are handled separately. Also instance fields in special 3829 // classes that are created by the JVM are handled separately since only 3830 // in those classes can fields be read without being written (in java) 3831 String methodname = "push_field_tag"; 3832 Type[] args = object_int; 3833 if (f.isStatic()) { 3834 methodname = "push_static_tag"; 3835 args = integer_arg; 3836 } else if (is_uninit_class(gen.getClassName())) { 3837 methodname = "push_field_tag_null_ok"; 3838 } 3839 3840 String classname = gen.getClassName(); 3841 String accessor_name = Premain.tag_method_name(Premain.GET_TAG, classname, f.getName()); 3842 3843 InstructionList il = new InstructionList(); 3844 3845 if (!f.isStatic()) { 3846 il.append(InstructionFactory.createThis()); 3847 } 3848 il.append(ifact.createConstant(tag_offset)); 3849 il.append(dcr_call(methodname, Type.VOID, args)); 3850 il.append(InstructionFactory.createReturn(Type.VOID)); 3851 3852 int access_flags = f.getAccessFlags(); 3853 if (gen.isInterface()) { 3854 // method in interface cannot be final 3855 access_flags &= ~Const.ACC_FINAL; 3856 if (gen.getMajor() < Const.MAJOR_1_8) { 3857 // If class file version is prior to 8 then a method in an interface 3858 // cannot be static (it's implicit) and must be abstract. 3859 access_flags &= ~Const.ACC_STATIC; 3860 access_flags |= Const.ACC_ABSTRACT; 3861 } 3862 } else { 3863 access_flags |= Const.ACC_FINAL; 3864 } 3865 3866 // Create the get accessor method 3867 MethodGen get_method = 3868 new MethodGen( 3869 access_flags, 3870 Type.VOID, 3871 Type.NO_ARGS, 3872 new String[] {}, 3873 accessor_name, 3874 classname, 3875 il, 3876 pool); 3877 get_method.isPrivate(false); 3878 get_method.isProtected(false); 3879 get_method.isPublic(true); 3880 get_method.setMaxLocals(); 3881 get_method.setMaxStack(); 3882 // add_line_numbers(get_method, il); 3883 3884 return get_method; 3885 } 3886 3887 /** 3888 * Creates a set tag method for field f. The tag on the top of the tag stack will be popped off 3889 * and placed in the tag storeage corresponding to field 3890 * 3891 * <pre>{@code 3892 * void <field>_<class>__$set_tag() { 3893 * #if f.isStatic() 3894 * DCRuntime.pop_static_tag (tag_offset) 3895 * #else 3896 * DCRuntime.pop_field_tag (this, tag_offset); 3897 * } 3898 * }</pre> 3899 * 3900 * @param gen class whose accessors are being built. Not necessarily the class declaring f (if f 3901 * is inherited). 3902 * @param f field to build an accessor for 3903 * @param tag_offset offset of f in the tag storage for this field 3904 * @return the set tag method 3905 */ 3906 MethodGen create_set_tag(ClassGen gen, Field f, int tag_offset) { 3907 3908 String methodname = "pop_field_tag"; 3909 Type[] args = object_int; 3910 if (f.isStatic()) { 3911 methodname = "pop_static_tag"; 3912 args = integer_arg; 3913 } 3914 3915 String classname = gen.getClassName(); 3916 String setter_name = Premain.tag_method_name(Premain.SET_TAG, classname, f.getName()); 3917 3918 InstructionList il = new InstructionList(); 3919 3920 if (!f.isStatic()) { 3921 il.append(InstructionFactory.createThis()); 3922 } 3923 il.append(ifact.createConstant(tag_offset)); 3924 il.append(dcr_call(methodname, Type.VOID, args)); 3925 il.append(InstructionFactory.createReturn(Type.VOID)); 3926 3927 int access_flags = f.getAccessFlags(); 3928 if (gen.isInterface()) { 3929 // method in interface cannot be final 3930 access_flags &= ~Const.ACC_FINAL; 3931 if (gen.getMajor() < Const.MAJOR_1_8) { 3932 // If class file version is prior to 8 then a method in an interface 3933 // cannot be static (it's implicit) and must be abstract. 3934 access_flags &= ~Const.ACC_STATIC; 3935 access_flags |= Const.ACC_ABSTRACT; 3936 } 3937 } else { 3938 access_flags |= Const.ACC_FINAL; 3939 } 3940 3941 // Create the setter method 3942 MethodGen set_method = 3943 new MethodGen( 3944 access_flags, 3945 Type.VOID, 3946 Type.NO_ARGS, 3947 new String[] {}, 3948 setter_name, 3949 classname, 3950 il, 3951 pool); 3952 set_method.setMaxLocals(); 3953 set_method.setMaxStack(); 3954 // add_line_numbers(set_method, il); 3955 3956 return set_method; 3957 } 3958 3959 /** 3960 * Adds the DCompInstrumented interface to the given class. Adds the following method to the 3961 * class, so that it implements the DCompInstrumented interface: 3962 * 3963 * <pre>{@code 3964 * public boolean equals_dcomp_instrumented(Object o) { 3965 * return this.equals(o, null); 3966 * } 3967 * }</pre> 3968 * 3969 * The method does nothing except call the instrumented equals method (boolean equals(Object, 3970 * DCompMarker)). 3971 * 3972 * @param gen class to add interface to 3973 */ 3974 void add_dcomp_interface(ClassGen gen) { 3975 gen.addInterface(DCRuntime.instrumentation_interface); 3976 debugInstrument.log("Added interface DCompInstrumented%n"); 3977 3978 InstructionList il = new InstructionList(); 3979 int access_flags = Const.ACC_PUBLIC; 3980 if (gen.isInterface()) { 3981 access_flags |= Const.ACC_ABSTRACT; 3982 } 3983 MethodGen method = 3984 new MethodGen( 3985 access_flags, 3986 Type.BOOLEAN, 3987 new Type[] {Type.OBJECT}, 3988 new String[] {"obj"}, 3989 "equals_dcomp_instrumented", 3990 gen.getClassName(), 3991 il, 3992 pool); 3993 3994 il.append(InstructionFactory.createLoad(Type.OBJECT, 0)); // load this 3995 il.append(InstructionFactory.createLoad(Type.OBJECT, 1)); // load obj 3996 il.append(new ACONST_NULL()); // use null for marker 3997 il.append( 3998 ifact.createInvoke( 3999 gen.getClassName(), 4000 "equals", 4001 Type.BOOLEAN, 4002 new Type[] {Type.OBJECT, dcomp_marker}, 4003 Const.INVOKEVIRTUAL)); 4004 il.append(InstructionFactory.createReturn(Type.BOOLEAN)); 4005 method.setMaxStack(); 4006 method.setMaxLocals(); 4007 gen.addMethod(method.getMethod()); 4008 il.dispose(); 4009 } 4010 4011 /** 4012 * Adds the following method to a class: 4013 * 4014 * <pre>{@code 4015 * public boolean equals (Object obj) { 4016 * return super.equals(obj); 4017 * } 4018 * }</pre> 4019 * 4020 * Must only be called if the Object equals method has not been overridden; if the equals method 4021 * is already defined in the class, a ClassFormatError will result because of the duplicate 4022 * method. 4023 * 4024 * @param gen class to add method to 4025 */ 4026 void add_equals_method(ClassGen gen) { 4027 InstructionList il = new InstructionList(); 4028 int access_flags = Const.ACC_PUBLIC; 4029 if (gen.isInterface()) { 4030 access_flags |= Const.ACC_ABSTRACT; 4031 } 4032 MethodGen method = 4033 new MethodGen( 4034 access_flags, 4035 Type.BOOLEAN, 4036 new Type[] {Type.OBJECT}, 4037 new String[] {"obj"}, 4038 "equals", 4039 gen.getClassName(), 4040 il, 4041 pool); 4042 4043 il.append(InstructionFactory.createLoad(Type.OBJECT, 0)); // load this 4044 il.append(InstructionFactory.createLoad(Type.OBJECT, 1)); // load obj 4045 il.append( 4046 ifact.createInvoke( 4047 gen.getSuperclassName(), 4048 "equals", 4049 Type.BOOLEAN, 4050 new Type[] {Type.OBJECT}, 4051 Const.INVOKESPECIAL)); 4052 il.append(InstructionFactory.createReturn(Type.BOOLEAN)); 4053 method.setMaxStack(); 4054 method.setMaxLocals(); 4055 gen.addMethod(method.getMethod()); 4056 il.dispose(); 4057 } 4058 4059 /** 4060 * Marks the class as implementing various object methods (currently clone and toString). Callers 4061 * will call the instrumented version of the method if it exists, otherwise they will call the 4062 * uninstrumented version. 4063 * 4064 * @param gen class to check 4065 */ 4066 void handle_object(ClassGen gen) { 4067 Method cl = gen.containsMethod("clone", "()Ljava/lang/Object;"); 4068 if (cl != null) { 4069 gen.addInterface(Signatures.addPackage(dcomp_prefix, "DCompClone")); 4070 } 4071 4072 Method ts = gen.containsMethod("toString", "()Ljava/lang/String;"); 4073 if (ts != null) { 4074 gen.addInterface(Signatures.addPackage(dcomp_prefix, "DCompToString")); 4075 } 4076 } 4077 4078 /** 4079 * Add a dcomp marker argument to indicate this is the instrumented version of the method. 4080 * 4081 * @param mg method to ard dcomp marker to 4082 */ 4083 void add_dcomp_arg(MethodGen mg) { 4084 4085 // Don't modify main or the JVM won't be able to find it. 4086 if (BcelUtil.isMain(mg)) { 4087 return; 4088 } 4089 4090 // Don't modify class init methods, they don't take arguments 4091 if (BcelUtil.isClinit(mg)) { 4092 return; 4093 } 4094 4095 // Add the dcomp marker argument to indicate this is the 4096 // instrumented version of the method. 4097 addNewParameter(mg, "marker", dcomp_marker); 4098 } 4099 4100 /** 4101 * Returns whether or not the method is defined in Object. 4102 * 4103 * @param methodName method to check 4104 * @param argTypes array of argument types to method 4105 * @return true if method is member of Object 4106 */ 4107 @Pure 4108 boolean is_object_method(String methodName, Type[] argTypes) { 4109 for (MethodDef md : obj_methods) { 4110 if (md.equals(methodName, argTypes)) { 4111 return true; 4112 } 4113 } 4114 return false; 4115 } 4116 4117 /** 4118 * Returns whether or not the class is one of those that has values initialized by the JVM or 4119 * native methods. 4120 * 4121 * @param classname class to check 4122 * @return true if classname has members that are uninitialized 4123 */ 4124 @Pure 4125 boolean is_uninit_class(String classname) { 4126 4127 for (String u_name : uninit_classes) { 4128 if (u_name.equals(classname)) { 4129 return true; 4130 } 4131 } 4132 4133 return false; 4134 } 4135 4136 /** 4137 * Creates a method with a DcompMarker argument that does nothing but call the corresponding 4138 * method without the DCompMarker argument. (Currently, only used for ? va main.) 4139 * 4140 * @param mg MethodGen of method to create stub for 4141 * @return the stub 4142 */ 4143 MethodGen create_dcomp_stub(MethodGen mg) { 4144 4145 InstructionList il = new InstructionList(); 4146 Type returnType = mg.getReturnType(); 4147 4148 // if mg is dynamic, Push 'this' on the stack 4149 int offset = 0; 4150 if (!mg.isStatic()) { 4151 il.append(InstructionFactory.createThis()); 4152 offset = 1; 4153 } 4154 4155 // push each argument on the stack 4156 for (Type argType : mg.getArgumentTypes()) { 4157 il.append(InstructionFactory.createLoad(argType, offset)); 4158 offset += argType.getSize(); 4159 } 4160 4161 // Call the method 4162 short kind = Const.INVOKEVIRTUAL; 4163 if (mg.isStatic()) { 4164 kind = Const.INVOKESTATIC; 4165 } 4166 il.append( 4167 ifact.createInvoke( 4168 mg.getClassName(), mg.getName(), returnType, mg.getArgumentTypes(), kind)); 4169 4170 il.append(InstructionFactory.createReturn(returnType)); 4171 4172 // Create the method 4173 Type[] argTypes = BcelUtil.postpendToArray(mg.getArgumentTypes(), dcomp_marker); 4174 String[] argNames = addString(mg.getArgumentNames(), "marker"); 4175 MethodGen dcomp_mg = 4176 new MethodGen( 4177 mg.getAccessFlags(), 4178 returnType, 4179 argTypes, 4180 argNames, 4181 mg.getName(), 4182 mg.getClassName(), 4183 il, 4184 pool); 4185 dcomp_mg.setMaxLocals(); 4186 dcomp_mg.setMaxStack(); 4187 4188 return dcomp_mg; 4189 } 4190 4191 /** 4192 * Writes the static map from field names to their integer ids to the specified file. Can be read 4193 * with restore_static_field_id. Each line contains a key/value combination with a blank 4194 * separating them. 4195 * 4196 * @param file where to write the static field ids 4197 * @throws IOException if unable to find or open the file 4198 */ 4199 static void save_static_field_id(File file) throws IOException { 4200 4201 PrintStream ps = new PrintStream(file); 4202 for (Map.Entry<@KeyFor("static_field_id") String, Integer> entry : static_field_id.entrySet()) { 4203 ps.printf("%s %d%n", entry.getKey(), entry.getValue()); 4204 } 4205 ps.close(); 4206 } 4207 4208 /** 4209 * Restores the static map from the specified file. 4210 * 4211 * @param file where to read the static field ids 4212 * @throws IOException if unable to create an EntryReader 4213 * @see #save_static_field_id(File) 4214 */ 4215 static void restore_static_field_id(File file) throws IOException { 4216 try (EntryReader er = new EntryReader(file, "UTF-8")) { 4217 for (String line : er) { 4218 String[] key_val = line.split(" *"); 4219 assert !static_field_id.containsKey(key_val[0]) : key_val[0] + " " + key_val[1]; 4220 static_field_id.put(key_val[0], Integer.valueOf(key_val[1])); 4221 // System.out.printf("Adding %s %s to static map%n", key_val[0], 4222 // key_val[1]); 4223 } 4224 } 4225 } 4226 4227 /** 4228 * Return the fully qualified fieldname of the specified field. 4229 * 4230 * @param jc class containing the field 4231 * @param f the field 4232 * @return string containing the fully qualified name 4233 */ 4234 protected String full_name(JavaClass jc, Field f) { 4235 return jc.getClassName() + "." + f.getName(); 4236 } 4237 4238 /** 4239 * Return simplified name of a method. Both exceptions and annotations are removed. 4240 * 4241 * @param m the method 4242 * @return string containing the simplified method name 4243 */ 4244 protected String simplify_method_name(Method m) { 4245 // Remove exceptions from the full method name 4246 String full_name = m.toString().replaceFirst("\\s*throws.*", ""); 4247 // Remove annotations from full method name 4248 return full_name.replaceFirst("(?s) \\[.*", ""); 4249 } 4250}