move invoke recorder injection to mock class

This commit is contained in:
金戟 2021-02-10 22:42:00 +08:00
parent 6f9accf319
commit f1edf92626
2 changed files with 134 additions and 167 deletions

View File

@ -1,9 +1,17 @@
package com.alibaba.testable.agent.handler;
import com.alibaba.testable.agent.constant.ConstPool;
import com.alibaba.testable.agent.tool.ImmutablePair;
import com.alibaba.testable.agent.util.AnnotationUtil;
import com.alibaba.testable.agent.util.ClassUtil;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import javax.lang.model.type.NullType;
import java.util.List;
import static com.alibaba.testable.agent.constant.ConstPool.CONSTRUCTOR;
import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName;
/**
* @author flin
@ -11,6 +19,9 @@ import static com.alibaba.testable.agent.constant.ConstPool.CONSTRUCTOR;
public class MockClassHandler extends BaseClassHandler {
private static final String REF_INSTANCE = "_instance";
private static final String CLASS_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil";
private static final String METHOD_RECORD_MOCK_INVOKE = "recordMockInvoke";
private static final String SIGNATURE_INVOKE_RECORDER_METHOD = "([Ljava/lang/Object;ZZ)V";
public MockClassHandler(String className) {
this.mockClassName = className;
@ -22,6 +33,14 @@ public class MockClassHandler extends BaseClassHandler {
return;
}
addGetInstanceMethod(cn);
for (MethodNode mn : cn.methods) {
if (isMockMethod(mn)) {
mn.access &= ~ACC_PRIVATE;
mn.access &= ~ACC_PROTECTED;
mn.access |= ACC_PUBLIC;
injectInvokeRecorder(mn);
}
}
}
private void addGetInstanceMethod(ClassNode cn) {
@ -46,4 +65,119 @@ public class MockClassHandler extends BaseClassHandler {
cn.methods.add(getInstanceMethod);
}
private boolean isMockMethod(MethodNode mn) {
if (mn.visibleAnnotations == null) {
return false;
}
for (AnnotationNode an : mn.visibleAnnotations) {
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc) ||
ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc)) {
return true;
}
}
return false;
}
private void injectInvokeRecorder(MethodNode mn) {
InsnList il = new InsnList();
List<Byte> types = ClassUtil.getParameterTypes(mn.desc);
int size = types.size();
int parameterOffset = 1;
mn.maxStack += 2;
il.add(getIntInsn(size));
il.add(new TypeInsnNode(ANEWARRAY, ClassUtil.CLASS_OBJECT));
for (int i = 0; i < size; i++) {
mn.maxStack += 3;
il.add(new InsnNode(DUP));
il.add(getIntInsn(i));
ImmutablePair<Integer, Integer> code = getLoadParameterByteCode(types.get(i));
il.add(new VarInsnNode(code.left, parameterOffset));
parameterOffset += code.right;
MethodInsnNode typeConvertMethodNode = ClassUtil.getPrimaryTypeConvertMethod(types.get(i));
if (typeConvertMethodNode != null) {
il.add(typeConvertMethodNode);
}
il.add(new InsnNode(AASTORE));
}
if (isMockForConstructor(mn)) {
il.add(new InsnNode(ICONST_1));
} else {
il.add(new InsnNode(ICONST_0));
}
if (isTargetClassInParameter(mn)) {
il.add(new InsnNode(ICONST_1));
} else {
il.add(new InsnNode(ICONST_0));
}
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_INVOKE_RECORD_UTIL, METHOD_RECORD_MOCK_INVOKE,
SIGNATURE_INVOKE_RECORDER_METHOD, false));
mn.instructions.insertBefore(mn.instructions.get(0), il);
}
private boolean isMockForConstructor(MethodNode mn) {
for (AnnotationNode an : mn.visibleAnnotations) {
String annotationName = toDotSeparateFullClassName(an.desc);
if (ConstPool.MOCK_CONSTRUCTOR.equals(annotationName)) {
return true;
} else if (ConstPool.MOCK_METHOD.equals(annotationName)) {
String method = AnnotationUtil.getAnnotationParameter
(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
if (ConstPool.CONSTRUCTOR.equals(method)) {
return true;
}
}
}
return false;
}
private boolean isTargetClassInParameter(MethodNode mn) {
for (AnnotationNode an : mn.visibleAnnotations) {
if (ConstPool.MOCK_METHOD.equals(toDotSeparateFullClassName(an.desc))) {
Type type = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
if (type != null && !type.getClassName().equals(NullType.class.getName())) {
return false;
}
}
}
return true;
}
private static ImmutablePair<Integer, Integer> getLoadParameterByteCode(Byte type) {
switch (type) {
case ClassUtil.TYPE_BYTE:
case ClassUtil.TYPE_CHAR:
case ClassUtil.TYPE_SHORT:
case ClassUtil.TYPE_INT:
case ClassUtil.TYPE_BOOL:
return ImmutablePair.of(ILOAD, 1);
case ClassUtil.TYPE_DOUBLE:
return ImmutablePair.of(DLOAD, 2);
case ClassUtil.TYPE_FLOAT:
return ImmutablePair.of(FLOAD, 1);
case ClassUtil.TYPE_LONG:
return ImmutablePair.of(LLOAD, 2);
default:
return ImmutablePair.of(ALOAD, 1);
}
}
private AbstractInsnNode getIntInsn(int num) {
switch (num) {
case 0:
return new InsnNode(ICONST_0);
case 1:
return new InsnNode(ICONST_1);
case 2:
return new InsnNode(ICONST_2);
case 3:
return new InsnNode(ICONST_3);
case 4:
return new InsnNode(ICONST_4);
case 5:
return new InsnNode(ICONST_5);
default:
return new IntInsnNode(BIPUSH, num);
}
}
}

View File

@ -21,7 +21,6 @@ public class TestClassHandler extends BaseClassHandler {
private static final String CLASS_TESTABLE_TOOL = "com/alibaba/testable/core/tool/TestableTool";
private static final String CLASS_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil";
private static final String CLASS_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil";
private static final String CLASS_MOCK_CONTEXT = "com/alibaba/testable/agent/model/MockContext";
private static final String REF_TESTABLE_CONTEXT = "_testableContextReference";
private static final String FIELD_TEST_CASE = "TEST_CASE";
@ -30,10 +29,8 @@ public class TestClassHandler extends BaseClassHandler {
private static final String FIELD_PARAMETERS = "parameters";
private static final String METHOD_CURRENT_TEST_CASE_NAME = "currentTestCaseName";
private static final String METHOD_CURRENT_SOURCE_METHOD_NAME = "currentSourceMethodName";
private static final String METHOD_RECORD_MOCK_INVOKE = "recordMockInvoke";
private static final String SIGNATURE_CURRENT_TEST_CASE_NAME = "(Ljava/lang/String;)Ljava/lang/String;";
private static final String SIGNATURE_CURRENT_SOURCE_METHOD_NAME = "()Ljava/lang/String;";
private static final String SIGNATURE_INVOKE_RECORDER_METHOD = "([Ljava/lang/Object;ZZ)V";
private static final String SIGNATURE_PARAMETERS = "Ljava/util/Map;";
public TestClassHandler(String mockClassName) {
@ -53,7 +50,6 @@ public class TestClassHandler extends BaseClassHandler {
if (mn.name.equals(ConstPool.CONSTRUCTOR)) {
initMockContextReference(cn, mn);
} else {
handleMockMethod(cn, mn);
handleInstruction(cn, mn);
}
}
@ -70,67 +66,6 @@ public class TestClassHandler extends BaseClassHandler {
mn.maxStack++;
}
private void handleMockMethod(ClassNode cn, MethodNode mn) {
if (isMockMethod(mn)) {
toPublicStatic(cn, mn);
injectInvokeRecorder(mn);
}
}
private void toPublicStatic(ClassNode cn, MethodNode mn) {
mn.access &= ~ACC_PRIVATE;
mn.access &= ~ACC_PROTECTED;
mn.access |= ACC_PUBLIC;
if ((mn.access & ACC_STATIC) == 0) {
mn.access |= ACC_STATIC;
// remove `this` reference
LocalVariableNode thisRef = null;
for (LocalVariableNode vn : mn.localVariables) {
if (vn.index == 0) {
thisRef = vn;
} else {
vn.index--;
}
}
if (thisRef != null) {
mn.localVariables.remove(thisRef);
} else {
LogUtil.error("Fail to find `this` reference in non-static method " + getName(cn, mn));
return;
}
for (AbstractInsnNode in : mn.instructions) {
if (in instanceof IincInsnNode) {
((IincInsnNode)in).var--;
} else if (in instanceof VarInsnNode) {
if (((VarInsnNode)in).var > 0) {
((VarInsnNode)in).var--;
} else if (in.getOpcode() == ALOAD) {
LogUtil.error("Attempt to access non-static member in mock method " + getName(cn, mn));
return;
}
}
}
mn.maxLocals--;
}
}
private String getName(ClassNode cn, MethodNode mn) {
return cn.name + ":" + mn.name;
}
private boolean isMockMethod(MethodNode mn) {
if (mn.visibleAnnotations == null) {
return false;
}
for (AnnotationNode an : mn.visibleAnnotations) {
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc) ||
ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc)) {
return true;
}
}
return false;
}
private void handleInstruction(ClassNode cn, MethodNode mn) {
AbstractInsnNode[] instructions = mn.instructions.toArray();
int i = 0;
@ -173,106 +108,4 @@ public class TestClassHandler extends BaseClassHandler {
return mn.instructions.toArray();
}
private void injectInvokeRecorder(MethodNode mn) {
InsnList il = new InsnList();
List<Byte> types = ClassUtil.getParameterTypes(mn.desc);
int size = types.size();
int parameterOffset = 0;
mn.maxStack += 2;
il.add(getIntInsn(size));
il.add(new TypeInsnNode(ANEWARRAY, ClassUtil.CLASS_OBJECT));
for (int i = 0; i < size; i++) {
mn.maxStack += 3;
il.add(new InsnNode(DUP));
il.add(getIntInsn(i));
ImmutablePair<Integer, Integer> code = getLoadParameterByteCode(types.get(i));
il.add(new VarInsnNode(code.left, parameterOffset));
parameterOffset += code.right;
MethodInsnNode typeConvertMethodNode = ClassUtil.getPrimaryTypeConvertMethod(types.get(i));
if (typeConvertMethodNode != null) {
il.add(typeConvertMethodNode);
}
il.add(new InsnNode(AASTORE));
}
if (isMockForConstructor(mn)) {
il.add(new InsnNode(ICONST_1));
} else {
il.add(new InsnNode(ICONST_0));
}
if (isTargetClassInParameter(mn)) {
il.add(new InsnNode(ICONST_1));
} else {
il.add(new InsnNode(ICONST_0));
}
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_INVOKE_RECORD_UTIL, METHOD_RECORD_MOCK_INVOKE,
SIGNATURE_INVOKE_RECORDER_METHOD, false));
mn.instructions.insertBefore(mn.instructions.get(0), il);
}
private boolean isMockForConstructor(MethodNode mn) {
for (AnnotationNode an : mn.visibleAnnotations) {
String annotationName = toDotSeparateFullClassName(an.desc);
if (ConstPool.MOCK_CONSTRUCTOR.equals(annotationName)) {
return true;
} else if (ConstPool.MOCK_METHOD.equals(annotationName)) {
String method = AnnotationUtil.getAnnotationParameter
(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
if (ConstPool.CONSTRUCTOR.equals(method)) {
return true;
}
}
}
return false;
}
private boolean isTargetClassInParameter(MethodNode mn) {
for (AnnotationNode an : mn.visibleAnnotations) {
if (ConstPool.MOCK_METHOD.equals(toDotSeparateFullClassName(an.desc))) {
Type type = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
if (type != null && !type.getClassName().equals(NullType.class.getName())) {
return false;
}
}
}
return true;
}
private static ImmutablePair<Integer, Integer> getLoadParameterByteCode(Byte type) {
switch (type) {
case ClassUtil.TYPE_BYTE:
case ClassUtil.TYPE_CHAR:
case ClassUtil.TYPE_SHORT:
case ClassUtil.TYPE_INT:
case ClassUtil.TYPE_BOOL:
return ImmutablePair.of(ILOAD, 1);
case ClassUtil.TYPE_DOUBLE:
return ImmutablePair.of(DLOAD, 2);
case ClassUtil.TYPE_FLOAT:
return ImmutablePair.of(FLOAD, 1);
case ClassUtil.TYPE_LONG:
return ImmutablePair.of(LLOAD, 2);
default:
return ImmutablePair.of(ALOAD, 1);
}
}
private AbstractInsnNode getIntInsn(int num) {
switch (num) {
case 0:
return new InsnNode(ICONST_0);
case 1:
return new InsnNode(ICONST_1);
case 2:
return new InsnNode(ICONST_2);
case 3:
return new InsnNode(ICONST_3);
case 4:
return new InsnNode(ICONST_4);
case 5:
return new InsnNode(ICONST_5);
default:
return new IntInsnNode(BIPUSH, num);
}
}
}