Home | History | Annotate | Download | only in bytecode
      1 package org.robolectric.internal.bytecode;
      2 
      3 import static org.objectweb.asm.Type.ARRAY;
      4 import static org.objectweb.asm.Type.OBJECT;
      5 import static org.objectweb.asm.Type.VOID;
      6 
      7 import java.util.ListIterator;
      8 import org.objectweb.asm.Label;
      9 import org.objectweb.asm.Opcodes;
     10 import org.objectweb.asm.Type;
     11 import org.objectweb.asm.commons.Method;
     12 import org.objectweb.asm.tree.AbstractInsnNode;
     13 import org.objectweb.asm.tree.InsnNode;
     14 import org.objectweb.asm.tree.LdcInsnNode;
     15 import org.objectweb.asm.tree.MethodInsnNode;
     16 import org.objectweb.asm.tree.MethodNode;
     17 import org.objectweb.asm.tree.TypeInsnNode;
     18 
     19 public class OldClassInstrumentor extends ClassInstrumentor {
     20   private static final Type PLAN_TYPE = Type.getType(ClassHandler.Plan.class);
     21   static final Type THROWABLE_TYPE = Type.getType(Throwable.class);
     22   static final Type ROBOLECTRIC_INTERNALS_TYPE = Type.getType(RobolectricInternals.class);
     23   private static final Method INITIALIZING_METHOD = new Method("initializing", "(Ljava/lang/Object;)Ljava/lang/Object;");
     24   private static final Method METHOD_INVOKED_METHOD = new Method("methodInvoked", "(Ljava/lang/String;ZLjava/lang/Class;)L" + PLAN_TYPE.getInternalName() + ";");
     25   private static final Method PLAN_RUN_METHOD = new Method("run", OBJECT_TYPE, new Type[]{OBJECT_TYPE, Type.getType(Object[].class)});
     26   static final Method HANDLE_EXCEPTION_METHOD = new Method("cleanStackTrace", THROWABLE_TYPE, new Type[]{THROWABLE_TYPE});
     27   private static final String DIRECT_OBJECT_MARKER_TYPE_DESC = Type.getObjectType(DirectObjectMarker.class.getName().replace('.', '/')).getDescriptor();
     28 
     29   public OldClassInstrumentor(ClassInstrumentor.Decorator decorator) {
     30     super(decorator);
     31   }
     32 
     33   /**
     34    * Generates code like this:
     35    * ```java
     36    * public ThisClass(DirectObjectMarker dom, ThisClass domInstance) {
     37    *   super(dom, domInstance);
     38    *   __robo_data__ = domInstance;
     39    * }
     40    * ```
     41    */
     42   @Override
     43   protected void addDirectCallConstructor(MutableClass mutableClass) {
     44     MethodNode directCallConstructor = new MethodNode(Opcodes.ACC_PUBLIC,
     45         "<init>", "(" + DIRECT_OBJECT_MARKER_TYPE_DESC + mutableClass.classType.getDescriptor() + ")V", null, null);
     46     RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(directCallConstructor);
     47     generator.loadThis();
     48     String superName = mutableClass.classNode.superName;
     49     if (superName.equals("java/lang/Object")) {
     50       generator.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "<init>", "()V", false);
     51     } else {
     52       generator.loadArgs();
     53       generator.visitMethodInsn(Opcodes.INVOKESPECIAL, superName,
     54           "<init>", "(" + DIRECT_OBJECT_MARKER_TYPE_DESC + "L" + superName + ";)V", false);
     55     }
     56     generator.loadThis();
     57     generator.loadArg(1);
     58     generator.putField(mutableClass.classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);
     59     generator.returnValue();
     60     mutableClass.addMethod(directCallConstructor);
     61   }
     62 
     63   @Override
     64   protected void writeCallToInitializing(MutableClass mutableClass,
     65       RobolectricGeneratorAdapter generator) {
     66     generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, INITIALIZING_METHOD);
     67   }
     68 
     69   @Override
     70   protected void generateClassHandlerCall(MutableClass mutableClass, MethodNode originalMethod,
     71       String originalMethodName, RobolectricGeneratorAdapter generator) {
     72     generateCallToClassHandler(mutableClass, originalMethod, originalMethodName, generator);
     73   }
     74 
     75   /**
     76    * Generates codelike this:
     77    * ```java
     78    * // decorator-specific code...
     79    *
     80    * Plan plan = RobolectricInternals.methodInvoked(
     81    *     "pkg/ThisClass/thisMethod(Ljava/lang/String;Z)V", isStatic, ThisClass.class);
     82    * if (plan != null) {
     83    *   try {
     84    *     return plan.run(this, args);
     85    *   } catch (Throwable t) {
     86    *     throw RobolectricInternals.cleanStackTrace(t);
     87    *   }
     88    * } else {
     89    *   return $$robo$$thisMethod(*args);
     90    * }
     91    * ```
     92    */
     93   private void generateCallToClassHandler(MutableClass mutableClass, MethodNode originalMethod,
     94       String originalMethodName, RobolectricGeneratorAdapter generator) {
     95     decorator.decorateMethodPreClassHandler(mutableClass, originalMethod, originalMethodName, generator);
     96 
     97     int planLocalVar = generator.newLocal(PLAN_TYPE);
     98     int exceptionLocalVar = generator.newLocal(THROWABLE_TYPE);
     99     Label directCall = new Label();
    100     Label doReturn = new Label();
    101 
    102     // prepare for call to classHandler.methodInvoked(String signature, boolean isStatic)
    103     generator.push(mutableClass.classType.getInternalName() + "/" + originalMethodName + originalMethod.desc);
    104     generator.push(generator.isStatic());
    105     generator.push(mutableClass.classType);                                         // my class
    106     generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, METHOD_INVOKED_METHOD);
    107     generator.storeLocal(planLocalVar);
    108 
    109     generator.loadLocal(planLocalVar); // plan
    110     generator.ifNull(directCall);
    111 
    112     // prepare for call to plan.run(Object instance, Object[] params)
    113     TryCatch tryCatchForHandler = generator.tryStart(THROWABLE_TYPE);
    114     generator.loadLocal(planLocalVar); // plan
    115     generator.loadThisOrNull();        // instance
    116     generator.loadArgArray();          // params
    117     generator.invokeInterface(PLAN_TYPE, PLAN_RUN_METHOD);
    118 
    119     Type returnType = generator.getReturnType();
    120     int sort = returnType.getSort();
    121     switch (sort) {
    122       case VOID:
    123         generator.pop();
    124         break;
    125       case OBJECT:
    126         /* falls through */
    127       case ARRAY:
    128         generator.checkCast(returnType);
    129         break;
    130       default:
    131         int unboxLocalVar = generator.newLocal(OBJECT_TYPE);
    132         generator.storeLocal(unboxLocalVar);
    133         generator.loadLocal(unboxLocalVar);
    134         Label notNull = generator.newLabel();
    135         Label afterward = generator.newLabel();
    136         generator.ifNonNull(notNull);
    137         generator.pushDefaultReturnValueToStack(returnType); // return zero, false, whatever
    138         generator.goTo(afterward);
    139 
    140         generator.mark(notNull);
    141         generator.loadLocal(unboxLocalVar);
    142         generator.unbox(returnType);
    143         generator.mark(afterward);
    144         break;
    145     }
    146     tryCatchForHandler.end();
    147     generator.goTo(doReturn);
    148 
    149     // catch(Throwable)
    150     tryCatchForHandler.handler();
    151     generator.storeLocal(exceptionLocalVar);
    152     generator.loadLocal(exceptionLocalVar);
    153     generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, HANDLE_EXCEPTION_METHOD);
    154     generator.throwException();
    155 
    156 
    157     if (!originalMethod.name.equals("<init>")) {
    158       generator.mark(directCall);
    159       TryCatch tryCatchForDirect = generator.tryStart(THROWABLE_TYPE);
    160       generator.invokeMethod(mutableClass.classType.getInternalName(), originalMethod.name, originalMethod.desc);
    161       tryCatchForDirect.end();
    162       generator.returnValue();
    163 
    164       // catch(Throwable)
    165       tryCatchForDirect.handler();
    166       generator.storeLocal(exceptionLocalVar);
    167       generator.loadLocal(exceptionLocalVar);
    168       generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, HANDLE_EXCEPTION_METHOD);
    169       generator.throwException();
    170     }
    171 
    172     generator.mark(doReturn);
    173     generator.returnValue();
    174   }
    175 
    176   /**
    177    * Decides to call through the appropriate method to intercept the method with an INVOKEVIRTUAL
    178    * Opcode, depending if the invokedynamic bytecode instruction is available (Java 7+).
    179    */
    180   @Override
    181   protected void interceptInvokeVirtualMethod(MutableClass mutableClass,
    182       ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
    183     interceptInvokeVirtualMethodWithoutInvokeDynamic(mutableClass, instructions, targetMethod);
    184   }
    185 
    186   /**
    187    * Intercepts the method without using the invokedynamic bytecode instruction.
    188    * Should be called through interceptInvokeVirtualMethod, not directly.
    189    */
    190   private void interceptInvokeVirtualMethodWithoutInvokeDynamic(MutableClass mutableClass,
    191       ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
    192     boolean isStatic = targetMethod.getOpcode() == Opcodes.INVOKESTATIC;
    193 
    194     instructions.remove(); // remove the method invocation
    195 
    196     Type[] argumentTypes = Type.getArgumentTypes(targetMethod.desc);
    197 
    198     instructions.add(new LdcInsnNode(argumentTypes.length));
    199     instructions.add(new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object"));
    200 
    201     // first, move any arguments into an Object[] in reverse order
    202     for (int i = argumentTypes.length - 1; i >= 0; i--) {
    203       Type type = argumentTypes[i];
    204       int argWidth = type.getSize();
    205 
    206       if (argWidth == 1) {                               // A B C []
    207         instructions.add(new InsnNode(Opcodes.DUP_X1));  // A B [] C []
    208         instructions.add(new InsnNode(Opcodes.SWAP));    // A B [] [] C
    209         instructions.add(new LdcInsnNode(i));            // A B [] [] C 2
    210         instructions.add(new InsnNode(Opcodes.SWAP));    // A B [] [] 2 C
    211         box(type, instructions);                         // A B [] [] 2 (C)
    212         instructions.add(new InsnNode(Opcodes.AASTORE)); // A B [(C)]
    213       } else if (argWidth == 2) {                        // A B _C_ []
    214         instructions.add(new InsnNode(Opcodes.DUP_X2));  // A B [] _C_ []
    215         instructions.add(new InsnNode(Opcodes.DUP_X2));  // A B [] [] _C_ []
    216         instructions.add(new InsnNode(Opcodes.POP));     // A B [] [] _C_
    217         box(type, instructions);                         // A B [] [] (C)
    218         instructions.add(new LdcInsnNode(i));            // A B [] [] (C) 2
    219         instructions.add(new InsnNode(Opcodes.SWAP));    // A B [] [] 2 (C)
    220         instructions.add(new InsnNode(Opcodes.AASTORE)); // A B [(C)]
    221       }
    222     }
    223 
    224     if (isStatic) { // []
    225       instructions.add(new InsnNode(Opcodes.ACONST_NULL)); // [] null
    226       instructions.add(new InsnNode(Opcodes.SWAP));        // null []
    227     }
    228 
    229     // instance []
    230     instructions.add(new LdcInsnNode(targetMethod.owner + "/" + targetMethod.name + targetMethod.desc)); // target method signature
    231     // instance [] signature
    232     instructions.add(new InsnNode(Opcodes.DUP_X2));       // signature instance [] signature
    233     instructions.add(new InsnNode(Opcodes.POP));          // signature instance []
    234 
    235     instructions.add(new LdcInsnNode(mutableClass.classType)); // signature instance [] class
    236     instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
    237         Type.getType(RobolectricInternals.class).getInternalName(), "intercept",
    238         "(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;",
    239         false));
    240 
    241     final Type returnType = Type.getReturnType(targetMethod.desc);
    242     switch (returnType.getSort()) {
    243       case ARRAY:
    244         /* falls through */
    245       case OBJECT:
    246         String remappedType = mutableClass.config.mappedTypeName(returnType.getInternalName());
    247         instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, remappedType));
    248         break;
    249       case VOID:
    250         instructions.add(new InsnNode(Opcodes.POP));
    251         break;
    252       case Type.LONG:
    253         instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(Long.class)));
    254         instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Long.class), "longValue", Type.getMethodDescriptor(Type.LONG_TYPE), false));
    255         break;
    256       case Type.FLOAT:
    257         instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(Float.class)));
    258         instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Float.class), "floatValue", Type.getMethodDescriptor(Type.FLOAT_TYPE), false));
    259         break;
    260       case Type.DOUBLE:
    261         instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(Double.class)));
    262         instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Double.class), "doubleValue", Type.getMethodDescriptor(Type.DOUBLE_TYPE), false));
    263         break;
    264       case Type.BOOLEAN:
    265         instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(Boolean.class)));
    266         instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Boolean.class), "booleanValue", Type.getMethodDescriptor(Type.BOOLEAN_TYPE), false));
    267         break;
    268       case Type.INT:
    269         instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(Integer.class)));
    270         instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", Type.getMethodDescriptor(Type.INT_TYPE), false));
    271         break;
    272       case Type.SHORT:
    273         instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(Short.class)));
    274         instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Short.class), "shortValue", Type.getMethodDescriptor(Type.SHORT_TYPE), false));
    275         break;
    276       case Type.BYTE:
    277         instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(Byte.class)));
    278         instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Byte.class), "byteValue", Type.getMethodDescriptor(Type.BYTE_TYPE), false));
    279         break;
    280       default:
    281         throw new RuntimeException("Not implemented: " + getClass().getName() + " cannot intercept methods with return type " + returnType.getClassName());
    282     }
    283   }
    284 
    285   static void box(final Type type, ListIterator<AbstractInsnNode> instructions) {
    286     if (type.getSort() == OBJECT || type.getSort() == ARRAY) {
    287       return;
    288     }
    289 
    290     if (Type.VOID_TYPE.equals(type)) {
    291       instructions.add(new InsnNode(Opcodes.ACONST_NULL));
    292     } else {
    293       Type boxed = getBoxedType(type);
    294       instructions.add(new TypeInsnNode(Opcodes.NEW, boxed.getInternalName()));
    295       if (type.getSize() == 2) {
    296         // Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
    297         instructions.add(new InsnNode(Opcodes.DUP_X2));
    298         instructions.add(new InsnNode(Opcodes.DUP_X2));
    299         instructions.add(new InsnNode(Opcodes.POP));
    300       } else {
    301         // p -> po -> opo -> oop -> o
    302         instructions.add(new InsnNode(Opcodes.DUP_X1));
    303         instructions.add(new InsnNode(Opcodes.SWAP));
    304       }
    305       instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, boxed.getInternalName(),
    306           "<init>", "(" + type.getDescriptor() + ")V", false));
    307     }
    308   }
    309 
    310   private static Type getBoxedType(final Type type) {
    311     switch (type.getSort()) {
    312       case Type.BYTE:
    313         return Type.getObjectType("java/lang/Byte");
    314       case Type.BOOLEAN:
    315         return Type.getObjectType("java/lang/Boolean");
    316       case Type.SHORT:
    317         return Type.getObjectType("java/lang/Short");
    318       case Type.CHAR:
    319         return Type.getObjectType("java/lang/Character");
    320       case Type.INT:
    321         return Type.getObjectType("java/lang/Integer");
    322       case Type.FLOAT:
    323         return Type.getObjectType("java/lang/Float");
    324       case Type.LONG:
    325         return Type.getObjectType("java/lang/Long");
    326       case Type.DOUBLE:
    327         return Type.getObjectType("java/lang/Double");
    328       default:
    329         // no boxing required
    330         return type;
    331     }
    332   }
    333 }
    334