From 9321980a68248393dfe72a42ecf91e92330fc13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Wed, 29 Jul 2020 21:41:38 +0800 Subject: [PATCH] dynamical make substitution via byte code exchange --- .../testable/agent/constant/ConstPool.java | 1 + .../agent/handler/SourceClassHandler.java | 84 +++++++++---------- .../agent/handler/TestClassHandler.java | 5 +- 3 files changed, 43 insertions(+), 47 deletions(-) diff --git a/agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java b/agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java index f8cd50b..e7146e8 100644 --- a/agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java +++ b/agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java @@ -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"; diff --git a/agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java b/agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java index e76e1b3..4437440 100644 --- a/agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java +++ b/agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java @@ -20,14 +20,7 @@ import java.util.Set; public class SourceClassHandler extends ClassHandler { private static final String CONSTRUCTOR = ""; - 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 injectMethods; + private final List injectMethods; public SourceClassHandler(List injectMethods) { this.injectMethods = injectMethods; @@ -44,12 +37,12 @@ public class SourceClassHandler extends ClassHandler { Set memberInjectMethods = CollectionUtil.getCrossSet(methods, injectMethods); Set 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 memberInjectMethods, - Set newOperatorInjectDesc) { + Set 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 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 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 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; - } - } diff --git a/agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java b/agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java index d16a0c7..6336aeb 100644 --- a/agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java +++ b/agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java @@ -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 TEST_ANNOTATIONS = new ArrayList(); - 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); } }