support targetMethod parameter of TestableInject annotation

This commit is contained in:
金戟 2020-10-13 23:13:07 +08:00
parent 64227f1ed0
commit c869caa0c9
3 changed files with 36 additions and 9 deletions

View File

@ -39,7 +39,7 @@ public class SourceClassHandler extends BaseClassHandler {
for (MethodNode m : cn.methods) { for (MethodNode m : cn.methods) {
// record all member method names // record all member method names
if (!ConstPool.CONSTRUCTOR.equals(m.name)) { if (!ConstPool.CONSTRUCTOR.equals(m.name)) {
methods.add(new MethodInfo(cn.name, m.name, m.desc)); methods.add(new MethodInfo(cn.name, m.name, null, m.desc));
} }
} }
// member methods which has injection stub // member methods which has injection stub
@ -59,18 +59,23 @@ public class SourceClassHandler extends BaseClassHandler {
private void transformMethod(ClassNode cn, MethodNode mn, Set<MethodInfo> memberInjectMethods, private void transformMethod(ClassNode cn, MethodNode mn, Set<MethodInfo> memberInjectMethods,
Set<MethodInfo> newOperatorInjectMethods) { Set<MethodInfo> newOperatorInjectMethods) {
AbstractInsnNode[] instructions = mn.instructions.toArray(); AbstractInsnNode[] instructions = mn.instructions.toArray();
List<MethodInfo> memberInjectMethodList = new ArrayList<MethodInfo>(memberInjectMethods);
int i = 0; int i = 0;
do { do {
if (invokeOps.contains(instructions[i].getOpcode())) { if (invokeOps.contains(instructions[i].getOpcode())) {
MethodInsnNode node = (MethodInsnNode)instructions[i]; MethodInsnNode node = (MethodInsnNode)instructions[i];
if (memberInjectMethods.contains(new MethodInfo(node.owner, node.name, node.desc))) { int index = memberInjectMethodList.indexOf(new MethodInfo(node.owner, node.name, null, node.desc));
// it's a member method of current class and an inject method for it exist if (index >= 0) {
// it's a member method and an inject method for it exist
int rangeStart = getMemberMethodStart(instructions, i); int rangeStart = getMemberMethodStart(instructions, i);
if (rangeStart >= 0) { if (rangeStart >= 0) {
if (cn.name.equals(node.owner)) { if (cn.name.equals(node.owner)) {
// member method of current class
instructions = replaceMemberCallOps(cn, mn, instructions, rangeStart, i); instructions = replaceMemberCallOps(cn, mn, instructions, rangeStart, i);
} else { } else {
instructions = replaceCommonCallOps(cn, mn, instructions, node.owner, rangeStart, i); // member method of other class
String method = memberInjectMethodList.get(index).getSubstitutionMethod();
instructions = replaceCommonCallOps(cn, mn, instructions, node.owner, method, rangeStart, i);
} }
i = rangeStart; i = rangeStart;
} }
@ -168,14 +173,14 @@ public class SourceClassHandler extends BaseClassHandler {
} }
private AbstractInsnNode[] replaceCommonCallOps(ClassNode cn, MethodNode mn, AbstractInsnNode[] instructions, private AbstractInsnNode[] replaceCommonCallOps(ClassNode cn, MethodNode mn, AbstractInsnNode[] instructions,
String ownerClass, int start, int end) { String ownerClass, String substitutionMethod, int start, int end) {
mn.maxStack++; mn.maxStack++;
MethodInsnNode method = (MethodInsnNode)instructions[end]; MethodInsnNode method = (MethodInsnNode)instructions[end];
String testClassName = ClassUtil.getTestClassName(cn.name); String testClassName = ClassUtil.getTestClassName(cn.name);
mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName, mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName,
ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName))); ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName)));
mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName, mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName,
method.name, addFirstParameter(method.desc, ownerClass), false)); substitutionMethod, addFirstParameter(method.desc, ownerClass), false));
mn.instructions.remove(instructions[end]); mn.instructions.remove(instructions[end]);
return mn.instructions.toArray(); return mn.instructions.toArray();
} }

View File

@ -5,13 +5,28 @@ package com.alibaba.testable.agent.model;
*/ */
public class MethodInfo { public class MethodInfo {
/**
* name of the class this method belongs to
*/
private final String clazz; private final String clazz;
/**
* name of the method
*/
private final String name; private final String name;
/**
* name of the substitution method
* Note: this field do NOT join the `equals()` or `hashCode()` calculation
*/
private final String substitutionMethod;
/**
* parameter and return value of the method
*/
private final String desc; private final String desc;
public MethodInfo(String clazz, String name, String desc) { public MethodInfo(String clazz, String name, String substitutionMethod, String desc) {
this.clazz = clazz; this.clazz = clazz;
this.name = name; this.name = name;
this.substitutionMethod = substitutionMethod;
this.desc = desc; this.desc = desc;
} }
@ -23,6 +38,10 @@ public class MethodInfo {
return name; return name;
} }
public String getSubstitutionMethod() {
return substitutionMethod;
}
public String getDesc() { public String getDesc() {
return desc; return desc;
} }

View File

@ -86,12 +86,15 @@ public class TestableClassTransformer implements ClassFileTransformer {
String targetClass = getAnnotationParameter(an, TARGET_CLASS, sourceClassName); String targetClass = getAnnotationParameter(an, TARGET_CLASS, sourceClassName);
String targetMethod = getAnnotationParameter(an, TARGET_METHOD, mn.name); String targetMethod = getAnnotationParameter(an, TARGET_METHOD, mn.name);
if (sourceClassName.equals(targetClass)) { if (sourceClassName.equals(targetClass)) {
methodInfos.add(new MethodInfo(toSlashSeparatedName(targetClass), targetMethod, mn.desc)); // member method of the source class
methodInfos.add(new MethodInfo(
toSlashSeparatedName(targetClass), targetMethod, null, mn.desc));
} else { } else {
// member method of a common class
ImmutablePair<String, String> methodDescPair = extractFirstParameter(mn.desc); ImmutablePair<String, String> methodDescPair = extractFirstParameter(mn.desc);
if (methodDescPair != null && methodDescPair.left.equals(ClassUtil.toByteCodeClassName(targetClass))) { if (methodDescPair != null && methodDescPair.left.equals(ClassUtil.toByteCodeClassName(targetClass))) {
methodInfos.add(new MethodInfo( methodInfos.add(new MethodInfo(
toSlashSeparatedName(targetClass), targetMethod, methodDescPair.right)); toSlashSeparatedName(targetClass), targetMethod, mn.name, methodDescPair.right));
} }
} }
break; break;