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