
org.robolectric.internal.bytecode.ClassInstrumentor Maven / Gradle / Ivy
package org.robolectric.internal.bytecode;
import static java.lang.invoke.MethodType.methodType;
import com.google.common.collect.Iterables;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.JSRInlinerAdapter;
import org.objectweb.asm.commons.Method;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.robolectric.util.PerfStatsCollector;
/**
* Instruments (i.e. modifies the bytecode) of classes to place the scaffolding necessary to use
* Robolectric's shadows.
*/
public class ClassInstrumentor {
private static final Handle BOOTSTRAP_INIT;
private static final Handle BOOTSTRAP;
private static final Handle BOOTSTRAP_STATIC;
private static final Handle BOOTSTRAP_INTRINSIC;
private static final String ROBO_INIT_METHOD_NAME = "$$robo$init";
protected static final Type OBJECT_TYPE = Type.getType(Object.class);
private static final ShadowImpl SHADOW_IMPL = new ShadowImpl();
final Decorator decorator;
static {
String className = Type.getInternalName(InvokeDynamicSupport.class);
MethodType bootstrap =
methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
/*
* There is an additional int.class argument to the invokedynamic bootstrap method. This conveys
* whether or not the method invocation represents a native method. A one means the original
* method was a native method, and a zero means it was not. It should be boolean.class, but
* that is nt possible due to https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8322510.
*/
String bootstrapMethod =
bootstrap
.appendParameterTypes(MethodHandle.class, /* isNative */ int.class)
.toMethodDescriptorString();
String bootstrapIntrinsic =
bootstrap.appendParameterTypes(String.class).toMethodDescriptorString();
BOOTSTRAP_INIT =
new Handle(
Opcodes.H_INVOKESTATIC,
className,
"bootstrapInit",
bootstrap.toMethodDescriptorString(),
false);
BOOTSTRAP = new Handle(Opcodes.H_INVOKESTATIC, className, "bootstrap", bootstrapMethod, false);
BOOTSTRAP_STATIC =
new Handle(Opcodes.H_INVOKESTATIC, className, "bootstrapStatic", bootstrapMethod, false);
BOOTSTRAP_INTRINSIC =
new Handle(
Opcodes.H_INVOKESTATIC, className, "bootstrapIntrinsic", bootstrapIntrinsic, false);
}
public ClassInstrumentor() {
this(new ShadowDecorator());
}
protected ClassInstrumentor(Decorator decorator) {
this.decorator = decorator;
}
private MutableClass analyzeClass(
byte[] origClassBytes,
final InstrumentationConfiguration config,
ClassNodeProvider classNodeProvider) {
ClassNode classNode =
new ClassNode(Opcodes.ASM4) {
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor methodVisitor =
super.visitMethod(access, name, config.remapParams(desc), signature, exceptions);
return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions);
}
};
final ClassReader classReader = new ClassReader(origClassBytes);
classReader.accept(classNode, 0);
return new MutableClass(classNode, config, classNodeProvider);
}
byte[] instrumentToBytes(MutableClass mutableClass) {
instrument(mutableClass);
ClassNode classNode = mutableClass.classNode;
ClassWriter writer = new InstrumentingClassWriter(mutableClass.classNodeProvider, classNode);
Remapper remapper =
new Remapper() {
@Override
public String map(final String internalName) {
return mutableClass.config.mappedTypeName(internalName);
}
};
ClassRemapper visitor = new ClassRemapper(writer, remapper);
classNode.accept(visitor);
return writer.toByteArray();
}
public byte[] instrument(
ClassDetails classDetails,
InstrumentationConfiguration config,
ClassNodeProvider classNodeProvider) {
PerfStatsCollector perfStats = PerfStatsCollector.getInstance();
MutableClass mutableClass =
perfStats.measure(
"analyze class",
() -> analyzeClass(classDetails.getClassBytes(), config, classNodeProvider));
byte[] instrumentedBytes =
perfStats.measure("instrument class", () -> instrumentToBytes(mutableClass));
recordPackageStats(perfStats, mutableClass);
return instrumentedBytes;
}
private void recordPackageStats(PerfStatsCollector perfStats, MutableClass mutableClass) {
String className = mutableClass.getName();
for (int i = className.indexOf('.'); i != -1; i = className.indexOf('.', i + 1)) {
perfStats.incrementCount("instrument package " + className.substring(0, i));
}
}
public void instrument(MutableClass mutableClass) {
try {
// Need Java version >=7 to allow invokedynamic
mutableClass.classNode.version = Math.max(mutableClass.classNode.version, Opcodes.V1_7);
if (mutableClass.getName().equals("android.util.SparseArray")) {
addSetToSparseArray(mutableClass);
}
instrumentMethods(mutableClass);
if (mutableClass.isInterface()) {
mutableClass.addInterface(Type.getInternalName(InstrumentedInterface.class));
} else {
makeClassPublic(mutableClass.classNode);
if ((mutableClass.classNode.access & Opcodes.ACC_FINAL) == Opcodes.ACC_FINAL) {
mutableClass
.classNode
.visitAnnotation("Lcom/google/errorprone/annotations/DoNotMock;", true)
.visit(
"value",
"This class is final. Consider using the real thing, or "
+ "adding/enhancing a Robolectric shadow for it.");
}
mutableClass.classNode.access = mutableClass.classNode.access & ~Opcodes.ACC_FINAL;
// If there is no constructor, adds one
addNoArgsConstructor(mutableClass);
addRoboInitMethod(mutableClass);
removeFinalFromFields(mutableClass);
decorator.decorate(mutableClass);
}
} catch (Exception e) {
throw new RuntimeException("failed to instrument " + mutableClass.getName(), e);
}
}
// See https://github.com/robolectric/robolectric/issues/6840
// Adds Set(int, object) to android.util.SparseArray.
private void addSetToSparseArray(MutableClass mutableClass) {
for (MethodNode method : mutableClass.getMethods()) {
if ("set".equals(method.name)) {
return;
}
}
MethodNode setFunction =
new MethodNode(
Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
"set",
"(ILjava/lang/Object;)V",
"(ITE;)V",
null);
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(setFunction);
generator.loadThis();
generator.loadArg(0);
generator.loadArg(1);
generator.invokeVirtual(mutableClass.classType, new Method("put", "(ILjava/lang/Object;)V"));
generator.returnValue();
mutableClass.addMethod(setFunction);
}
/**
* Checks if the first or second instruction is a Jacoco load instruction. Robolectric is not
* capable at the moment of re-instrumenting Jacoco-instrumented constructors, so these are
* currently skipped.
*
* @param ctor constructor method node
* @return whether or not the constructor can be instrumented
*/
private boolean isJacocoInstrumented(MethodNode ctor) {
AbstractInsnNode[] insns = ctor.instructions.toArray();
if (insns.length > 1) {
AbstractInsnNode node = insns[0];
if (node instanceof LabelNode) {
node = insns[1];
}
if ((node instanceof LdcInsnNode && ((LdcInsnNode) node).cst instanceof ConstantDynamic)) {
ConstantDynamic cst = (ConstantDynamic) ((LdcInsnNode) node).cst;
return cst.getName().equals("$jacocoData");
} else if (node instanceof MethodInsnNode) {
return Objects.equals(((MethodInsnNode) node).name, "$jacocoInit");
}
}
return false;
}
/**
* Adds a call $$robo$init, which instantiates a shadow object if required. This is to support
* custom shadows for Jacoco-instrumented classes (except cnstructor shadows).
*/
protected void addCallToRoboInit(MutableClass mutableClass, MethodNode ctor) {
AbstractInsnNode returnNode =
Iterables.find(
ctor.instructions,
node -> {
if (node.getOpcode() == Opcodes.INVOKESPECIAL) {
MethodInsnNode mNode = (MethodInsnNode) node;
return (mNode.owner.equals(mutableClass.internalClassName)
|| mNode.owner.equals(mutableClass.classNode.superName));
}
return false;
},
null);
ctor.instructions.insert(
returnNode,
new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
mutableClass.classType.getInternalName(),
ROBO_INIT_METHOD_NAME,
"()V"));
ctor.instructions.insert(returnNode, new VarInsnNode(Opcodes.ALOAD, 0));
}
private void instrumentMethods(MutableClass mutableClass) {
if (mutableClass.isInterface()) {
for (MethodNode method : mutableClass.getMethods()) {
rewriteMethodBody(mutableClass, method);
}
} else {
for (MethodNode method : mutableClass.getMethods()) {
rewriteMethodBody(mutableClass, method);
if (method.name.equals("")) {
method.name = ShadowConstants.STATIC_INITIALIZER_METHOD_NAME;
mutableClass.addMethod(generateStaticInitializerNotifierMethod(mutableClass));
} else if (method.name.equals("")) {
if (isJacocoInstrumented(method)) {
addCallToRoboInit(mutableClass, method);
} else {
instrumentConstructor(mutableClass, method);
}
} else if (!isSyntheticAccessorMethod(method) && !Modifier.isAbstract(method.access)) {
instrumentNormalMethod(mutableClass, method);
}
}
}
}
private static void addNoArgsConstructor(MutableClass mutableClass) {
if (!mutableClass.foundMethods.contains("()V")) {
MethodNode defaultConstructor =
new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "", "()V", "()V", null);
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(defaultConstructor);
generator.loadThis();
generator.visitMethodInsn(
Opcodes.INVOKESPECIAL, mutableClass.classNode.superName, "", "()V", false);
generator.loadThis();
generator.invokeVirtual(mutableClass.classType, new Method(ROBO_INIT_METHOD_NAME, "()V"));
generator.returnValue();
mutableClass.addMethod(defaultConstructor);
}
}
/**
* Generates code like this:
*
*
* protected void $$robo$init() {
* if (__robo_data__ == null) {
* __robo_data__ = RobolectricInternals.initializing(this);
* }
* }
*
*/
private void addRoboInitMethod(MutableClass mutableClass) {
MethodNode initMethodNode =
new MethodNode(
Opcodes.ACC_PROTECTED | Opcodes.ACC_SYNTHETIC,
ROBO_INIT_METHOD_NAME,
"()V",
null,
null);
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(initMethodNode);
Label alreadyInitialized = new Label();
generator.loadThis(); // this
generator.getField(
mutableClass.classType,
ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME,
OBJECT_TYPE); // contents of __robo_data__
generator.ifNonNull(alreadyInitialized);
generator.loadThis(); // this
generator.loadThis(); // this, this
writeCallToInitializing(mutableClass, generator);
// this, __robo_data__
generator.putField(
mutableClass.classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);
generator.mark(alreadyInitialized);
generator.returnValue();
mutableClass.addMethod(initMethodNode);
}
protected void writeCallToInitializing(
MutableClass mutableClass, RobolectricGeneratorAdapter generator) {
generator.invokeDynamic(
"initializing",
Type.getMethodDescriptor(OBJECT_TYPE, mutableClass.classType),
BOOTSTRAP_INIT);
}
private static void removeFinalFromFields(MutableClass mutableClass) {
for (FieldNode fieldNode : mutableClass.getFields()) {
fieldNode.access &= ~Modifier.FINAL;
}
}
private static boolean isSyntheticAccessorMethod(MethodNode method) {
return (method.access & Opcodes.ACC_SYNTHETIC) != 0;
}
/**
* Constructors are instrumented as follows:
*
*
* - The original constructor will be stripped of its instructions leading up to, and
* including, the call to super() or this(). It is also renamed to $$robo$$__constructor__
*
- A method called __constructor__ is created and its job is to call
* $$robo$$__constructor__. The __constructor__ method is what gets shadowed if a Shadow
* wants to shadow a constructor.
*
- A new constructor is created and contains the stripped instructions of the original
* constructor leading up to, and including, the call to super() or this(). Then, it has a
* call to $$robo$init to initialize the Class' Shadow Object. Then, it uses invokedynamic
* to call __constructor__. Finally, it contains any instructions that might occur after the
* return statement in the original constructor.
*
*
* @param method the constructor to instrument
*/
protected void instrumentConstructor(MutableClass mutableClass, MethodNode method) {
int methodAccess = method.access;
makeMethodPrivate(method);
InsnList callSuper = extractCallToSuperConstructor(mutableClass, method);
method.name = directMethodName(mutableClass, ShadowConstants.CONSTRUCTOR_METHOD_NAME);
mutableClass.addMethod(
redirectorMethod(mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME));
String[] exceptions = exceptionArray(method);
MethodNode initMethodNode =
new MethodNode(methodAccess, "", method.desc, method.signature, exceptions);
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(initMethodNode);
initMethodNode.instructions.add(callSuper);
generator.loadThis();
generator.invokeVirtual(mutableClass.classType, new Method(ROBO_INIT_METHOD_NAME, "()V"));
generateClassHandlerCall(
mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME, generator, false);
generator.endMethod();
InsnList postamble = extractInstructionsAfterReturn(method, initMethodNode);
if (postamble.size() > 0) {
initMethodNode.instructions.add(postamble);
}
mutableClass.addMethod(initMethodNode);
}
/**
* Checks to see if there are instructions after RETURN. If there are, it will check to see if
* they belong in the call-to-super, or the shadowable part of the constructor.
*/
private InsnList extractInstructionsAfterReturn(MethodNode method, MethodNode initMethodNode) {
InsnList removedInstructions = new InsnList();
AbstractInsnNode returnNode =
Iterables.find(
method.instructions,
node -> node instanceof InsnNode && node.getOpcode() == Opcodes.RETURN,
null);
if (returnNode == null) {
return removedInstructions;
}
if (returnNode.getNext() instanceof LabelNode) {
// There are instructions after the return, check where they belong. Note this is a very rare
// edge case and only seems to happen with desugared+proguarded classes such as
// play-services-basement's ApiException.
LabelNode labelAfterReturn = (LabelNode) returnNode.getNext();
boolean inInitMethodNode =
Iterables.any(
initMethodNode.instructions,
input ->
input instanceof JumpInsnNode
&& ((JumpInsnNode) input).label == labelAfterReturn);
if (inInitMethodNode) {
while (returnNode.getNext() != null) {
AbstractInsnNode node = returnNode.getNext();
method.instructions.remove(node);
removedInstructions.add(node);
}
}
}
return removedInstructions;
}
private static InsnList extractCallToSuperConstructor(
MutableClass mutableClass, MethodNode ctor) {
InsnList removedInstructions = new InsnList();
// Start removing instructions at the beginning of the method. The first instructions of
// constructors may vary.
int startIndex = 0;
AbstractInsnNode[] insns = ctor.instructions.toArray();
for (int i = 0; i < insns.length; i++) {
AbstractInsnNode node = insns[i];
switch (node.getOpcode()) {
case Opcodes.INVOKESPECIAL:
MethodInsnNode mnode = (MethodInsnNode) node;
if (mnode.owner.equals(mutableClass.internalClassName)
|| mnode.owner.equals(mutableClass.classNode.superName)) {
if (!"".equals(mnode.name)) {
throw new AssertionError("Invalid MethodInsnNode name");
}
// remove all instructions in the range 0 (the start) to invokespecial
//
while (startIndex <= i) {
ctor.instructions.remove(insns[startIndex]);
removedInstructions.add(insns[startIndex]);
startIndex++;
}
return removedInstructions;
}
break;
case Opcodes.ATHROW:
ctor.visitCode();
ctor.visitInsn(Opcodes.RETURN);
ctor.visitEnd();
return removedInstructions;
default:
// nothing to do
}
}
throw new RuntimeException("huh? " + ctor.name + ctor.desc);
}
/**
* Instruments a normal method
*
*
* - Rename the method from {@code methodName} to {@code $$robo$$methodName}.
*
- Make it private so we can invoke it directly without subclass overrides taking
* precedence.
*
- Remove {@code final} modifiers, if present.
*
- Create a delegator method named {@code methodName} which delegates to the {@link
* ClassHandler}.
*
*/
protected void instrumentNormalMethod(MutableClass mutableClass, MethodNode method) {
// if not abstract, set a final modifier
if ((method.access & Opcodes.ACC_ABSTRACT) == 0) {
method.access = method.access | Opcodes.ACC_FINAL;
}
boolean isNativeMethod = (method.access & Opcodes.ACC_NATIVE) != 0;
if (isNativeMethod) {
instrumentNativeMethod(mutableClass, method);
}
// Create delegator method with same name as original method. The delegator method will use
// invokedynamic to decide at runtime whether to call original method or shadowed method
String originalName = method.name;
method.name = directMethodName(mutableClass, originalName);
MethodNode delegatorMethodNode =
new MethodNode(
method.access, originalName, method.desc, method.signature, exceptionArray(method));
delegatorMethodNode.visibleAnnotations = method.visibleAnnotations;
delegatorMethodNode.access &= ~(Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL);
makeMethodPrivate(method);
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(delegatorMethodNode);
generateClassHandlerCall(mutableClass, method, originalName, generator, isNativeMethod);
generator.endMethod();
mutableClass.addMethod(delegatorMethodNode);
}
/**
* Creates native stub which returns the default return value.
*
* @param mutableClass Class to be instrumented
* @param method Method to be instrumented, must be native
*/
protected void instrumentNativeMethod(MutableClass mutableClass, MethodNode method) {
String nativeBindingMethodName =
SHADOW_IMPL.directNativeMethodName(mutableClass.getName(), method.name);
// Generate native binding method
MethodNode nativeBindingMethod =
new MethodNode(
Opcodes.ASM4,
nativeBindingMethodName,
method.desc,
method.signature,
exceptionArray(method));
nativeBindingMethod.access = method.access | Opcodes.ACC_SYNTHETIC;
makeMethodPrivate(nativeBindingMethod);
mutableClass.addMethod(nativeBindingMethod);
method.access = method.access & ~Opcodes.ACC_NATIVE;
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(method);
Type returnType = generator.getReturnType();
generator.pushDefaultReturnValueToStack(returnType);
generator.returnValue();
}
protected static String directMethodName(MutableClass mutableClass, String originalName) {
return SHADOW_IMPL.directMethodName(mutableClass.getName(), originalName);
}
// todo rename
private MethodNode redirectorMethod(
MutableClass mutableClass, MethodNode method, String newName) {
MethodNode redirector =
new MethodNode(
Opcodes.ASM4, newName, method.desc, method.signature, exceptionArray(method));
redirector.access =
method.access & ~(Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL);
makeMethodPrivate(redirector);
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(redirector);
generator.invokeMethod(mutableClass.internalClassName, method);
generator.returnValue();
return redirector;
}
protected String[] exceptionArray(MethodNode method) {
List exceptions = method.exceptions;
return exceptions.toArray(new String[exceptions.size()]);
}
/** Filters methods that might need special treatment because of various reasons */
private void rewriteMethodBody(MutableClass mutableClass, MethodNode callingMethod) {
ListIterator instructions = callingMethod.instructions.iterator();
while (instructions.hasNext()) {
AbstractInsnNode node = instructions.next();
switch (node.getOpcode()) {
case Opcodes.NEW:
TypeInsnNode newInsnNode = (TypeInsnNode) node;
newInsnNode.desc = mutableClass.config.mappedTypeName(newInsnNode.desc);
break;
case Opcodes.GETFIELD:
/* falls through */
case Opcodes.PUTFIELD:
/* falls through */
case Opcodes.GETSTATIC:
/* falls through */
case Opcodes.PUTSTATIC:
FieldInsnNode fieldInsnNode = (FieldInsnNode) node;
fieldInsnNode.desc = mutableClass.config.mappedTypeName(fieldInsnNode.desc); // todo test
break;
case Opcodes.INVOKESTATIC:
/* falls through */
case Opcodes.INVOKEINTERFACE:
/* falls through */
case Opcodes.INVOKESPECIAL:
/* falls through */
case Opcodes.INVOKEVIRTUAL:
MethodInsnNode targetMethod = (MethodInsnNode) node;
targetMethod.desc = mutableClass.config.remapParams(targetMethod.desc);
if (isGregorianCalendarBooleanConstructor(targetMethod)) {
replaceGregorianCalendarBooleanConstructor(instructions, targetMethod);
} else if (mutableClass.config.shouldIntercept(targetMethod)) {
interceptInvokeVirtualMethod(mutableClass, instructions, targetMethod);
}
break;
case Opcodes.INVOKEDYNAMIC:
/* no unusual behavior */
break;
default:
break;
}
}
}
/**
* Verifies if the @targetMethod is a {@code (boolean)} constructor for {@link
* java.util.GregorianCalendar}.
*/
private static boolean isGregorianCalendarBooleanConstructor(MethodInsnNode targetMethod) {
return targetMethod.owner.equals("java/util/GregorianCalendar")
&& targetMethod.name.equals("")
&& targetMethod.desc.equals("(Z)V");
}
/**
* Replaces the void {@code (boolean)} constructor for a call to the {@code void (int,
* int, int)} one.
*/
private static void replaceGregorianCalendarBooleanConstructor(
ListIterator instructions, MethodInsnNode targetMethod) {
// Remove the call to GregorianCalendar(boolean)
instructions.remove();
// Discard the already-pushed parameter for GregorianCalendar(boolean)
instructions.add(new InsnNode(Opcodes.POP));
// Add parameters values for calling GregorianCalendar(int, int, int)
instructions.add(new InsnNode(Opcodes.ICONST_0));
instructions.add(new InsnNode(Opcodes.ICONST_0));
instructions.add(new InsnNode(Opcodes.ICONST_0));
// Call GregorianCalendar(int, int, int)
instructions.add(
new MethodInsnNode(
Opcodes.INVOKESPECIAL,
targetMethod.owner,
targetMethod.name,
"(III)V",
targetMethod.itf));
}
/**
* Decides to call through the appropriate method to intercept the method with an INVOKEVIRTUAL
* Opcode, depending if the invokedynamic bytecode instruction is available (Java 7+).
*/
protected void interceptInvokeVirtualMethod(
MutableClass mutableClass,
ListIterator instructions,
MethodInsnNode targetMethod) {
instructions.remove(); // remove the method invocation
Type type = Type.getObjectType(targetMethod.owner);
String description = targetMethod.desc;
String owner = type.getClassName();
if (targetMethod.getOpcode() != Opcodes.INVOKESTATIC) {
String thisType = type.getDescriptor();
description = "(" + thisType + description.substring(1);
}
instructions.add(
new InvokeDynamicInsnNode(targetMethod.name, description, BOOTSTRAP_INTRINSIC, owner));
}
/** Replaces protected and private class modifiers with public. */
private static void makeClassPublic(ClassNode clazz) {
clazz.access =
(clazz.access | Opcodes.ACC_PUBLIC) & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
}
/** Replaces protected and public class modifiers with private. */
protected void makeMethodPrivate(MethodNode method) {
method.access =
(method.access | Opcodes.ACC_PRIVATE) & ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED);
}
private static MethodNode generateStaticInitializerNotifierMethod(MutableClass mutableClass) {
MethodNode methodNode = new MethodNode(Opcodes.ACC_STATIC, "", "()V", "()V", null);
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(methodNode);
generator.push(mutableClass.classType);
generator.invokeStatic(
Type.getType(RobolectricInternals.class),
new Method("classInitializing", "(Ljava/lang/Class;)V"));
generator.returnValue();
generator.endMethod();
return methodNode;
}
// todo javadocs
protected void generateClassHandlerCall(
MutableClass mutableClass,
MethodNode originalMethod,
String originalMethodName,
RobolectricGeneratorAdapter generator,
boolean isNativeMethod) {
Handle original =
new Handle(
getTag(originalMethod),
mutableClass.classType.getInternalName(),
originalMethod.name,
originalMethod.desc,
getTag(originalMethod) == Opcodes.H_INVOKEINTERFACE);
if (generator.isStatic()) {
generator.loadArgs();
generator.invokeDynamic(
originalMethodName, originalMethod.desc, BOOTSTRAP_STATIC, original, isNativeMethod);
} else {
String desc = "(" + mutableClass.classType.getDescriptor() + originalMethod.desc.substring(1);
generator.loadThis();
generator.loadArgs();
generator.invokeDynamic(originalMethodName, desc, BOOTSTRAP, original, isNativeMethod);
}
generator.returnValue();
}
int getTag(MethodNode m) {
return Modifier.isStatic(m.access) ? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKESPECIAL;
}
// implemented in DirectClassInstrumentor
public void setAndroidJarSDKVersion(int androidJarSDKVersion) {}
// implemented in DirectClassInstrumentor
protected int getAndroidJarSDKVersion() {
return -1;
}
public interface Decorator {
void decorate(MutableClass mutableClass);
}
/**
* Provides try/catch code generation with a {@link org.objectweb.asm.commons.GeneratorAdapter}.
*/
static class TryCatch {
private final Label start;
private final Label end;
private final Label handler;
private final GeneratorAdapter generatorAdapter;
TryCatch(GeneratorAdapter generatorAdapter, Type type) {
this.generatorAdapter = generatorAdapter;
this.start = generatorAdapter.mark();
this.end = new Label();
this.handler = new Label();
generatorAdapter.visitTryCatchBlock(start, end, handler, type.getInternalName());
}
void end() {
generatorAdapter.mark(end);
}
void handler() {
generatorAdapter.mark(handler);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy