• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
OldClassInstrumentor(ClassInstrumentor.Decorator decorator)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
addDirectCallConstructor(MutableClass mutableClass)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
writeCallToInitializing(MutableClass mutableClass, RobolectricGeneratorAdapter generator)64   protected void writeCallToInitializing(MutableClass mutableClass,
65       RobolectricGeneratorAdapter generator) {
66     generator.invokeStatic(ROBOLECTRIC_INTERNALS_TYPE, INITIALIZING_METHOD);
67   }
68 
69   @Override
generateClassHandlerCall(MutableClass mutableClass, MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator)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    */
generateCallToClassHandler(MutableClass mutableClass, MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator)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
interceptInvokeVirtualMethod(MutableClass mutableClass, ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod)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    */
interceptInvokeVirtualMethodWithoutInvokeDynamic(MutableClass mutableClass, ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod)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 
box(final Type type, ListIterator<AbstractInsnNode> instructions)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 
getBoxedType(final Type type)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