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