• 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 
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