mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-03-13 11:20:32 +08:00
dynamical make substitution via byte code exchange
This commit is contained in:
parent
47aa73bd13
commit
9321980a68
@ -9,6 +9,7 @@ public class ConstPool {
|
||||
public static final String SLASH = "/";
|
||||
|
||||
public static final String TEST_POSTFIX = "Test";
|
||||
public static final String TESTABLE_INJECT_REF = "_testableInternalRef";
|
||||
|
||||
public static final String ENABLE_TESTABLE = "com.alibaba.testable.core.annotation.EnableTestable";
|
||||
public static final String TESTABLE_INJECT = "com.alibaba.testable.core.annotation.TestableInject";
|
||||
|
@ -20,14 +20,7 @@ import java.util.Set;
|
||||
public class SourceClassHandler extends ClassHandler {
|
||||
|
||||
private static final String CONSTRUCTOR = "<init>";
|
||||
private static final String TESTABLE_NE = "n/e";
|
||||
private static final String TESTABLE_W = "w";
|
||||
private static final String TESTABLE_F = "f";
|
||||
private static final String CONSTRUCTOR_DESC_PREFIX = "(Ljava/lang/Class;";
|
||||
private static final String METHOD_DESC_PREFIX = "(Ljava/lang/Object;Ljava/lang/String;";
|
||||
private static final String OBJECT_DESC = "Ljava/lang/Object;";
|
||||
private static final String METHOD_DESC_POSTFIX = ")Ljava/lang/Object;";
|
||||
private List<MethodInfo> injectMethods;
|
||||
private final List<MethodInfo> injectMethods;
|
||||
|
||||
public SourceClassHandler(List<MethodInfo> injectMethods) {
|
||||
this.injectMethods = injectMethods;
|
||||
@ -44,12 +37,12 @@ public class SourceClassHandler extends ClassHandler {
|
||||
Set<MethodInfo> memberInjectMethods = CollectionUtil.getCrossSet(methods, injectMethods);
|
||||
Set<MethodInfo> newOperatorInjectMethods = CollectionUtil.getMinusSet(injectMethods, memberInjectMethods);
|
||||
for (MethodNode m : cn.methods) {
|
||||
transformMethod(cn, m, memberInjectMethods, MethodInfo.descSet(newOperatorInjectMethods));
|
||||
transformMethod(cn, m, memberInjectMethods, newOperatorInjectMethods);
|
||||
}
|
||||
}
|
||||
|
||||
private void transformMethod(ClassNode cn, MethodNode mn, Set<MethodInfo> memberInjectMethods,
|
||||
Set<String> newOperatorInjectDesc) {
|
||||
Set<MethodInfo> newOperatorInjectMethods) {
|
||||
AbstractInsnNode[] instructions = mn.instructions.toArray();
|
||||
int i = 0;
|
||||
do {
|
||||
@ -58,15 +51,17 @@ public class SourceClassHandler extends ClassHandler {
|
||||
if (cn.name.equals(node.owner) && memberInjectMethods.contains(new MethodInfo(node.name, node.desc))) {
|
||||
int rangeStart = getMemberMethodStart(instructions, i);
|
||||
if (rangeStart >= 0) {
|
||||
instructions = replaceMemberCallOps(mn, instructions, rangeStart, i);
|
||||
instructions = replaceMemberCallOps(cn, mn, instructions, rangeStart, i);
|
||||
i = rangeStart;
|
||||
}
|
||||
} else if (CONSTRUCTOR.equals(node.name) &&
|
||||
newOperatorInjectDesc.contains(getConstructorInjectDesc(node))) {
|
||||
int rangeStart = getConstructorStart(instructions, node.owner, i);
|
||||
if (rangeStart >= 0) {
|
||||
instructions = replaceNewOps(mn, instructions, rangeStart, i);
|
||||
i = rangeStart;
|
||||
} else if (CONSTRUCTOR.equals(node.name)) {
|
||||
String newOperatorInjectMethodName = getNewOperatorInjectMethodName(newOperatorInjectMethods, node);
|
||||
if (newOperatorInjectMethodName.length() > 0) {
|
||||
int rangeStart = getConstructorStart(instructions, node.owner, i);
|
||||
if (rangeStart >= 0) {
|
||||
instructions = replaceNewOps(cn, mn, newOperatorInjectMethodName, instructions, rangeStart, i);
|
||||
i = rangeStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -74,6 +69,15 @@ public class SourceClassHandler extends ClassHandler {
|
||||
} while (i < instructions.length);
|
||||
}
|
||||
|
||||
private String getNewOperatorInjectMethodName(Set<MethodInfo> newOperatorInjectMethods, MethodInsnNode node) {
|
||||
for (MethodInfo m : newOperatorInjectMethods) {
|
||||
if (m.getDesc().equals(getConstructorInjectDesc(node))) {
|
||||
return m.getName();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getConstructorInjectDesc(MethodInsnNode constructorNode) {
|
||||
return constructorNode.desc.substring(0, constructorNode.desc.length() - 1) +
|
||||
ClassUtil.toByteCodeClassName(constructorNode.owner);
|
||||
@ -97,45 +101,37 @@ public class SourceClassHandler extends ClassHandler {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private AbstractInsnNode[] replaceNewOps(MethodNode mn, AbstractInsnNode[] instructions, int start, int end) {
|
||||
private AbstractInsnNode[] replaceNewOps(ClassNode cn, MethodNode mn, String newOperatorInjectMethodName,
|
||||
AbstractInsnNode[] instructions, int start, int end) {
|
||||
String classType = ((TypeInsnNode)instructions[start]).desc;
|
||||
String constructorDesc = ((MethodInsnNode)instructions[end]).desc;
|
||||
mn.instructions.insertBefore(instructions[start], new LdcInsnNode(Type.getType("L" + classType + ";")));
|
||||
List<Byte> parameterTypes = ClassUtil.getParameterTypes(constructorDesc);
|
||||
InsnList il = new InsnList();
|
||||
il.add(new MethodInsnNode(INVOKESTATIC, TESTABLE_NE, TESTABLE_W,
|
||||
getConstructorSubstitutionDesc(parameterTypes.size())));
|
||||
il.add(new TypeInsnNode(CHECKCAST, classType));
|
||||
mn.instructions.insertBefore(instructions[end], il);
|
||||
String testClassName = cn.name + ConstPool.TEST_POSTFIX;
|
||||
mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName,
|
||||
ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName)));
|
||||
mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName,
|
||||
newOperatorInjectMethodName, getConstructorInjectDesc(constructorDesc, classType), false));
|
||||
mn.instructions.remove(instructions[start]);
|
||||
mn.instructions.remove(instructions[start + 1]);
|
||||
mn.instructions.remove(instructions[end]);
|
||||
mn.maxStack += 1;
|
||||
return mn.instructions.toArray();
|
||||
}
|
||||
|
||||
private String getConstructorSubstitutionDesc(int parameterCount) {
|
||||
return CONSTRUCTOR_DESC_PREFIX + StringUtil.repeat(OBJECT_DESC, parameterCount) + METHOD_DESC_POSTFIX;
|
||||
private String getConstructorInjectDesc(String constructorDesc, String classType) {
|
||||
return constructorDesc.substring(0, constructorDesc.length() - 1) +
|
||||
ClassUtil.toByteCodeClassName(classType);
|
||||
}
|
||||
|
||||
private AbstractInsnNode[] replaceMemberCallOps(MethodNode mn, AbstractInsnNode[] instructions, int start, int end) {
|
||||
String methodDesc = ((MethodInsnNode)instructions[end]).desc;
|
||||
String returnType = ClassUtil.getReturnType(methodDesc);
|
||||
String methodName = ((MethodInsnNode)instructions[end]).name;
|
||||
mn.instructions.insert(instructions[start], new LdcInsnNode(methodName));
|
||||
List<Byte> parameterTypes = ClassUtil.getParameterTypes(methodDesc);
|
||||
InsnList il = new InsnList();
|
||||
il.add(new MethodInsnNode(INVOKESTATIC, TESTABLE_NE, TESTABLE_F,
|
||||
getMethodSubstitutionDesc(parameterTypes.size())));
|
||||
il.add(new TypeInsnNode(CHECKCAST, returnType));
|
||||
mn.instructions.insertBefore(instructions[end], il);
|
||||
private AbstractInsnNode[] replaceMemberCallOps(ClassNode cn, MethodNode mn, AbstractInsnNode[] instructions,
|
||||
int start, int end) {
|
||||
MethodInsnNode method = (MethodInsnNode)instructions[end];
|
||||
String testClassName = cn.name + ConstPool.TEST_POSTFIX;
|
||||
mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName,
|
||||
ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName)));
|
||||
mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName,
|
||||
method.name, method.desc, false));
|
||||
mn.instructions.remove(instructions[start]);
|
||||
mn.instructions.remove(instructions[end]);
|
||||
mn.maxStack += 1;
|
||||
return mn.instructions.toArray();
|
||||
}
|
||||
|
||||
private String getMethodSubstitutionDesc(int parameterCount) {
|
||||
return METHOD_DESC_PREFIX + StringUtil.repeat(OBJECT_DESC, parameterCount) + METHOD_DESC_POSTFIX;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.alibaba.testable.agent.handler;
|
||||
|
||||
import com.alibaba.testable.agent.constant.ConstPool;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import com.alibaba.testable.agent.util.CollectionUtil;
|
||||
import org.objectweb.asm.tree.*;
|
||||
@ -13,8 +14,6 @@ import java.util.List;
|
||||
public class TestClassHandler extends ClassHandler {
|
||||
|
||||
private static final List<String> TEST_ANNOTATIONS = new ArrayList<String>();
|
||||
private static final String TESTABLE_SETUP_METHOD_NAME = "testableSetup";
|
||||
private static final String TESTABLE_SETUP_METHOD_DESC = "()V";
|
||||
|
||||
static {
|
||||
// JUnit4
|
||||
@ -43,7 +42,7 @@ public class TestClassHandler extends ClassHandler {
|
||||
if (CollectionUtil.containsAny(visibleAnnotationNames, TEST_ANNOTATIONS)) {
|
||||
InsnList il = new InsnList();
|
||||
il.add(new VarInsnNode(ALOAD, 0));
|
||||
il.add(new MethodInsnNode(INVOKESPECIAL, cn.name, TESTABLE_SETUP_METHOD_NAME, TESTABLE_SETUP_METHOD_DESC));
|
||||
il.add(new FieldInsnNode(PUTSTATIC, cn.name, ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(cn.name)));
|
||||
mn.instructions.insertBefore(mn.instructions.get(0), il);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user