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