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 SLASH = "/";
public static final String TEST_POSTFIX = "Test"; 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 ENABLE_TESTABLE = "com.alibaba.testable.core.annotation.EnableTestable";
public static final String TESTABLE_INJECT = "com.alibaba.testable.core.annotation.TestableInject"; 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 { public class SourceClassHandler extends ClassHandler {
private static final String CONSTRUCTOR = "<init>"; private static final String CONSTRUCTOR = "<init>";
private static final String TESTABLE_NE = "n/e"; private final List<MethodInfo> injectMethods;
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;
public SourceClassHandler(List<MethodInfo> injectMethods) { public SourceClassHandler(List<MethodInfo> injectMethods) {
this.injectMethods = injectMethods; this.injectMethods = injectMethods;
@ -44,12 +37,12 @@ public class SourceClassHandler extends ClassHandler {
Set<MethodInfo> memberInjectMethods = CollectionUtil.getCrossSet(methods, injectMethods); Set<MethodInfo> memberInjectMethods = CollectionUtil.getCrossSet(methods, injectMethods);
Set<MethodInfo> newOperatorInjectMethods = CollectionUtil.getMinusSet(injectMethods, memberInjectMethods); Set<MethodInfo> newOperatorInjectMethods = CollectionUtil.getMinusSet(injectMethods, memberInjectMethods);
for (MethodNode m : cn.methods) { 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, private void transformMethod(ClassNode cn, MethodNode mn, Set<MethodInfo> memberInjectMethods,
Set<String> newOperatorInjectDesc) { Set<MethodInfo> newOperatorInjectMethods) {
AbstractInsnNode[] instructions = mn.instructions.toArray(); AbstractInsnNode[] instructions = mn.instructions.toArray();
int i = 0; int i = 0;
do { do {
@ -58,15 +51,17 @@ public class SourceClassHandler extends ClassHandler {
if (cn.name.equals(node.owner) && memberInjectMethods.contains(new MethodInfo(node.name, node.desc))) { if (cn.name.equals(node.owner) && memberInjectMethods.contains(new MethodInfo(node.name, node.desc))) {
int rangeStart = getMemberMethodStart(instructions, i); int rangeStart = getMemberMethodStart(instructions, i);
if (rangeStart >= 0) { if (rangeStart >= 0) {
instructions = replaceMemberCallOps(mn, instructions, rangeStart, i); instructions = replaceMemberCallOps(cn, mn, instructions, rangeStart, i);
i = rangeStart; i = rangeStart;
} }
} else if (CONSTRUCTOR.equals(node.name) && } else if (CONSTRUCTOR.equals(node.name)) {
newOperatorInjectDesc.contains(getConstructorInjectDesc(node))) { String newOperatorInjectMethodName = getNewOperatorInjectMethodName(newOperatorInjectMethods, node);
int rangeStart = getConstructorStart(instructions, node.owner, i); if (newOperatorInjectMethodName.length() > 0) {
if (rangeStart >= 0) { int rangeStart = getConstructorStart(instructions, node.owner, i);
instructions = replaceNewOps(mn, instructions, rangeStart, i); if (rangeStart >= 0) {
i = rangeStart; instructions = replaceNewOps(cn, mn, newOperatorInjectMethodName, instructions, rangeStart, i);
i = rangeStart;
}
} }
} }
} }
@ -74,6 +69,15 @@ public class SourceClassHandler extends ClassHandler {
} while (i < instructions.length); } 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) { private String getConstructorInjectDesc(MethodInsnNode constructorNode) {
return constructorNode.desc.substring(0, constructorNode.desc.length() - 1) + return constructorNode.desc.substring(0, constructorNode.desc.length() - 1) +
ClassUtil.toByteCodeClassName(constructorNode.owner); ClassUtil.toByteCodeClassName(constructorNode.owner);
@ -97,45 +101,37 @@ public class SourceClassHandler extends ClassHandler {
return -1; 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 classType = ((TypeInsnNode)instructions[start]).desc;
String constructorDesc = ((MethodInsnNode)instructions[end]).desc; String constructorDesc = ((MethodInsnNode)instructions[end]).desc;
mn.instructions.insertBefore(instructions[start], new LdcInsnNode(Type.getType("L" + classType + ";"))); String testClassName = cn.name + ConstPool.TEST_POSTFIX;
List<Byte> parameterTypes = ClassUtil.getParameterTypes(constructorDesc); mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName,
InsnList il = new InsnList(); ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName)));
il.add(new MethodInsnNode(INVOKESTATIC, TESTABLE_NE, TESTABLE_W, mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName,
getConstructorSubstitutionDesc(parameterTypes.size()))); newOperatorInjectMethodName, getConstructorInjectDesc(constructorDesc, classType), false));
il.add(new TypeInsnNode(CHECKCAST, classType));
mn.instructions.insertBefore(instructions[end], il);
mn.instructions.remove(instructions[start]); mn.instructions.remove(instructions[start]);
mn.instructions.remove(instructions[start + 1]); mn.instructions.remove(instructions[start + 1]);
mn.instructions.remove(instructions[end]); mn.instructions.remove(instructions[end]);
mn.maxStack += 1;
return mn.instructions.toArray(); return mn.instructions.toArray();
} }
private String getConstructorSubstitutionDesc(int parameterCount) { private String getConstructorInjectDesc(String constructorDesc, String classType) {
return CONSTRUCTOR_DESC_PREFIX + StringUtil.repeat(OBJECT_DESC, parameterCount) + METHOD_DESC_POSTFIX; return constructorDesc.substring(0, constructorDesc.length() - 1) +
ClassUtil.toByteCodeClassName(classType);
} }
private AbstractInsnNode[] replaceMemberCallOps(MethodNode mn, AbstractInsnNode[] instructions, int start, int end) { private AbstractInsnNode[] replaceMemberCallOps(ClassNode cn, MethodNode mn, AbstractInsnNode[] instructions,
String methodDesc = ((MethodInsnNode)instructions[end]).desc; int start, int end) {
String returnType = ClassUtil.getReturnType(methodDesc); MethodInsnNode method = (MethodInsnNode)instructions[end];
String methodName = ((MethodInsnNode)instructions[end]).name; String testClassName = cn.name + ConstPool.TEST_POSTFIX;
mn.instructions.insert(instructions[start], new LdcInsnNode(methodName)); mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName,
List<Byte> parameterTypes = ClassUtil.getParameterTypes(methodDesc); ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName)));
InsnList il = new InsnList(); mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName,
il.add(new MethodInsnNode(INVOKESTATIC, TESTABLE_NE, TESTABLE_F, method.name, method.desc, false));
getMethodSubstitutionDesc(parameterTypes.size()))); mn.instructions.remove(instructions[start]);
il.add(new TypeInsnNode(CHECKCAST, returnType));
mn.instructions.insertBefore(instructions[end], il);
mn.instructions.remove(instructions[end]); mn.instructions.remove(instructions[end]);
mn.maxStack += 1;
return mn.instructions.toArray(); 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; 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.ClassUtil;
import com.alibaba.testable.agent.util.CollectionUtil; import com.alibaba.testable.agent.util.CollectionUtil;
import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.*;
@ -13,8 +14,6 @@ import java.util.List;
public class TestClassHandler extends ClassHandler { public class TestClassHandler extends ClassHandler {
private static final List<String> TEST_ANNOTATIONS = new ArrayList<String>(); 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 { static {
// JUnit4 // JUnit4
@ -43,7 +42,7 @@ public class TestClassHandler extends ClassHandler {
if (CollectionUtil.containsAny(visibleAnnotationNames, TEST_ANNOTATIONS)) { if (CollectionUtil.containsAny(visibleAnnotationNames, TEST_ANNOTATIONS)) {
InsnList il = new InsnList(); InsnList il = new InsnList();
il.add(new VarInsnNode(ALOAD, 0)); 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); mn.instructions.insertBefore(mn.instructions.get(0), il);
} }
} }