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