dynamical make substitution via byte code exchange

This commit is contained in:
金戟 2020-07-29 21:41:38 +08:00
parent 47aa73bd13
commit 9321980a68
3 changed files with 43 additions and 47 deletions

View File

@ -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";

View File

@ -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;
}
}

View File

@ -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);
}
}