1 package org.robolectric.internal.bytecode; 2 3 import static java.lang.invoke.MethodType.methodType; 4 5 import com.google.common.collect.Iterables; 6 import java.lang.invoke.CallSite; 7 import java.lang.invoke.MethodHandle; 8 import java.lang.invoke.MethodHandles; 9 import java.lang.invoke.MethodType; 10 import java.lang.reflect.Modifier; 11 import java.util.List; 12 import java.util.ListIterator; 13 import java.util.Objects; 14 import org.objectweb.asm.ClassReader; 15 import org.objectweb.asm.ClassWriter; 16 import org.objectweb.asm.ConstantDynamic; 17 import org.objectweb.asm.Handle; 18 import org.objectweb.asm.Label; 19 import org.objectweb.asm.MethodVisitor; 20 import org.objectweb.asm.Opcodes; 21 import org.objectweb.asm.Type; 22 import org.objectweb.asm.commons.ClassRemapper; 23 import org.objectweb.asm.commons.GeneratorAdapter; 24 import org.objectweb.asm.commons.JSRInlinerAdapter; 25 import org.objectweb.asm.commons.Method; 26 import org.objectweb.asm.commons.Remapper; 27 import org.objectweb.asm.tree.AbstractInsnNode; 28 import org.objectweb.asm.tree.ClassNode; 29 import org.objectweb.asm.tree.FieldInsnNode; 30 import org.objectweb.asm.tree.FieldNode; 31 import org.objectweb.asm.tree.InsnList; 32 import org.objectweb.asm.tree.InsnNode; 33 import org.objectweb.asm.tree.InvokeDynamicInsnNode; 34 import org.objectweb.asm.tree.JumpInsnNode; 35 import org.objectweb.asm.tree.LabelNode; 36 import org.objectweb.asm.tree.LdcInsnNode; 37 import org.objectweb.asm.tree.MethodInsnNode; 38 import org.objectweb.asm.tree.MethodNode; 39 import org.objectweb.asm.tree.TypeInsnNode; 40 import org.objectweb.asm.tree.VarInsnNode; 41 import org.robolectric.util.PerfStatsCollector; 42 43 /** 44 * Instruments (i.e. modifies the bytecode) of classes to place the scaffolding necessary to use 45 * Robolectric's shadows. 46 */ 47 public class ClassInstrumentor { 48 private static final Handle BOOTSTRAP_INIT; 49 private static final Handle BOOTSTRAP; 50 private static final Handle BOOTSTRAP_STATIC; 51 private static final Handle BOOTSTRAP_INTRINSIC; 52 private static final String ROBO_INIT_METHOD_NAME = "$$robo$init"; 53 protected static final Type OBJECT_TYPE = Type.getType(Object.class); 54 private static final ShadowImpl SHADOW_IMPL = new ShadowImpl(); 55 final Decorator decorator; 56 57 static { 58 String className = Type.getInternalName(InvokeDynamicSupport.class); 59 60 MethodType bootstrap = 61 methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class); 62 63 /* 64 * There is an additional int.class argument to the invokedynamic bootstrap method. This conveys 65 * whether or not the method invocation represents a native method. A one means the original 66 * method was a native method, and a zero means it was not. It should be boolean.class, but 67 * that is nt possible due to https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8322510. 68 */ 69 String bootstrapMethod = 70 bootstrap 71 .appendParameterTypes(MethodHandle.class, /* isNative */ int.class) 72 .toMethodDescriptorString(); 73 String bootstrapIntrinsic = 74 bootstrap.appendParameterTypes(String.class).toMethodDescriptorString(); 75 76 BOOTSTRAP_INIT = 77 new Handle( 78 Opcodes.H_INVOKESTATIC, 79 className, 80 "bootstrapInit", 81 bootstrap.toMethodDescriptorString(), 82 false); 83 BOOTSTRAP = new Handle(Opcodes.H_INVOKESTATIC, className, "bootstrap", bootstrapMethod, false); 84 BOOTSTRAP_STATIC = 85 new Handle(Opcodes.H_INVOKESTATIC, className, "bootstrapStatic", bootstrapMethod, false); 86 BOOTSTRAP_INTRINSIC = 87 new Handle( 88 Opcodes.H_INVOKESTATIC, className, "bootstrapIntrinsic", bootstrapIntrinsic, false); 89 } 90 ClassInstrumentor()91 public ClassInstrumentor() { 92 this(new ShadowDecorator()); 93 } 94 ClassInstrumentor(Decorator decorator)95 protected ClassInstrumentor(Decorator decorator) { 96 this.decorator = decorator; 97 } 98 analyzeClass( byte[] origClassBytes, final InstrumentationConfiguration config, ClassNodeProvider classNodeProvider)99 private MutableClass analyzeClass( 100 byte[] origClassBytes, 101 final InstrumentationConfiguration config, 102 ClassNodeProvider classNodeProvider) { 103 ClassNode classNode = 104 new ClassNode(Opcodes.ASM4) { 105 @Override 106 public MethodVisitor visitMethod( 107 int access, String name, String desc, String signature, String[] exceptions) { 108 MethodVisitor methodVisitor = 109 super.visitMethod(access, name, config.remapParams(desc), signature, exceptions); 110 return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions); 111 } 112 }; 113 114 final ClassReader classReader = new ClassReader(origClassBytes); 115 classReader.accept(classNode, 0); 116 return new MutableClass(classNode, config, classNodeProvider); 117 } 118 instrumentToBytes(MutableClass mutableClass)119 byte[] instrumentToBytes(MutableClass mutableClass) { 120 instrument(mutableClass); 121 122 ClassNode classNode = mutableClass.classNode; 123 ClassWriter writer = new InstrumentingClassWriter(mutableClass.classNodeProvider, classNode); 124 Remapper remapper = 125 new Remapper() { 126 @Override 127 public String map(final String internalName) { 128 return mutableClass.config.mappedTypeName(internalName); 129 } 130 }; 131 ClassRemapper visitor = new ClassRemapper(writer, remapper); 132 classNode.accept(visitor); 133 134 return writer.toByteArray(); 135 } 136 instrument( ClassDetails classDetails, InstrumentationConfiguration config, ClassNodeProvider classNodeProvider)137 public byte[] instrument( 138 ClassDetails classDetails, 139 InstrumentationConfiguration config, 140 ClassNodeProvider classNodeProvider) { 141 PerfStatsCollector perfStats = PerfStatsCollector.getInstance(); 142 MutableClass mutableClass = 143 perfStats.measure( 144 "analyze class", 145 () -> analyzeClass(classDetails.getClassBytes(), config, classNodeProvider)); 146 byte[] instrumentedBytes = 147 perfStats.measure("instrument class", () -> instrumentToBytes(mutableClass)); 148 recordPackageStats(perfStats, mutableClass); 149 return instrumentedBytes; 150 } 151 recordPackageStats(PerfStatsCollector perfStats, MutableClass mutableClass)152 private void recordPackageStats(PerfStatsCollector perfStats, MutableClass mutableClass) { 153 String className = mutableClass.getName(); 154 for (int i = className.indexOf('.'); i != -1; i = className.indexOf('.', i + 1)) { 155 perfStats.incrementCount("instrument package " + className.substring(0, i)); 156 } 157 } 158 instrument(MutableClass mutableClass)159 public void instrument(MutableClass mutableClass) { 160 try { 161 // Need Java version >=7 to allow invokedynamic 162 mutableClass.classNode.version = Math.max(mutableClass.classNode.version, Opcodes.V1_7); 163 164 if (mutableClass.getName().equals("android.util.SparseArray")) { 165 addSetToSparseArray(mutableClass); 166 } 167 168 instrumentMethods(mutableClass); 169 170 if (mutableClass.isInterface()) { 171 mutableClass.addInterface(Type.getInternalName(InstrumentedInterface.class)); 172 } else { 173 makeClassPublic(mutableClass.classNode); 174 if ((mutableClass.classNode.access & Opcodes.ACC_FINAL) == Opcodes.ACC_FINAL) { 175 mutableClass 176 .classNode 177 .visitAnnotation("Lcom/google/errorprone/annotations/DoNotMock;", true) 178 .visit( 179 "value", 180 "This class is final. Consider either:\n" 181 + "1. Using the real class.\n" 182 + "2. If it's a pure data class, adding a Robolectric Builder for it.\n" 183 + "3. If it cannot function on the JVM, adding or enhancing a Robolectric" 184 + " Shadow for it"); 185 } 186 mutableClass.classNode.access = mutableClass.classNode.access & ~Opcodes.ACC_FINAL; 187 188 // If there is no constructor, adds one 189 addNoArgsConstructor(mutableClass); 190 191 addRoboInitMethod(mutableClass); 192 193 removeFinalFromFields(mutableClass); 194 195 decorator.decorate(mutableClass); 196 } 197 } catch (Exception e) { 198 throw new RuntimeException("failed to instrument " + mutableClass.getName(), e); 199 } 200 } 201 202 // See https://github.com/robolectric/robolectric/issues/6840 203 // Adds Set(int, object) to android.util.SparseArray. addSetToSparseArray(MutableClass mutableClass)204 private void addSetToSparseArray(MutableClass mutableClass) { 205 for (MethodNode method : mutableClass.getMethods()) { 206 if ("set".equals(method.name)) { 207 return; 208 } 209 } 210 211 MethodNode setFunction = 212 new MethodNode( 213 Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, 214 "set", 215 "(ILjava/lang/Object;)V", 216 "(ITE;)V", 217 null); 218 RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(setFunction); 219 generator.loadThis(); 220 generator.loadArg(0); 221 generator.loadArg(1); 222 generator.invokeVirtual(mutableClass.classType, new Method("put", "(ILjava/lang/Object;)V")); 223 generator.returnValue(); 224 mutableClass.addMethod(setFunction); 225 } 226 227 /** 228 * Checks if the first or second instruction is a Jacoco load instruction. Robolectric is not 229 * capable at the moment of re-instrumenting Jacoco-instrumented constructors, so these are 230 * currently skipped. 231 * 232 * @param ctor constructor method node 233 * @return whether or not the constructor can be instrumented 234 */ isJacocoInstrumented(MethodNode ctor)235 private boolean isJacocoInstrumented(MethodNode ctor) { 236 AbstractInsnNode[] insns = ctor.instructions.toArray(); 237 if (insns.length > 1) { 238 AbstractInsnNode node = insns[0]; 239 if (node instanceof LabelNode) { 240 node = insns[1]; 241 } 242 if ((node instanceof LdcInsnNode && ((LdcInsnNode) node).cst instanceof ConstantDynamic)) { 243 ConstantDynamic cst = (ConstantDynamic) ((LdcInsnNode) node).cst; 244 return cst.getName().equals("$jacocoData"); 245 } else if (node instanceof MethodInsnNode) { 246 return Objects.equals(((MethodInsnNode) node).name, "$jacocoInit"); 247 } 248 } 249 return false; 250 } 251 252 /** 253 * Adds a call $$robo$init, which instantiates a shadow object if required. This is to support 254 * custom shadows for Jacoco-instrumented classes (except cnstructor shadows). 255 */ addCallToRoboInit(MutableClass mutableClass, MethodNode ctor)256 protected void addCallToRoboInit(MutableClass mutableClass, MethodNode ctor) { 257 AbstractInsnNode returnNode = 258 Iterables.find( 259 ctor.instructions, 260 node -> { 261 if (node.getOpcode() == Opcodes.INVOKESPECIAL) { 262 MethodInsnNode mNode = (MethodInsnNode) node; 263 return (mNode.owner.equals(mutableClass.internalClassName) 264 || mNode.owner.equals(mutableClass.classNode.superName)); 265 } 266 return false; 267 }, 268 null); 269 ctor.instructions.insert( 270 returnNode, 271 new MethodInsnNode( 272 Opcodes.INVOKEVIRTUAL, 273 mutableClass.classType.getInternalName(), 274 ROBO_INIT_METHOD_NAME, 275 "()V")); 276 ctor.instructions.insert(returnNode, new VarInsnNode(Opcodes.ALOAD, 0)); 277 } 278 instrumentMethods(MutableClass mutableClass)279 private void instrumentMethods(MutableClass mutableClass) { 280 if (mutableClass.isInterface()) { 281 for (MethodNode method : mutableClass.getMethods()) { 282 rewriteMethodBody(mutableClass, method); 283 } 284 } else { 285 for (MethodNode method : mutableClass.getMethods()) { 286 rewriteMethodBody(mutableClass, method); 287 288 if (method.name.equals("<clinit>")) { 289 method.name = ShadowConstants.STATIC_INITIALIZER_METHOD_NAME; 290 mutableClass.addMethod(generateStaticInitializerNotifierMethod(mutableClass)); 291 } else if (method.name.equals("<init>")) { 292 if (isJacocoInstrumented(method)) { 293 addCallToRoboInit(mutableClass, method); 294 } else { 295 instrumentConstructor(mutableClass, method); 296 } 297 } else if (!isSyntheticAccessorMethod(method) && !Modifier.isAbstract(method.access)) { 298 instrumentNormalMethod(mutableClass, method); 299 } 300 } 301 } 302 } 303 addNoArgsConstructor(MutableClass mutableClass)304 private static void addNoArgsConstructor(MutableClass mutableClass) { 305 if (!mutableClass.foundMethods.contains("<init>()V")) { 306 MethodNode defaultConstructor = 307 new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "<init>", "()V", "()V", null); 308 RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(defaultConstructor); 309 generator.loadThis(); 310 generator.visitMethodInsn( 311 Opcodes.INVOKESPECIAL, mutableClass.classNode.superName, "<init>", "()V", false); 312 generator.loadThis(); 313 generator.invokeVirtual(mutableClass.classType, new Method(ROBO_INIT_METHOD_NAME, "()V")); 314 generator.returnValue(); 315 mutableClass.addMethod(defaultConstructor); 316 } 317 } 318 319 /** 320 * Generates code like this: 321 * 322 * <pre> 323 * protected void $$robo$init() { 324 * if (__robo_data__ == null) { 325 * __robo_data__ = RobolectricInternals.initializing(this); 326 * } 327 * } 328 * </pre> 329 */ addRoboInitMethod(MutableClass mutableClass)330 private void addRoboInitMethod(MutableClass mutableClass) { 331 MethodNode initMethodNode = 332 new MethodNode( 333 Opcodes.ACC_PROTECTED | Opcodes.ACC_SYNTHETIC, 334 ROBO_INIT_METHOD_NAME, 335 "()V", 336 null, 337 null); 338 RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(initMethodNode); 339 Label alreadyInitialized = new Label(); 340 generator.loadThis(); // this 341 generator.getField( 342 mutableClass.classType, 343 ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, 344 OBJECT_TYPE); // contents of __robo_data__ 345 generator.ifNonNull(alreadyInitialized); 346 generator.loadThis(); // this 347 generator.loadThis(); // this, this 348 writeCallToInitializing(mutableClass, generator); 349 // this, __robo_data__ 350 generator.putField( 351 mutableClass.classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE); 352 generator.mark(alreadyInitialized); 353 generator.returnValue(); 354 mutableClass.addMethod(initMethodNode); 355 } 356 writeCallToInitializing( MutableClass mutableClass, RobolectricGeneratorAdapter generator)357 protected void writeCallToInitializing( 358 MutableClass mutableClass, RobolectricGeneratorAdapter generator) { 359 generator.invokeDynamic( 360 "initializing", 361 Type.getMethodDescriptor(OBJECT_TYPE, mutableClass.classType), 362 BOOTSTRAP_INIT); 363 } 364 removeFinalFromFields(MutableClass mutableClass)365 private static void removeFinalFromFields(MutableClass mutableClass) { 366 for (FieldNode fieldNode : mutableClass.getFields()) { 367 fieldNode.access &= ~Modifier.FINAL; 368 } 369 } 370 isSyntheticAccessorMethod(MethodNode method)371 private static boolean isSyntheticAccessorMethod(MethodNode method) { 372 return (method.access & Opcodes.ACC_SYNTHETIC) != 0; 373 } 374 375 /** 376 * Constructors are instrumented as follows: 377 * 378 * <ul> 379 * <li>The original constructor will be stripped of its instructions leading up to, and 380 * including, the call to super() or this(). It is also renamed to $$robo$$__constructor__ 381 * <li>A method called __constructor__ is created and its job is to call 382 * $$robo$$__constructor__. The __constructor__ method is what gets shadowed if a Shadow 383 * wants to shadow a constructor. 384 * <li>A new constructor is created and contains the stripped instructions of the original 385 * constructor leading up to, and including, the call to super() or this(). Then, it has a 386 * call to $$robo$init to initialize the Class' Shadow Object. Then, it uses invokedynamic 387 * to call __constructor__. Finally, it contains any instructions that might occur after the 388 * return statement in the original constructor. 389 * </ul> 390 * 391 * @param method the constructor to instrument 392 */ instrumentConstructor(MutableClass mutableClass, MethodNode method)393 protected void instrumentConstructor(MutableClass mutableClass, MethodNode method) { 394 int methodAccess = method.access; 395 makeMethodPrivate(method); 396 397 InsnList callSuper = extractCallToSuperConstructor(mutableClass, method); 398 method.name = directMethodName(mutableClass, ShadowConstants.CONSTRUCTOR_METHOD_NAME); 399 mutableClass.addMethod( 400 redirectorMethod(mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME)); 401 402 String[] exceptions = exceptionArray(method); 403 MethodNode initMethodNode = 404 new MethodNode(methodAccess, "<init>", method.desc, method.signature, exceptions); 405 RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(initMethodNode); 406 initMethodNode.instructions.add(callSuper); 407 generator.loadThis(); 408 generator.invokeVirtual(mutableClass.classType, new Method(ROBO_INIT_METHOD_NAME, "()V")); 409 generateClassHandlerCall( 410 mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME, generator, false); 411 412 generator.endMethod(); 413 414 InsnList postamble = extractInstructionsAfterReturn(method, initMethodNode); 415 if (postamble.size() > 0) { 416 initMethodNode.instructions.add(postamble); 417 } 418 mutableClass.addMethod(initMethodNode); 419 } 420 421 /** 422 * Checks to see if there are instructions after RETURN. If there are, it will check to see if 423 * they belong in the call-to-super, or the shadowable part of the constructor. 424 */ extractInstructionsAfterReturn(MethodNode method, MethodNode initMethodNode)425 private InsnList extractInstructionsAfterReturn(MethodNode method, MethodNode initMethodNode) { 426 InsnList removedInstructions = new InsnList(); 427 AbstractInsnNode returnNode = 428 Iterables.find( 429 method.instructions, 430 node -> node instanceof InsnNode && node.getOpcode() == Opcodes.RETURN, 431 null); 432 if (returnNode == null) { 433 return removedInstructions; 434 } 435 if (returnNode.getNext() instanceof LabelNode) { 436 // There are instructions after the return, check where they belong. Note this is a very rare 437 // edge case and only seems to happen with desugared+proguarded classes such as 438 // play-services-basement's ApiException. 439 LabelNode labelAfterReturn = (LabelNode) returnNode.getNext(); 440 boolean inInitMethodNode = 441 Iterables.any( 442 initMethodNode.instructions, 443 input -> 444 input instanceof JumpInsnNode 445 && ((JumpInsnNode) input).label == labelAfterReturn); 446 447 if (inInitMethodNode) { 448 while (returnNode.getNext() != null) { 449 AbstractInsnNode node = returnNode.getNext(); 450 method.instructions.remove(node); 451 removedInstructions.add(node); 452 } 453 } 454 } 455 return removedInstructions; 456 } 457 extractCallToSuperConstructor( MutableClass mutableClass, MethodNode ctor)458 private static InsnList extractCallToSuperConstructor( 459 MutableClass mutableClass, MethodNode ctor) { 460 InsnList removedInstructions = new InsnList(); 461 // Start removing instructions at the beginning of the method. The first instructions of 462 // constructors may vary. 463 int startIndex = 0; 464 465 AbstractInsnNode[] insns = ctor.instructions.toArray(); 466 for (int i = 0; i < insns.length; i++) { 467 AbstractInsnNode node = insns[i]; 468 469 switch (node.getOpcode()) { 470 case Opcodes.INVOKESPECIAL: 471 MethodInsnNode mnode = (MethodInsnNode) node; 472 if (mnode.owner.equals(mutableClass.internalClassName) 473 || mnode.owner.equals(mutableClass.classNode.superName)) { 474 if (!"<init>".equals(mnode.name)) { 475 throw new AssertionError("Invalid MethodInsnNode name"); 476 } 477 478 // remove all instructions in the range 0 (the start) to invokespecial 479 // <init> 480 while (startIndex <= i) { 481 ctor.instructions.remove(insns[startIndex]); 482 removedInstructions.add(insns[startIndex]); 483 startIndex++; 484 } 485 return removedInstructions; 486 } 487 break; 488 489 case Opcodes.ATHROW: 490 ctor.visitCode(); 491 ctor.visitInsn(Opcodes.RETURN); 492 ctor.visitEnd(); 493 return removedInstructions; 494 495 default: 496 // nothing to do 497 } 498 } 499 500 throw new RuntimeException("huh? " + ctor.name + ctor.desc); 501 } 502 503 /** 504 * Instruments a normal method 505 * 506 * <ul> 507 * <li>Rename the method from {@code methodName} to {@code $$robo$$methodName}. 508 * <li>Make it private so we can invoke it directly without subclass overrides taking 509 * precedence. 510 * <li>Remove {@code final} modifiers, if present. 511 * <li>Create a delegator method named {@code methodName} which delegates to the {@link 512 * ClassHandler}. 513 * </ul> 514 */ instrumentNormalMethod(MutableClass mutableClass, MethodNode method)515 protected void instrumentNormalMethod(MutableClass mutableClass, MethodNode method) { 516 // if not abstract, set a final modifier 517 if ((method.access & Opcodes.ACC_ABSTRACT) == 0) { 518 method.access = method.access | Opcodes.ACC_FINAL; 519 } 520 boolean isNativeMethod = (method.access & Opcodes.ACC_NATIVE) != 0; 521 if (isNativeMethod) { 522 instrumentNativeMethod(mutableClass, method); 523 } 524 525 // Create delegator method with same name as original method. The delegator method will use 526 // invokedynamic to decide at runtime whether to call original method or shadowed method 527 String originalName = method.name; 528 method.name = directMethodName(mutableClass, originalName); 529 530 MethodNode delegatorMethodNode = 531 new MethodNode( 532 method.access, originalName, method.desc, method.signature, exceptionArray(method)); 533 delegatorMethodNode.visibleAnnotations = method.visibleAnnotations; 534 delegatorMethodNode.access &= ~(Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL); 535 536 makeMethodPrivate(method); 537 538 RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(delegatorMethodNode); 539 generateClassHandlerCall(mutableClass, method, originalName, generator, isNativeMethod); 540 generator.endMethod(); 541 mutableClass.addMethod(delegatorMethodNode); 542 } 543 /** 544 * Creates native stub which returns the default return value. 545 * 546 * @param mutableClass Class to be instrumented 547 * @param method Method to be instrumented, must be native 548 */ instrumentNativeMethod(MutableClass mutableClass, MethodNode method)549 protected void instrumentNativeMethod(MutableClass mutableClass, MethodNode method) { 550 551 String nativeBindingMethodName = 552 SHADOW_IMPL.directNativeMethodName(mutableClass.getName(), method.name); 553 554 // Generate native binding method 555 MethodNode nativeBindingMethod = 556 new MethodNode( 557 Opcodes.ASM4, 558 nativeBindingMethodName, 559 method.desc, 560 method.signature, 561 exceptionArray(method)); 562 nativeBindingMethod.access = method.access | Opcodes.ACC_SYNTHETIC; 563 makeMethodPrivate(nativeBindingMethod); 564 mutableClass.addMethod(nativeBindingMethod); 565 566 method.access = method.access & ~Opcodes.ACC_NATIVE; 567 568 RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(method); 569 570 Type returnType = generator.getReturnType(); 571 generator.pushDefaultReturnValueToStack(returnType); 572 generator.returnValue(); 573 } 574 directMethodName(MutableClass mutableClass, String originalName)575 protected static String directMethodName(MutableClass mutableClass, String originalName) { 576 return SHADOW_IMPL.directMethodName(mutableClass.getName(), originalName); 577 } 578 579 // todo rename redirectorMethod( MutableClass mutableClass, MethodNode method, String newName)580 private MethodNode redirectorMethod( 581 MutableClass mutableClass, MethodNode method, String newName) { 582 MethodNode redirector = 583 new MethodNode( 584 Opcodes.ASM4, newName, method.desc, method.signature, exceptionArray(method)); 585 redirector.access = 586 method.access & ~(Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL); 587 makeMethodPrivate(redirector); 588 RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(redirector); 589 generator.invokeMethod(mutableClass.internalClassName, method); 590 generator.returnValue(); 591 return redirector; 592 } 593 exceptionArray(MethodNode method)594 protected String[] exceptionArray(MethodNode method) { 595 List<String> exceptions = method.exceptions; 596 return exceptions.toArray(new String[exceptions.size()]); 597 } 598 599 /** Filters methods that might need special treatment because of various reasons */ rewriteMethodBody(MutableClass mutableClass, MethodNode callingMethod)600 private void rewriteMethodBody(MutableClass mutableClass, MethodNode callingMethod) { 601 ListIterator<AbstractInsnNode> instructions = callingMethod.instructions.iterator(); 602 while (instructions.hasNext()) { 603 AbstractInsnNode node = instructions.next(); 604 605 switch (node.getOpcode()) { 606 case Opcodes.NEW: 607 TypeInsnNode newInsnNode = (TypeInsnNode) node; 608 newInsnNode.desc = mutableClass.config.mappedTypeName(newInsnNode.desc); 609 break; 610 611 case Opcodes.GETFIELD: 612 /* falls through */ 613 case Opcodes.PUTFIELD: 614 /* falls through */ 615 case Opcodes.GETSTATIC: 616 /* falls through */ 617 case Opcodes.PUTSTATIC: 618 FieldInsnNode fieldInsnNode = (FieldInsnNode) node; 619 fieldInsnNode.desc = mutableClass.config.mappedTypeName(fieldInsnNode.desc); // todo test 620 break; 621 622 case Opcodes.INVOKESTATIC: 623 /* falls through */ 624 case Opcodes.INVOKEINTERFACE: 625 /* falls through */ 626 case Opcodes.INVOKESPECIAL: 627 /* falls through */ 628 case Opcodes.INVOKEVIRTUAL: 629 MethodInsnNode targetMethod = (MethodInsnNode) node; 630 targetMethod.desc = mutableClass.config.remapParams(targetMethod.desc); 631 if (isGregorianCalendarBooleanConstructor(targetMethod)) { 632 replaceGregorianCalendarBooleanConstructor(instructions, targetMethod); 633 } else if (mutableClass.config.shouldIntercept(targetMethod)) { 634 interceptInvokeVirtualMethod(mutableClass, instructions, targetMethod); 635 } 636 break; 637 638 case Opcodes.INVOKEDYNAMIC: 639 /* no unusual behavior */ 640 break; 641 642 default: 643 break; 644 } 645 } 646 } 647 648 /** 649 * Verifies if the @targetMethod is a {@code <init>(boolean)} constructor for {@link 650 * java.util.GregorianCalendar}. 651 */ isGregorianCalendarBooleanConstructor(MethodInsnNode targetMethod)652 private static boolean isGregorianCalendarBooleanConstructor(MethodInsnNode targetMethod) { 653 return targetMethod.owner.equals("java/util/GregorianCalendar") 654 && targetMethod.name.equals("<init>") 655 && targetMethod.desc.equals("(Z)V"); 656 } 657 658 /** 659 * Replaces the void {@code <init>(boolean)} constructor for a call to the {@code void <init>(int, 660 * int, int)} one. 661 */ replaceGregorianCalendarBooleanConstructor( ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod)662 private static void replaceGregorianCalendarBooleanConstructor( 663 ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) { 664 // Remove the call to GregorianCalendar(boolean) 665 instructions.remove(); 666 667 // Discard the already-pushed parameter for GregorianCalendar(boolean) 668 instructions.add(new InsnNode(Opcodes.POP)); 669 670 // Add parameters values for calling GregorianCalendar(int, int, int) 671 instructions.add(new InsnNode(Opcodes.ICONST_0)); 672 instructions.add(new InsnNode(Opcodes.ICONST_0)); 673 instructions.add(new InsnNode(Opcodes.ICONST_0)); 674 675 // Call GregorianCalendar(int, int, int) 676 instructions.add( 677 new MethodInsnNode( 678 Opcodes.INVOKESPECIAL, 679 targetMethod.owner, 680 targetMethod.name, 681 "(III)V", 682 targetMethod.itf)); 683 } 684 685 /** 686 * Decides to call through the appropriate method to intercept the method with an INVOKEVIRTUAL 687 * Opcode, depending if the invokedynamic bytecode instruction is available (Java 7+). 688 */ interceptInvokeVirtualMethod( MutableClass mutableClass, ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod)689 protected void interceptInvokeVirtualMethod( 690 MutableClass mutableClass, 691 ListIterator<AbstractInsnNode> instructions, 692 MethodInsnNode targetMethod) { 693 instructions.remove(); // remove the method invocation 694 695 Type type = Type.getObjectType(targetMethod.owner); 696 String description = targetMethod.desc; 697 String owner = type.getClassName(); 698 699 if (targetMethod.getOpcode() != Opcodes.INVOKESTATIC) { 700 String thisType = type.getDescriptor(); 701 description = "(" + thisType + description.substring(1); 702 } 703 704 instructions.add( 705 new InvokeDynamicInsnNode(targetMethod.name, description, BOOTSTRAP_INTRINSIC, owner)); 706 } 707 708 /** Replaces protected and private class modifiers with public. */ makeClassPublic(ClassNode clazz)709 private static void makeClassPublic(ClassNode clazz) { 710 clazz.access = 711 (clazz.access | Opcodes.ACC_PUBLIC) & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); 712 } 713 714 /** Replaces protected and public class modifiers with private. */ makeMethodPrivate(MethodNode method)715 protected void makeMethodPrivate(MethodNode method) { 716 method.access = 717 (method.access | Opcodes.ACC_PRIVATE) & ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED); 718 } 719 generateStaticInitializerNotifierMethod(MutableClass mutableClass)720 private static MethodNode generateStaticInitializerNotifierMethod(MutableClass mutableClass) { 721 MethodNode methodNode = new MethodNode(Opcodes.ACC_STATIC, "<clinit>", "()V", "()V", null); 722 RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(methodNode); 723 generator.push(mutableClass.classType); 724 generator.invokeStatic( 725 Type.getType(RobolectricInternals.class), 726 new Method("classInitializing", "(Ljava/lang/Class;)V")); 727 generator.returnValue(); 728 generator.endMethod(); 729 return methodNode; 730 } 731 732 // todo javadocs generateClassHandlerCall( MutableClass mutableClass, MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator, boolean isNativeMethod)733 protected void generateClassHandlerCall( 734 MutableClass mutableClass, 735 MethodNode originalMethod, 736 String originalMethodName, 737 RobolectricGeneratorAdapter generator, 738 boolean isNativeMethod) { 739 Handle original = 740 new Handle( 741 getTag(originalMethod), 742 mutableClass.classType.getInternalName(), 743 originalMethod.name, 744 originalMethod.desc, 745 getTag(originalMethod) == Opcodes.H_INVOKEINTERFACE); 746 747 if (generator.isStatic()) { 748 generator.loadArgs(); 749 generator.invokeDynamic( 750 originalMethodName, originalMethod.desc, BOOTSTRAP_STATIC, original, isNativeMethod); 751 } else { 752 String desc = "(" + mutableClass.classType.getDescriptor() + originalMethod.desc.substring(1); 753 generator.loadThis(); 754 generator.loadArgs(); 755 generator.invokeDynamic(originalMethodName, desc, BOOTSTRAP, original, isNativeMethod); 756 } 757 758 generator.returnValue(); 759 } 760 getTag(MethodNode m)761 int getTag(MethodNode m) { 762 return Modifier.isStatic(m.access) ? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKESPECIAL; 763 } 764 765 // implemented in DirectClassInstrumentor setAndroidJarSDKVersion(int androidJarSDKVersion)766 public void setAndroidJarSDKVersion(int androidJarSDKVersion) {} 767 768 // implemented in DirectClassInstrumentor getAndroidJarSDKVersion()769 protected int getAndroidJarSDKVersion() { 770 return -1; 771 } 772 773 public interface Decorator { decorate(MutableClass mutableClass)774 void decorate(MutableClass mutableClass); 775 } 776 777 /** 778 * Provides try/catch code generation with a {@link org.objectweb.asm.commons.GeneratorAdapter}. 779 */ 780 static class TryCatch { 781 private final Label start; 782 private final Label end; 783 private final Label handler; 784 private final GeneratorAdapter generatorAdapter; 785 TryCatch(GeneratorAdapter generatorAdapter, Type type)786 TryCatch(GeneratorAdapter generatorAdapter, Type type) { 787 this.generatorAdapter = generatorAdapter; 788 this.start = generatorAdapter.mark(); 789 this.end = new Label(); 790 this.handler = new Label(); 791 generatorAdapter.visitTryCatchBlock(start, end, handler, type.getInternalName()); 792 } 793 end()794 void end() { 795 generatorAdapter.mark(end); 796 } 797 handler()798 void handler() { 799 generatorAdapter.mark(handler); 800 } 801 } 802 } 803