SandboxClassLoader.java revision 07e61e04ff795fb67174de36821945e7501c2ff6
1package org.robolectric.internal.bytecode;
2
3import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
4import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
5import static java.lang.invoke.MethodType.methodType;
6import static org.objectweb.asm.Type.ARRAY;
7import static org.objectweb.asm.Type.OBJECT;
8import static org.objectweb.asm.Type.VOID;
9import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
10
11import com.google.common.base.Splitter;
12import com.google.common.collect.ImmutableList;
13import java.io.File;
14import java.io.IOException;
15import java.io.InputStream;
16import java.lang.invoke.CallSite;
17import java.lang.invoke.MethodHandle;
18import java.lang.invoke.MethodHandles;
19import java.lang.invoke.MethodType;
20import java.lang.reflect.Modifier;
21import java.net.MalformedURLException;
22import java.net.URL;
23import java.net.URLClassLoader;
24import java.util.ArrayList;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.List;
28import java.util.ListIterator;
29import java.util.Map;
30import java.util.Set;
31import javax.annotation.Nonnull;
32import org.objectweb.asm.ClassReader;
33import org.objectweb.asm.ClassWriter;
34import org.objectweb.asm.FieldVisitor;
35import org.objectweb.asm.Handle;
36import org.objectweb.asm.Label;
37import org.objectweb.asm.MethodVisitor;
38import org.objectweb.asm.Opcodes;
39import org.objectweb.asm.Type;
40import org.objectweb.asm.commons.GeneratorAdapter;
41import org.objectweb.asm.commons.JSRInlinerAdapter;
42import org.objectweb.asm.commons.Method;
43import org.objectweb.asm.tree.AbstractInsnNode;
44import org.objectweb.asm.tree.ClassNode;
45import org.objectweb.asm.tree.FieldInsnNode;
46import org.objectweb.asm.tree.FieldNode;
47import org.objectweb.asm.tree.InsnList;
48import org.objectweb.asm.tree.InsnNode;
49import org.objectweb.asm.tree.InvokeDynamicInsnNode;
50import org.objectweb.asm.tree.LdcInsnNode;
51import org.objectweb.asm.tree.MethodInsnNode;
52import org.objectweb.asm.tree.MethodNode;
53import org.objectweb.asm.tree.TypeInsnNode;
54import org.objectweb.asm.tree.VarInsnNode;
55import org.robolectric.util.Logger;
56import org.robolectric.util.ReflectionHelpers;
57import org.robolectric.util.Util;
58
59/**
60 * Class loader that modifies the bytecode of Android classes to insert calls to Robolectric's shadow classes.
61 */
62public class SandboxClassLoader extends URLClassLoader implements Opcodes {
63  private final ClassLoader systemClassLoader;
64  private final URLClassLoader urls;
65  private final InstrumentationConfiguration config;
66  private final Map<String, String> classesToRemap;
67  private final Set<MethodRef> methodsToIntercept;
68
69  public SandboxClassLoader(InstrumentationConfiguration config) {
70    this(ClassLoader.getSystemClassLoader(), config);
71  }
72
73  public SandboxClassLoader(
74      ClassLoader systemClassLoader, InstrumentationConfiguration config, URL... urls) {
75    super(getClassPathUrls(systemClassLoader), systemClassLoader.getParent());
76    this.systemClassLoader = systemClassLoader;
77
78    this.config = config;
79    this.urls = new URLClassLoader(urls, null);
80    classesToRemap = convertToSlashes(config.classNameTranslations());
81    methodsToIntercept = convertToSlashes(config.methodsToIntercept());
82    for (URL url : urls) {
83      Logger.debug("Loading classes from: %s", url);
84    }
85  }
86
87  private static URL[] getClassPathUrls(ClassLoader classloader) {
88    if (classloader instanceof URLClassLoader) {
89      return ((URLClassLoader) classloader).getURLs();
90    }
91    return parseJavaClassPath();
92  }
93
94  // TODO(b/65488446): Use a public API once one is available.
95  private static URL[] parseJavaClassPath() {
96    ImmutableList.Builder<URL> urls = ImmutableList.builder();
97    for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
98      try {
99        try {
100          urls.add(new File(entry).toURI().toURL());
101        } catch (SecurityException e) { // File.toURI checks to see if the file is a directory
102          urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
103        }
104      } catch (MalformedURLException e) {
105        Logger.strict("malformed classpath entry: " + entry, e);
106      }
107    }
108    return urls.build().toArray(new URL[0]);
109  }
110
111  @Override
112  public URL getResource(String name) {
113    URL fromParent = super.getResource(name);
114    if (fromParent != null) {
115      return fromParent;
116    }
117    return urls.getResource(name);
118  }
119
120  private InputStream getClassBytesAsStreamPreferringLocalUrls(String resName) {
121    InputStream fromUrlsClassLoader = urls.getResourceAsStream(resName);
122    if (fromUrlsClassLoader != null) {
123      return fromUrlsClassLoader;
124    }
125    return super.getResourceAsStream(resName);
126  }
127
128  @Override
129  protected Class<?> findClass(String name) throws ClassNotFoundException {
130    if (config.shouldAcquire(name)) {
131      return maybeInstrumentClass(name);
132    } else {
133      return systemClassLoader.loadClass(name);
134    }
135  }
136
137  protected Class<?> maybeInstrumentClass(String className) throws ClassNotFoundException {
138    final byte[] origClassBytes = getByteCode(className);
139
140    ClassNode classNode = new ClassNode(Opcodes.ASM4) {
141      @Override
142      public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
143        desc = remapParamType(desc);
144        return super.visitField(access, name, desc, signature, value);
145      }
146
147      @Override
148      public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
149        MethodVisitor methodVisitor = super.visitMethod(access, name, remapParams(desc), signature, exceptions);
150        return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions);
151      }
152    };
153
154    final ClassReader classReader = new ClassReader(origClassBytes);
155    classReader.accept(classNode, 0);
156
157    classNode.interfaces.add(Type.getInternalName(ShadowedObject.class));
158
159    try {
160      byte[] bytes;
161      ClassInfo classInfo = new ClassInfo(className, classNode);
162      if (config.shouldInstrument(classInfo)) {
163        bytes = getInstrumentedBytes(classNode, config.containsStubs(classInfo));
164      } else {
165        bytes = origClassBytes;
166      }
167      ensurePackage(className);
168      return defineClass(className, bytes, 0, bytes.length);
169    } catch (Exception e) {
170      throw new ClassNotFoundException("couldn't load " + className, e);
171    } catch (OutOfMemoryError e) {
172      System.err.println("[ERROR] couldn't load " + className + " in " + this);
173      throw e;
174    }
175  }
176
177  @Override
178  protected Package getPackage(String name) {
179    Package aPackage = super.getPackage(name);
180    if (aPackage != null) {
181      return aPackage;
182    }
183
184    return ReflectionHelpers.callInstanceMethod(systemClassLoader, "getPackage",
185        from(String.class, name));
186  }
187
188  protected byte[] getByteCode(String className) throws ClassNotFoundException {
189    String classFilename = className.replace('.', '/') + ".class";
190    try (InputStream classBytesStream = getClassBytesAsStreamPreferringLocalUrls(classFilename)) {
191      if (classBytesStream == null) throw new ClassNotFoundException(className);
192
193      return Util.readBytes(classBytesStream);
194    } catch (IOException e) {
195      throw new ClassNotFoundException("couldn't load " + className, e);
196    }
197  }
198
199  private void ensurePackage(final String className) {
200    int lastDotIndex = className.lastIndexOf('.');
201    if (lastDotIndex != -1) {
202      String pckgName = className.substring(0, lastDotIndex);
203      Package pckg = getPackage(pckgName);
204      if (pckg == null) {
205        definePackage(pckgName, null, null, null, null, null, null, null);
206      }
207    }
208  }
209
210  private String remapParams(String desc) {
211    StringBuilder buf = new StringBuilder();
212    buf.append("(");
213    for (Type type : Type.getArgumentTypes(desc)) {
214      buf.append(remapParamType(type));
215    }
216    buf.append(")");
217    buf.append(remapParamType(Type.getReturnType(desc)));
218    return buf.toString();
219  }
220
221  // remap Landroid/Foo; to Landroid/Bar;
222  private String remapParamType(String desc) {
223    return remapParamType(Type.getType(desc));
224  }
225
226  private String remapParamType(Type type) {
227    String remappedName;
228    String internalName;
229
230    switch (type.getSort()) {
231      case ARRAY:
232        internalName = type.getInternalName();
233        int count = 0;
234        while (internalName.charAt(count) == '[') count++;
235
236        remappedName = remapParamType(internalName.substring(count));
237        if (remappedName != null) {
238          return Type.getObjectType(internalName.substring(0, count) + remappedName).getDescriptor();
239        }
240        break;
241
242      case OBJECT:
243        internalName = type.getInternalName();
244        remappedName = classesToRemap.get(internalName);
245        if (remappedName != null) {
246          return Type.getObjectType(remappedName).getDescriptor();
247        }
248        break;
249
250      default:
251        break;
252    }
253    return type.getDescriptor();
254  }
255
256  // remap android/Foo to android/Bar
257  private String remapType(String value) {
258    String remappedValue = classesToRemap.get(value);
259    if (remappedValue != null) {
260      value = remappedValue;
261    }
262    return value;
263  }
264
265  private byte[] getInstrumentedBytes(ClassNode classNode, boolean containsStubs) throws ClassNotFoundException {
266    if (InvokeDynamic.ENABLED) {
267      new InvokeDynamicClassInstrumentor(classNode, containsStubs).instrument();
268    } else {
269      new OldClassInstrumentor(classNode, containsStubs).instrument();
270    }
271    ClassWriter writer = new InstrumentingClassWriter(classNode);
272    classNode.accept(writer);
273    return writer.toByteArray();
274  }
275
276  private Map<String, String> convertToSlashes(Map<String, String> map) {
277    HashMap<String, String> newMap = new HashMap<>();
278    for (Map.Entry<String, String> entry : map.entrySet()) {
279      String key = internalize(entry.getKey());
280      String value = internalize(entry.getValue());
281      newMap.put(key, value);
282      newMap.put("L" + key + ";", "L" + value + ";"); // also the param reference form
283    }
284    return newMap;
285  }
286
287  private Set<MethodRef> convertToSlashes(Set<MethodRef> methodRefs) {
288    HashSet<MethodRef> transformed = new HashSet<>();
289    for (MethodRef methodRef : methodRefs) {
290      transformed.add(new MethodRef(internalize(methodRef.className), methodRef.methodName));
291    }
292    return transformed;
293  }
294
295  private String internalize(String className) {
296    return className.replace('.', '/');
297  }
298
299  public static void box(final Type type, ListIterator<AbstractInsnNode> instructions) {
300    if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
301      return;
302    }
303
304    if (Type.VOID_TYPE.equals(type)) {
305      instructions.add(new InsnNode(ACONST_NULL));
306    } else {
307      Type boxed = getBoxedType(type);
308      instructions.add(new TypeInsnNode(NEW, boxed.getInternalName()));
309      if (type.getSize() == 2) {
310        // Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
311        instructions.add(new InsnNode(DUP_X2));
312        instructions.add(new InsnNode(DUP_X2));
313        instructions.add(new InsnNode(POP));
314      } else {
315        // p -> po -> opo -> oop -> o
316        instructions.add(new InsnNode(DUP_X1));
317        instructions.add(new InsnNode(SWAP));
318      }
319      instructions.add(new MethodInsnNode(INVOKESPECIAL, boxed.getInternalName(), "<init>", "(" + type.getDescriptor() + ")V", false));
320    }
321  }
322
323  private static Type getBoxedType(final Type type) {
324    switch (type.getSort()) {
325      case Type.BYTE:
326        return Type.getObjectType("java/lang/Byte");
327      case Type.BOOLEAN:
328        return Type.getObjectType("java/lang/Boolean");
329      case Type.SHORT:
330        return Type.getObjectType("java/lang/Short");
331      case Type.CHAR:
332        return Type.getObjectType("java/lang/Character");
333      case Type.INT:
334        return Type.getObjectType("java/lang/Integer");
335      case Type.FLOAT:
336        return Type.getObjectType("java/lang/Float");
337      case Type.LONG:
338        return Type.getObjectType("java/lang/Long");
339      case Type.DOUBLE:
340        return Type.getObjectType("java/lang/Double");
341    }
342    return type;
343  }
344
345  private boolean shouldIntercept(MethodInsnNode targetMethod) {
346    if (targetMethod.name.equals("<init>")) return false; // sorry, can't strip out calls to super() in constructor
347    return methodsToIntercept.contains(new MethodRef(targetMethod.owner, targetMethod.name))
348        || methodsToIntercept.contains(new MethodRef(targetMethod.owner, "*"));
349  }
350
351  abstract class ClassInstrumentor {
352    private static final String ROBO_INIT_METHOD_NAME = "$$robo$init";
353    static final String GET_ROBO_DATA_SIGNATURE = "()Ljava/lang/Object;";
354    final Type OBJECT_TYPE = Type.getType(Object.class);
355    private final String OBJECT_DESC = Type.getDescriptor(Object.class);
356
357    final ClassNode classNode;
358    private final boolean containsStubs;
359    final String internalClassName;
360    private final String className;
361    final Type classType;
362
363    public ClassInstrumentor(ClassNode classNode, boolean containsStubs) {
364      this.classNode = classNode;
365      this.containsStubs = containsStubs;
366
367      this.internalClassName = classNode.name;
368      this.className = classNode.name.replace('/', '.');
369      this.classType = Type.getObjectType(internalClassName);
370    }
371
372    //todo javadoc. Extract blocks to separate methods.
373    public void instrument() {
374      makeClassPublic(classNode);
375      classNode.access = classNode.access & ~ACC_FINAL;
376
377      // Need Java version >=7 to allow invokedynamic
378      classNode.version = Math.max(classNode.version, V1_7);
379
380      classNode.fields.add(0, new FieldNode(ACC_PUBLIC | ACC_FINAL,
381          ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_DESC, OBJECT_DESC, null));
382
383      Set<String> foundMethods = instrumentMethods();
384
385      // If there is no constructor, adds one
386      addNoArgsConstructor(foundMethods);
387
388      addDirectCallConstructor();
389
390      // Do not override final #equals, #hashCode, and #toString for all classes
391      instrumentInheritedObjectMethod(classNode, foundMethods, "equals", "(Ljava/lang/Object;)Z");
392      instrumentInheritedObjectMethod(classNode, foundMethods, "hashCode", "()I");
393      instrumentInheritedObjectMethod(classNode, foundMethods, "toString", "()Ljava/lang/String;");
394
395      addRoboInitMethod();
396
397      addRoboGetDataMethod();
398
399      doSpecialHandling();
400    }
401
402    @Nonnull
403    private Set<String> instrumentMethods() {
404      Set<String> foundMethods = new HashSet<>();
405      List<MethodNode> methods = new ArrayList<>(classNode.methods);
406      for (MethodNode method : methods) {
407        foundMethods.add(method.name + method.desc);
408
409        filterSpecialMethods(method);
410
411        if (method.name.equals("<clinit>")) {
412          method.name = ShadowConstants.STATIC_INITIALIZER_METHOD_NAME;
413          classNode.methods.add(generateStaticInitializerNotifierMethod());
414        } else if (method.name.equals("<init>")) {
415          instrumentConstructor(method);
416        } else if (!isSyntheticAccessorMethod(method) && !Modifier.isAbstract(method.access)) {
417          instrumentNormalMethod(method);
418        }
419      }
420      return foundMethods;
421    }
422
423    private void addNoArgsConstructor(Set<String> foundMethods) {
424      if (!foundMethods.contains("<init>()V")) {
425        MethodNode defaultConstructor = new MethodNode(ACC_PUBLIC, "<init>", "()V", "()V", null);
426        RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(defaultConstructor);
427        generator.loadThis();
428        generator.visitMethodInsn(INVOKESPECIAL, classNode.superName, "<init>", "()V", false);
429        generator.loadThis();
430        generator.invokeVirtual(classType, new Method(ROBO_INIT_METHOD_NAME, "()V"));
431        generator.returnValue();
432        classNode.methods.add(defaultConstructor);
433      }
434    }
435
436    abstract protected void addDirectCallConstructor();
437
438    private void addRoboInitMethod() {
439      MethodNode initMethodNode = new MethodNode(ACC_PROTECTED, ROBO_INIT_METHOD_NAME, "()V", null, null);
440      RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(initMethodNode);
441      Label alreadyInitialized = new Label();
442      generator.loadThis();                                         // this
443      generator.getField(classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);  // contents of __robo_data__
444      generator.ifNonNull(alreadyInitialized);
445      generator.loadThis();                                         // this
446      generator.loadThis();                                         // this, this
447      writeCallToInitializing(generator);
448      // this, __robo_data__
449      generator.putField(classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);
450      generator.mark(alreadyInitialized);
451      generator.returnValue();
452      classNode.methods.add(initMethodNode);
453    }
454
455    abstract protected void writeCallToInitializing(RobolectricGeneratorAdapter generator);
456
457    private void addRoboGetDataMethod() {
458      MethodNode initMethodNode = new MethodNode(ACC_PUBLIC, ShadowConstants.GET_ROBO_DATA_METHOD_NAME, GET_ROBO_DATA_SIGNATURE, null, null);
459      RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(initMethodNode);
460      generator.loadThis();                                         // this
461      generator.getField(classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);  // contents of __robo_data__
462      generator.returnValue();
463      generator.endMethod();
464      classNode.methods.add(initMethodNode);
465    }
466
467    private void doSpecialHandling() {
468      if (className.equals("android.os.Build$VERSION")) {
469        for (Object field : classNode.fields) {
470          FieldNode fieldNode = (FieldNode) field;
471          fieldNode.access &= ~(Modifier.FINAL);
472        }
473      }
474    }
475
476    /**
477     * Checks if the given method in the class if overriding, at some point of it's
478     * inheritance tree, a final method
479     */
480    private boolean isOverridingFinalMethod(ClassNode classNode, String methodName, String methodSignature) {
481      while (true) {
482        List<MethodNode> methods = new ArrayList<>(classNode.methods);
483
484        for (MethodNode method : methods) {
485          if (method.name.equals(methodName) && method.desc.equals(methodSignature)) {
486            if ((method.access & ACC_FINAL) != 0) {
487              return true;
488            }
489          }
490        }
491
492        if (classNode.superName == null) {
493          return false;
494        }
495
496        try {
497          byte[] byteCode = getByteCode(classNode.superName);
498          ClassReader classReader = new ClassReader(byteCode);
499          classNode = new ClassNode();
500          classReader.accept(classNode, 0);
501        } catch (ClassNotFoundException e) {
502          e.printStackTrace();
503        }
504
505      }
506    }
507
508    private boolean isSyntheticAccessorMethod(MethodNode method) {
509      return (method.access & ACC_SYNTHETIC) != 0;
510    }
511
512    /**
513     * To be used to instrument methods inherited from the Object class,
514     * such as hashCode, equals, and toString.
515     * Adds the methods directly to the class.
516     */
517    private void instrumentInheritedObjectMethod(ClassNode classNode, Set<String> foundMethods, final String methodName, String methodDesc) {
518      // Won't instrument if method is overriding a final method
519      if (isOverridingFinalMethod(classNode, methodName, methodDesc)) {
520        return;
521      }
522
523      // if the class doesn't directly override the method, it adds it as a direct invocation and instruments it
524      if (!foundMethods.contains(methodName + methodDesc)) {
525        MethodNode methodNode = new MethodNode(ACC_PUBLIC, methodName, methodDesc, null, null);
526        RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(methodNode);
527        generator.invokeMethod("java/lang/Object", methodNode);
528        generator.returnValue();
529        generator.endMethod();
530        this.classNode.methods.add(methodNode);
531        instrumentNormalMethod(methodNode);
532      }
533    }
534
535    private void instrumentConstructor(MethodNode method) {
536      makeMethodPrivate(method);
537
538      if (containsStubs) {
539        method.instructions.clear();
540
541        RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(method);
542        generator.loadThis();
543        generator.visitMethodInsn(INVOKESPECIAL, classNode.superName, "<init>", "()V", false);
544        generator.returnValue();
545        generator.endMethod();
546      }
547
548      InsnList removedInstructions = extractCallToSuperConstructor(method);
549      method.name = new ShadowImpl().directMethodName(ShadowConstants.CONSTRUCTOR_METHOD_NAME);
550      classNode.methods.add(redirectorMethod(method, ShadowConstants.CONSTRUCTOR_METHOD_NAME));
551
552      String[] exceptions = exceptionArray(method);
553      MethodNode methodNode = new MethodNode(method.access, "<init>", method.desc, method.signature, exceptions);
554      makeMethodPublic(methodNode);
555      RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(methodNode);
556
557      methodNode.instructions = removedInstructions;
558
559      generator.loadThis();
560      generator.invokeVirtual(classType, new Method(ROBO_INIT_METHOD_NAME, "()V"));
561      generateShadowCall(method, ShadowConstants.CONSTRUCTOR_METHOD_NAME, generator);
562
563      generator.endMethod();
564      classNode.methods.add(methodNode);
565    }
566
567    private InsnList extractCallToSuperConstructor(MethodNode ctor) {
568      InsnList removedInstructions = new InsnList();
569      int startIndex = 0;
570
571      AbstractInsnNode[] insns = ctor.instructions.toArray();
572      for (int i = 0; i < insns.length; i++) {
573        AbstractInsnNode node = insns[i];
574
575        switch (node.getOpcode()) {
576          case ALOAD:
577            VarInsnNode vnode = (VarInsnNode) node;
578            if (vnode.var == 0) {
579              startIndex = i;
580            }
581            break;
582
583          case INVOKESPECIAL:
584            MethodInsnNode mnode = (MethodInsnNode) node;
585            if (mnode.owner.equals(internalClassName) || mnode.owner.equals(classNode.superName)) {
586              assert mnode.name.equals("<init>");
587
588              // remove all instructions in the range startIndex..i, from aload_0 to invokespecial <init>
589              while (startIndex <= i) {
590                ctor.instructions.remove(insns[startIndex]);
591                removedInstructions.add(insns[startIndex]);
592                startIndex++;
593              }
594              return removedInstructions;
595            }
596            break;
597
598          case ATHROW:
599            ctor.visitCode();
600            ctor.visitInsn(RETURN);
601            ctor.visitEnd();
602            return removedInstructions;
603        }
604      }
605
606      throw new RuntimeException("huh? " + ctor.name + ctor.desc);
607    }
608
609    //TODO javadocs
610    private void instrumentNormalMethod(MethodNode method) {
611      // if not abstract, set a final modifier
612      if ((method.access & ACC_ABSTRACT) == 0) {
613        method.access = method.access | ACC_FINAL;
614      }
615      // if a native method, remove native modifier and force return a default value
616      if ((method.access & ACC_NATIVE) != 0) {
617        method.access = method.access & ~ACC_NATIVE;
618
619        RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(method);
620        Type returnType = generator.getReturnType();
621        generator.pushDefaultReturnValueToStack(returnType);
622        generator.returnValue();
623      }
624
625      // todo figure out
626      String originalName = method.name;
627      method.name = new ShadowImpl().directMethodName(originalName);
628
629      MethodNode delegatorMethodNode = new MethodNode(method.access, originalName, method.desc, method.signature, exceptionArray(method));
630      delegatorMethodNode.visibleAnnotations = method.visibleAnnotations;
631      delegatorMethodNode.access &= ~(ACC_NATIVE | ACC_ABSTRACT | ACC_FINAL);
632
633      makeMethodPrivate(method);
634
635      RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(delegatorMethodNode);
636
637      generateShadowCall(method, originalName, generator);
638
639      generator.endMethod();
640
641      classNode.methods.add(delegatorMethodNode);
642    }
643
644    //todo rename
645    private MethodNode redirectorMethod(MethodNode method, String newName) {
646      MethodNode redirector = new MethodNode(ASM4, newName, method.desc, method.signature, exceptionArray(method));
647      redirector.access = method.access & ~(ACC_NATIVE | ACC_ABSTRACT | ACC_FINAL);
648      makeMethodPrivate(redirector);
649      RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(redirector);
650      generator.invokeMethod(internalClassName, method);
651      generator.returnValue();
652      return redirector;
653    }
654
655    private String[] exceptionArray(MethodNode method) {
656      return ((List<String>) method.exceptions).toArray(new String[method.exceptions.size()]);
657    }
658
659    /**
660     * Filters methods that might need special treatment because of various reasons
661     */
662    private void filterSpecialMethods(MethodNode callingMethod) {
663      ListIterator<AbstractInsnNode> instructions = callingMethod.instructions.iterator();
664      while (instructions.hasNext()) {
665        AbstractInsnNode node = instructions.next();
666
667        switch (node.getOpcode()) {
668          case NEW:
669            TypeInsnNode newInsnNode = (TypeInsnNode) node;
670            newInsnNode.desc = remapType(newInsnNode.desc);
671            break;
672
673          case GETFIELD:
674            /* falls through */
675          case PUTFIELD:
676            /* falls through */
677          case GETSTATIC:
678            /* falls through */
679          case PUTSTATIC:
680            FieldInsnNode fieldInsnNode = (FieldInsnNode) node;
681            fieldInsnNode.desc = remapType(fieldInsnNode.desc); // todo test
682            break;
683
684          case INVOKESTATIC:
685            /* falls through */
686          case INVOKEINTERFACE:
687            /* falls through */
688          case INVOKESPECIAL:
689            /* falls through */
690          case INVOKEVIRTUAL:
691            MethodInsnNode targetMethod = (MethodInsnNode) node;
692            targetMethod.desc = remapParams(targetMethod.desc);
693            if (isGregorianCalendarBooleanConstructor(targetMethod)) {
694              replaceGregorianCalendarBooleanConstructor(instructions, targetMethod);
695            } else if (shouldIntercept(targetMethod)) {
696              interceptInvokeVirtualMethod(instructions, targetMethod);
697            }
698            break;
699
700          case INVOKEDYNAMIC:
701            /* no unusual behavior */
702            break;
703
704          default:
705            break;
706        }
707      }
708    }
709
710    /**
711     * Verifies if the @targetMethod is a <init>(boolean) constructor for {@link java.util.GregorianCalendar}
712     */
713    private boolean isGregorianCalendarBooleanConstructor(MethodInsnNode targetMethod) {
714      return targetMethod.owner.equals("java/util/GregorianCalendar") &&
715          targetMethod.name.equals("<init>") &&
716          targetMethod.desc.equals("(Z)V");
717    }
718
719    /**
720     * Replaces the void <init> (boolean) constructor for a call to the void <init> (int, int, int) one
721     */
722    private void replaceGregorianCalendarBooleanConstructor(ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
723      // Remove the call to GregorianCalendar(boolean)
724      instructions.remove();
725
726      // Discard the already-pushed parameter for GregorianCalendar(boolean)
727      instructions.add(new InsnNode(POP));
728
729      // Add parameters values for calling GregorianCalendar(int, int, int)
730      instructions.add(new InsnNode(ICONST_0));
731      instructions.add(new InsnNode(ICONST_0));
732      instructions.add(new InsnNode(ICONST_0));
733
734      // Call GregorianCalendar(int, int, int)
735      instructions.add(new MethodInsnNode(INVOKESPECIAL, targetMethod.owner, targetMethod.name, "(III)V", targetMethod.itf));
736    }
737
738    /**
739     * Decides to call through the appropriate method to intercept the method with an INVOKEVIRTUAL Opcode,
740     * depending if the invokedynamic bytecode instruction is available (Java 7+)
741     */
742    abstract protected void interceptInvokeVirtualMethod(ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod);
743
744    /**
745     * Replaces protected and private class modifiers with public
746     */
747    private void makeClassPublic(ClassNode clazz) {
748      clazz.access = (clazz.access | ACC_PUBLIC) & ~(ACC_PROTECTED | ACC_PRIVATE);
749    }
750
751    /**
752     * Replaces protected and private method modifiers with public
753     */
754    private void makeMethodPublic(MethodNode method) {
755      method.access = (method.access | ACC_PUBLIC) & ~(ACC_PROTECTED | ACC_PRIVATE);
756    }
757
758    /**
759     * Replaces protected and public class modifiers with private
760     */
761    private void makeMethodPrivate(MethodNode method) {
762      method.access = (method.access | ACC_PRIVATE) & ~(ACC_PUBLIC | ACC_PROTECTED);
763    }
764
765    private MethodNode generateStaticInitializerNotifierMethod() {
766      MethodNode methodNode = new MethodNode(ACC_STATIC, "<clinit>", "()V", "()V", null);
767      RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(methodNode);
768      generator.push(classType);
769      generator.invokeStatic(Type.getType(RobolectricInternals.class), new Method("classInitializing", "(Ljava/lang/Class;)V"));
770      generator.returnValue();
771      generator.endMethod();
772      return methodNode;
773    }
774
775    // todo javadocs
776    protected abstract void generateShadowCall(MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator);
777
778    int getTag(MethodNode m) {
779      return Modifier.isStatic(m.access) ? H_INVOKESTATIC : H_INVOKESPECIAL;
780    }
781  }
782
783  /**
784   * ClassWriter implementation that verifies classes by comparing type information obtained
785   * from loading the classes as resources. This was taken from the ASM ClassWriter unit tests.
786   */
787  private class InstrumentingClassWriter extends ClassWriter {
788
789    /**
790     * Preserve stack map frames for V51 and newer bytecode. This fixes class verification errors
791     * for JDK7 and JDK8. The option to disable bytecode verification was removed in JDK8.
792     *
793     * Don't bother for V50 and earlier bytecode, because it doesn't contain stack map frames, and
794     * also because ASM's stack map frame handling doesn't support the JSR and RET instructions
795     * present in legacy bytecode.
796     */
797    public InstrumentingClassWriter(ClassNode classNode) {
798      super(classNode.version >= 51 ? ClassWriter.COMPUTE_FRAMES : ClassWriter.COMPUTE_MAXS);
799    }
800
801    @Override
802    public int newNameType(String name, String desc) {
803      return super.newNameType(name, desc.charAt(0) == ')' ? remapParams(desc) : remapParamType(desc));
804    }
805
806    @Override
807    public int newClass(String value) {
808      value = remapType(value);
809      return super.newClass(value);
810    }
811
812    @Override
813    protected String getCommonSuperClass(final String type1, final String type2) {
814      try {
815        ClassReader info1 = typeInfo(type1);
816        ClassReader info2 = typeInfo(type2);
817        if ((info1.getAccess() & Opcodes.ACC_INTERFACE) != 0) {
818          if (typeImplements(type2, info2, type1)) {
819            return type1;
820          }
821          if ((info2.getAccess() & Opcodes.ACC_INTERFACE) != 0) {
822            if (typeImplements(type1, info1, type2)) {
823              return type2;
824            }
825          }
826          return "java/lang/Object";
827        }
828        if ((info2.getAccess() & Opcodes.ACC_INTERFACE) != 0) {
829          if (typeImplements(type1, info1, type2)) {
830            return type2;
831          } else {
832            return "java/lang/Object";
833          }
834        }
835        StringBuilder b1 = typeAncestors(type1, info1);
836        StringBuilder b2 = typeAncestors(type2, info2);
837        String result = "java/lang/Object";
838        int end1 = b1.length();
839        int end2 = b2.length();
840        while (true) {
841          int start1 = b1.lastIndexOf(";", end1 - 1);
842          int start2 = b2.lastIndexOf(";", end2 - 1);
843          if (start1 != -1 && start2 != -1
844              && end1 - start1 == end2 - start2) {
845            String p1 = b1.substring(start1 + 1, end1);
846            String p2 = b2.substring(start2 + 1, end2);
847            if (p1.equals(p2)) {
848              result = p1;
849              end1 = start1;
850              end2 = start2;
851            } else {
852              return result;
853            }
854          } else {
855            return result;
856          }
857        }
858      } catch (IOException e) {
859        return "java/lang/Object"; // Handle classes that may be obfuscated
860      }
861    }
862
863    private StringBuilder typeAncestors(String type, ClassReader info) throws IOException {
864      StringBuilder b = new StringBuilder();
865      while (!"java/lang/Object".equals(type)) {
866        b.append(';').append(type);
867        type = info.getSuperName();
868        info = typeInfo(type);
869      }
870      return b;
871    }
872
873    private boolean typeImplements(String type, ClassReader info, String itf) throws IOException {
874      while (!"java/lang/Object".equals(type)) {
875        String[] itfs = info.getInterfaces();
876        for (String itf2 : itfs) {
877          if (itf2.equals(itf)) {
878            return true;
879          }
880        }
881        for (String itf1 : itfs) {
882          if (typeImplements(itf1, typeInfo(itf1), itf)) {
883            return true;
884          }
885        }
886        type = info.getSuperName();
887        info = typeInfo(type);
888      }
889      return false;
890    }
891
892    private ClassReader typeInfo(final String type) throws IOException {
893      try (InputStream is = getClassBytesAsStreamPreferringLocalUrls(type + ".class")) {
894        return new ClassReader(is);
895      }
896    }
897  }
898
899  /**
900   * GeneratorAdapter implementation specific to generate code for Robolectric purposes
901   */
902  private static class RobolectricGeneratorAdapter extends GeneratorAdapter {
903    private final boolean isStatic;
904    private final String desc;
905
906    public RobolectricGeneratorAdapter(MethodNode methodNode) {
907      super(Opcodes.ASM4, methodNode, methodNode.access, methodNode.name, methodNode.desc);
908      this.isStatic = Modifier.isStatic(methodNode.access);
909      this.desc = methodNode.desc;
910    }
911
912    public void loadThisOrNull() {
913      if (isStatic) {
914        loadNull();
915      } else {
916        loadThis();
917      }
918    }
919
920    public boolean isStatic() {
921      return isStatic;
922    }
923
924    public void loadNull() {
925      visitInsn(ACONST_NULL);
926    }
927
928    public Type getReturnType() {
929      return Type.getReturnType(desc);
930    }
931
932    /**
933     * Forces a return of a default value, depending on the method's return type
934     *
935     * @param type The method's return type
936     */
937    public void pushDefaultReturnValueToStack(Type type) {
938      if (type.equals(Type.BOOLEAN_TYPE)) {
939        push(false);
940      } else if (type.equals(Type.INT_TYPE) || type.equals(Type.SHORT_TYPE) || type.equals(Type.BYTE_TYPE) || type.equals(Type.CHAR_TYPE)) {
941        push(0);
942      } else if (type.equals(Type.LONG_TYPE)) {
943        push(0L);
944      } else if (type.equals(Type.FLOAT_TYPE)) {
945        push(0f);
946      } else if (type.equals(Type.DOUBLE_TYPE)) {
947        push(0d);
948      } else if (type.getSort() == ARRAY || type.getSort() == OBJECT) {
949        loadNull();
950      }
951    }
952
953    private void invokeMethod(String internalClassName, MethodNode method) {
954      invokeMethod(internalClassName, method.name, method.desc);
955    }
956
957    private void invokeMethod(String internalClassName, String methodName, String methodDesc) {
958      if (isStatic()) {
959        loadArgs();                                             // this, [args]
960        visitMethodInsn(INVOKESTATIC, internalClassName, methodName, methodDesc, false);
961      } else {
962        loadThisOrNull();                                       // this
963        loadArgs();                                             // this, [args]
964        visitMethodInsn(INVOKESPECIAL, internalClassName, methodName, methodDesc, false);
965      }
966    }
967
968    public TryCatch tryStart(Type exceptionType) {
969      return new TryCatch(this, exceptionType);
970    }
971  }
972
973  /**
974   * Provides try/catch code generation with a {@link org.objectweb.asm.commons.GeneratorAdapter}
975   */
976  static class TryCatch {
977    private final Label start;
978    private final Label end;
979    private final Label handler;
980    private final GeneratorAdapter generatorAdapter;
981
982    TryCatch(GeneratorAdapter generatorAdapter, Type type) {
983      this.generatorAdapter = generatorAdapter;
984      this.start = generatorAdapter.mark();
985      this.end = new Label();
986      this.handler = new Label();
987      generatorAdapter.visitTryCatchBlock(start, end, handler, type.getInternalName());
988    }
989
990    void end() {
991      generatorAdapter.mark(end);
992    }
993
994    void handler() {
995      generatorAdapter.mark(handler);
996    }
997  }
998
999  public class OldClassInstrumentor extends SandboxClassLoader.ClassInstrumentor {
1000    private final Type PLAN_TYPE = Type.getType(ClassHandler.Plan.class);
1001    private final Type THROWABLE_TYPE = Type.getType(Throwable.class);
1002    private final Method INITIALIZING_METHOD = new Method("initializing", "(Ljava/lang/Object;)Ljava/lang/Object;");
1003    private final Method METHOD_INVOKED_METHOD = new Method("methodInvoked", "(Ljava/lang/String;ZLjava/lang/Class;)L" + PLAN_TYPE.getInternalName() + ";");
1004    private final Method PLAN_RUN_METHOD = new Method("run", OBJECT_TYPE, new Type[]{OBJECT_TYPE, OBJECT_TYPE, Type.getType(Object[].class)});
1005    private final Method HANDLE_EXCEPTION_METHOD = new Method("cleanStackTrace", THROWABLE_TYPE, new Type[]{THROWABLE_TYPE});
1006    private final String DIRECT_OBJECT_MARKER_TYPE_DESC = Type.getObjectType(DirectObjectMarker.class.getName().replace('.', '/')).getDescriptor();
1007    private final Type ROBOLECTRIC_INTERNALS_TYPE = Type.getType(RobolectricInternals.class);
1008
1009    public OldClassInstrumentor(ClassNode classNode, boolean containsStubs) {
1010      super(classNode, containsStubs);
1011    }
1012
1013    @Override
1014    protected void addDirectCallConstructor() {
1015      MethodNode directCallConstructor = new MethodNode(ACC_PUBLIC,
1016          "<init>", "(" + DIRECT_OBJECT_MARKER_TYPE_DESC + classType.getDescriptor() + ")V", null, null);
1017      RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(directCallConstructor);
1018      generator.loadThis();
1019      if (classNode.superName.equals("java/lang/Object")) {
1020        generator.visitMethodInsn(INVOKESPECIAL, classNode.superName, "<init>", "()V", false);
1021      } else {
1022        generator.loadArgs();
1023        generator.visitMethodInsn(INVOKESPECIAL, classNode.superName,
1024            "<init>", "(" + DIRECT_OBJECT_MARKER_TYPE_DESC + "L" + classNode.superName + ";)V", false);
1025      }
1026      generator.loadThis();
1027      generator.loadArg(1);
1028      generator.putField(classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);
1029      generator.returnValue();
1030      classNode.methods.add(directCallConstructor);
1031    }
1032
1033    @Override
1034    protected void writeCallToInitializing(RobolectricGeneratorAdapter generator) {
1035      generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, INITIALIZING_METHOD);
1036    }
1037
1038    @Override
1039    protected void generateShadowCall(MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator) {
1040      generateCallToClassHandler(originalMethod, originalMethodName, generator);
1041    }
1042
1043    //TODO clean up & javadocs
1044    private void generateCallToClassHandler(MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator) {
1045      int planLocalVar = generator.newLocal(PLAN_TYPE);
1046      int exceptionLocalVar = generator.newLocal(THROWABLE_TYPE);
1047      Label directCall = new Label();
1048      Label doReturn = new Label();
1049
1050      boolean isNormalInstanceMethod = !generator.isStatic && !originalMethodName.equals(ShadowConstants.CONSTRUCTOR_METHOD_NAME);
1051
1052      // maybe perform proxy call...
1053      if (isNormalInstanceMethod) {
1054        Label notInstanceOfThis = new Label();
1055
1056        generator.loadThis();                                         // this
1057        generator.getField(classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);  // contents of __robo_data__
1058        generator.instanceOf(classType);                              // __robo_data__, is instance of same class?
1059        generator.visitJumpInsn(IFEQ, notInstanceOfThis);             // jump if no (is not instance)
1060
1061        TryCatch tryCatchForProxyCall = generator.tryStart(THROWABLE_TYPE);
1062        generator.loadThis();                                         // this
1063        generator.getField(classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);  // contents of __robo_data__
1064        generator.checkCast(classType);                               // __robo_data__ but cast to my class
1065        generator.loadArgs();                                         // __robo_data__ instance, [args]
1066
1067        generator.visitMethodInsn(INVOKESPECIAL, internalClassName, originalMethod.name, originalMethod.desc, false);
1068        tryCatchForProxyCall.end();
1069
1070        generator.returnValue();
1071
1072        // catch(Throwable)
1073        tryCatchForProxyCall.handler();
1074        generator.storeLocal(exceptionLocalVar);
1075        generator.loadLocal(exceptionLocalVar);
1076        generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, HANDLE_EXCEPTION_METHOD);
1077        generator.throwException();
1078
1079        // callClassHandler...
1080        generator.mark(notInstanceOfThis);
1081      }
1082
1083      // prepare for call to classHandler.methodInvoked(String signature, boolean isStatic)
1084      generator.push(classType.getInternalName() + "/" + originalMethodName + originalMethod.desc);
1085      generator.push(generator.isStatic());
1086      generator.push(classType);                                         // my class
1087      generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, METHOD_INVOKED_METHOD);
1088      generator.storeLocal(planLocalVar);
1089
1090      generator.loadLocal(planLocalVar); // plan
1091      generator.ifNull(directCall);
1092
1093      // prepare for call to plan.run(Object instance, Object[] params)
1094      TryCatch tryCatchForHandler = generator.tryStart(THROWABLE_TYPE);
1095      generator.loadLocal(planLocalVar); // plan
1096      generator.loadThisOrNull();        // instance
1097      if (generator.isStatic()) {        // roboData
1098        generator.loadNull();
1099      } else {
1100        generator.loadThis();
1101        generator.invokeVirtual(classType, new Method(ShadowConstants.GET_ROBO_DATA_METHOD_NAME, GET_ROBO_DATA_SIGNATURE));
1102      }
1103      generator.loadArgArray();          // params
1104      generator.invokeInterface(PLAN_TYPE, PLAN_RUN_METHOD);
1105
1106      Type returnType = generator.getReturnType();
1107      int sort = returnType.getSort();
1108      switch (sort) {
1109        case VOID:
1110          generator.pop();
1111          break;
1112        case OBJECT:
1113          /* falls through */
1114        case ARRAY:
1115          generator.checkCast(returnType);
1116          break;
1117        default:
1118          int unboxLocalVar = generator.newLocal(OBJECT_TYPE);
1119          generator.storeLocal(unboxLocalVar);
1120          generator.loadLocal(unboxLocalVar);
1121          Label notNull = generator.newLabel();
1122          Label afterward = generator.newLabel();
1123          generator.ifNonNull(notNull);
1124          generator.pushDefaultReturnValueToStack(returnType); // return zero, false, whatever
1125          generator.goTo(afterward);
1126
1127          generator.mark(notNull);
1128          generator.loadLocal(unboxLocalVar);
1129          generator.unbox(returnType);
1130          generator.mark(afterward);
1131          break;
1132      }
1133      tryCatchForHandler.end();
1134      generator.goTo(doReturn);
1135
1136      // catch(Throwable)
1137      tryCatchForHandler.handler();
1138      generator.storeLocal(exceptionLocalVar);
1139      generator.loadLocal(exceptionLocalVar);
1140      generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, HANDLE_EXCEPTION_METHOD);
1141      generator.throwException();
1142
1143
1144      if (!originalMethod.name.equals("<init>")) {
1145        generator.mark(directCall);
1146        TryCatch tryCatchForDirect = generator.tryStart(THROWABLE_TYPE);
1147        generator.invokeMethod(classType.getInternalName(), originalMethod.name, originalMethod.desc);
1148        tryCatchForDirect.end();
1149        generator.returnValue();
1150
1151        // catch(Throwable)
1152        tryCatchForDirect.handler();
1153        generator.storeLocal(exceptionLocalVar);
1154        generator.loadLocal(exceptionLocalVar);
1155        generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, HANDLE_EXCEPTION_METHOD);
1156        generator.throwException();
1157      }
1158
1159      generator.mark(doReturn);
1160      generator.returnValue();
1161    }
1162
1163    /**
1164     * Decides to call through the appropriate method to intercept the method with an INVOKEVIRTUAL Opcode,
1165     * depending if the invokedynamic bytecode instruction is available (Java 7+)
1166     */
1167    @Override
1168    protected void interceptInvokeVirtualMethod(ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
1169      interceptInvokeVirtualMethodWithoutInvokeDynamic(instructions, targetMethod);
1170    }
1171
1172    /**
1173     * Intercepts the method without using the invokedynamic bytecode instruction.
1174     * Should be called through interceptInvokeVirtualMethod, not directly
1175     */
1176    private void interceptInvokeVirtualMethodWithoutInvokeDynamic(ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
1177      boolean isStatic = targetMethod.getOpcode() == INVOKESTATIC;
1178
1179      instructions.remove(); // remove the method invocation
1180
1181      Type[] argumentTypes = Type.getArgumentTypes(targetMethod.desc);
1182
1183      instructions.add(new LdcInsnNode(argumentTypes.length));
1184      instructions.add(new TypeInsnNode(ANEWARRAY, "java/lang/Object"));
1185
1186      // first, move any arguments into an Object[] in reverse order
1187      for (int i = argumentTypes.length - 1; i >= 0; i--) {
1188        Type type = argumentTypes[i];
1189        int argWidth = type.getSize();
1190
1191        if (argWidth == 1) {                       // A B C []
1192          instructions.add(new InsnNode(DUP_X1));  // A B [] C []
1193          instructions.add(new InsnNode(SWAP));    // A B [] [] C
1194          instructions.add(new LdcInsnNode(i));    // A B [] [] C 2
1195          instructions.add(new InsnNode(SWAP));    // A B [] [] 2 C
1196          box(type, instructions);                 // A B [] [] 2 (C)
1197          instructions.add(new InsnNode(AASTORE)); // A B [(C)]
1198        } else if (argWidth == 2) {                // A B _C_ []
1199          instructions.add(new InsnNode(DUP_X2));  // A B [] _C_ []
1200          instructions.add(new InsnNode(DUP_X2));  // A B [] [] _C_ []
1201          instructions.add(new InsnNode(POP));     // A B [] [] _C_
1202          box(type, instructions);                 // A B [] [] (C)
1203          instructions.add(new LdcInsnNode(i));    // A B [] [] (C) 2
1204          instructions.add(new InsnNode(SWAP));    // A B [] [] 2 (C)
1205          instructions.add(new InsnNode(AASTORE)); // A B [(C)]
1206        }
1207      }
1208
1209      if (isStatic) { // []
1210        instructions.add(new InsnNode(Opcodes.ACONST_NULL)); // [] null
1211        instructions.add(new InsnNode(Opcodes.SWAP));        // null []
1212      }
1213
1214      // instance []
1215      instructions.add(new LdcInsnNode(targetMethod.owner + "/" + targetMethod.name + targetMethod.desc)); // target method signature
1216      // instance [] signature
1217      instructions.add(new InsnNode(DUP_X2));       // signature instance [] signature
1218      instructions.add(new InsnNode(POP));          // signature instance []
1219
1220      instructions.add(new LdcInsnNode(classType)); // signature instance [] class
1221      instructions.add(new MethodInsnNode(INVOKESTATIC,
1222          Type.getType(RobolectricInternals.class).getInternalName(), "intercept",
1223          "(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;",
1224          false));
1225
1226      final Type returnType = Type.getReturnType(targetMethod.desc);
1227      switch (returnType.getSort()) {
1228        case ARRAY:
1229          /* falls through */
1230        case OBJECT:
1231          instructions.add(new TypeInsnNode(CHECKCAST, remapType(returnType.getInternalName())));
1232          break;
1233        case VOID:
1234          instructions.add(new InsnNode(POP));
1235          break;
1236        case Type.LONG:
1237          instructions.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Long.class)));
1238          instructions.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(Long.class), "longValue", Type.getMethodDescriptor(Type.LONG_TYPE), false));
1239          break;
1240        case Type.FLOAT:
1241          instructions.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Float.class)));
1242          instructions.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(Float.class), "floatValue", Type.getMethodDescriptor(Type.FLOAT_TYPE), false));
1243          break;
1244        case Type.DOUBLE:
1245          instructions.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Double.class)));
1246          instructions.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(Double.class), "doubleValue", Type.getMethodDescriptor(Type.DOUBLE_TYPE), false));
1247          break;
1248        case Type.BOOLEAN:
1249          instructions.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Boolean.class)));
1250          instructions.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(Boolean.class), "booleanValue", Type.getMethodDescriptor(Type.BOOLEAN_TYPE), false));
1251          break;
1252        case Type.INT:
1253          instructions.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Integer.class)));
1254          instructions.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", Type.getMethodDescriptor(Type.INT_TYPE), false));
1255          break;
1256        case Type.SHORT:
1257          instructions.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Short.class)));
1258          instructions.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(Short.class), "shortValue", Type.getMethodDescriptor(Type.SHORT_TYPE), false));
1259          break;
1260        case Type.BYTE:
1261          instructions.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Byte.class)));
1262          instructions.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(Byte.class), "byteValue", Type.getMethodDescriptor(Type.BYTE_TYPE), false));
1263          break;
1264        default:
1265          throw new RuntimeException("Not implemented: " + getClass().getName() + " cannot intercept methods with return type " + returnType.getClassName());
1266      }
1267    }
1268  }
1269
1270  public class InvokeDynamicClassInstrumentor extends SandboxClassLoader.ClassInstrumentor {
1271    private final Handle BOOTSTRAP_INIT;
1272    private final Handle BOOTSTRAP;
1273    private final Handle BOOTSTRAP_STATIC;
1274    private final Handle BOOTSTRAP_INTRINSIC;
1275
1276    public InvokeDynamicClassInstrumentor(ClassNode classNode, boolean containsStubs) {
1277      super(classNode, containsStubs);
1278
1279      String className = Type.getInternalName(InvokeDynamicSupport.class);
1280
1281      MethodType bootstrap =
1282          methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
1283      String bootstrapMethod =
1284          bootstrap.appendParameterTypes(MethodHandle.class).toMethodDescriptorString();
1285      String bootstrapIntrinsic =
1286          bootstrap.appendParameterTypes(String.class).toMethodDescriptorString();
1287
1288      BOOTSTRAP_INIT = new Handle(H_INVOKESTATIC, className, "bootstrapInit", bootstrap.toMethodDescriptorString());
1289      BOOTSTRAP = new Handle(H_INVOKESTATIC, className, "bootstrap", bootstrapMethod);
1290      BOOTSTRAP_STATIC = new Handle(H_INVOKESTATIC, className, "bootstrapStatic", bootstrapMethod);
1291      BOOTSTRAP_INTRINSIC = new Handle(H_INVOKESTATIC, className, "bootstrapIntrinsic", bootstrapIntrinsic);
1292    }
1293
1294    @Override
1295    protected void addDirectCallConstructor() {
1296      // not needed, for reasons.
1297    }
1298
1299    @Override
1300    protected void writeCallToInitializing(RobolectricGeneratorAdapter generator) {
1301      generator.invokeDynamic("initializing", Type.getMethodDescriptor(OBJECT_TYPE, classType), BOOTSTRAP_INIT);
1302    }
1303
1304    @Override
1305    protected void generateShadowCall(MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator) {
1306      generateInvokeDynamic(originalMethod, originalMethodName, generator);
1307    }
1308
1309    // todo javadocs
1310    private void generateInvokeDynamic(MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator) {
1311      Handle original =
1312          new Handle(getTag(originalMethod), classType.getInternalName(), originalMethod.name,
1313              originalMethod.desc);
1314
1315      if (generator.isStatic()) {
1316        generator.loadArgs();
1317        generator.invokeDynamic(originalMethodName, originalMethod.desc, BOOTSTRAP_STATIC, original);
1318      } else {
1319        String desc = "(" + classType.getDescriptor() + originalMethod.desc.substring(1);
1320        generator.loadThis();
1321        generator.loadArgs();
1322        generator.invokeDynamic(originalMethodName, desc, BOOTSTRAP, original);
1323      }
1324
1325      generator.returnValue();
1326    }
1327
1328    @Override
1329    protected void interceptInvokeVirtualMethod(ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
1330      interceptInvokeVirtualMethodWithInvokeDynamic(instructions, targetMethod);
1331    }
1332
1333    /**
1334     * Intercepts the method using the invokedynamic bytecode instruction available in Java 7+.
1335     * Should be called through interceptInvokeVirtualMethod, not directly
1336     */
1337    private void interceptInvokeVirtualMethodWithInvokeDynamic(ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
1338      instructions.remove();  // remove the method invocation
1339
1340      Type type = Type.getObjectType(targetMethod.owner);
1341      String description = targetMethod.desc;
1342      String owner = type.getClassName();
1343
1344      if (targetMethod.getOpcode() != INVOKESTATIC) {
1345        String thisType = type.getDescriptor();
1346        description = "(" + thisType + description.substring(1, description.length());
1347      }
1348
1349      instructions.add(new InvokeDynamicInsnNode(targetMethod.name, description, BOOTSTRAP_INTRINSIC, owner));
1350    }
1351  }
1352}
1353