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