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