refactor and reduce stack size

This commit is contained in:
金戟 2021-02-18 19:08:51 +08:00
parent bdd99577c4
commit 65cc631d39
8 changed files with 179 additions and 164 deletions

View File

@ -0,0 +1,19 @@
package com.alibaba.testable.agent.constant;
public class ByteCodeConst {
public static final byte TYPE_BYTE = 'B';
public static final byte TYPE_CHAR = 'C';
public static final byte TYPE_DOUBLE = 'D';
public static final byte TYPE_FLOAT = 'F';
public static final byte TYPE_INT = 'I';
public static final byte TYPE_LONG = 'J';
public static final byte TYPE_CLASS = 'L';
public static final byte TYPE_SHORT = 'S';
public static final byte TYPE_BOOL = 'Z';
public static final byte TYPE_VOID = 'V';
public static final byte PARAM_END = ')';
public static final byte CLASS_END = ';';
public static final byte TYPE_ARRAY = '[';
}

View File

@ -1,5 +1,6 @@
package com.alibaba.testable.agent.handler;
import com.alibaba.testable.agent.constant.ByteCodeConst;
import com.alibaba.testable.agent.constant.ConstPool;
import com.alibaba.testable.agent.tool.ImmutablePair;
import com.alibaba.testable.agent.util.AnnotationUtil;
@ -92,7 +93,7 @@ public class MockClassHandler extends BaseClassWithContextHandler {
if (targetClassName != null) {
// must get label before method description changed
ImmutablePair<LabelNode, LabelNode> labels = getStartAndEndLabel(mn);
mn.desc = ClassUtil.addParameterAtBegin(mn.desc, targetClassName);
mn.desc = MethodUtil.addParameterAtBegin(mn.desc, targetClassName);
int parameterOffset = MethodUtil.isStaticMethod(mn) ? 0 : 1;
mn.localVariables.add(parameterOffset, new LocalVariableNode("__self", targetClassName, null,
labels.left, labels.right, parameterOffset));
@ -121,7 +122,7 @@ public class MockClassHandler extends BaseClassWithContextHandler {
break;
}
}
if (ClassUtil.extractParameters(mn.desc).isEmpty()) {
if (MethodUtil.extractParameters(mn.desc).isEmpty()) {
// for method without parameter, should manually add a ending label
endLabel = new LabelNode(new Label());
mn.instructions.add(endLabel);
@ -158,14 +159,13 @@ public class MockClassHandler extends BaseClassWithContextHandler {
private InsnList invokeOriginalMethod(MethodNode mn) {
InsnList il = new InsnList();
mn.maxStack += 3;
ImmutablePair<Type, String> target = getTargetClassAndMethodName(mn);
il.add(new LdcInsnNode(target.left));
il.add(new LdcInsnNode(target.right));
il.add(duplicateParameters(mn));
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_MOCK_ASSOCIATION_UTIL, METHOD_INVOKE_ORIGIN,
SIGNATURE_INVOKE_ORIGIN, false));
String returnType = ClassUtil.getReturnType(mn.desc);
String returnType = MethodUtil.getReturnType(mn.desc);
if (VOID_RES.equals(returnType)) {
il.add(new InsnNode(POP));
il.add(new InsnNode(RETURN));
@ -197,9 +197,9 @@ public class MockClassHandler extends BaseClassWithContextHandler {
}
}
if (methodName.equals(CONSTRUCTOR)) {
className = Type.getType(ClassUtil.getReturnType(mn.desc));
className = Type.getType(MethodUtil.getReturnType(mn.desc));
} else {
className = Type.getType(ClassUtil.getFirstParameter(mn.desc));
className = Type.getType(MethodUtil.getFirstParameter(mn.desc));
}
return ImmutablePair.of(className, methodName);
}
@ -233,7 +233,6 @@ public class MockClassHandler extends BaseClassWithContextHandler {
private void injectInvokeRecorder(MethodNode mn) {
InsnList il = new InsnList();
mn.maxStack += 2;
il.add(duplicateParameters(mn));
if (isMockForConstructor(mn)) {
il.add(new InsnNode(ICONST_1));
@ -243,17 +242,17 @@ public class MockClassHandler extends BaseClassWithContextHandler {
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_INVOKE_RECORD_UTIL, METHOD_RECORD_MOCK_INVOKE,
SIGNATURE_RECORDER_METHOD_INVOKE, false));
mn.instructions.insertBefore(mn.instructions.getFirst(), il);
mn.maxStack += (2 + MethodUtil.getParameterTypes(mn.desc).size() * 3);
}
private InsnList duplicateParameters(MethodNode mn) {
InsnList il = new InsnList();
List<Byte> types = ClassUtil.getParameterTypes(mn.desc);
List<Byte> types = MethodUtil.getParameterTypes(mn.desc);
int size = types.size();
il.add(getIntInsn(size));
il.add(new TypeInsnNode(ANEWARRAY, ClassUtil.CLASS_OBJECT));
int parameterOffset = MethodUtil.isStaticMethod(mn) ? 0 : 1;
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));
@ -286,17 +285,17 @@ public class MockClassHandler extends BaseClassWithContextHandler {
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:
case ByteCodeConst.TYPE_BYTE:
case ByteCodeConst.TYPE_CHAR:
case ByteCodeConst.TYPE_SHORT:
case ByteCodeConst.TYPE_INT:
case ByteCodeConst.TYPE_BOOL:
return ImmutablePair.of(ILOAD, 1);
case ClassUtil.TYPE_DOUBLE:
case ByteCodeConst.TYPE_DOUBLE:
return ImmutablePair.of(DLOAD, 2);
case ClassUtil.TYPE_FLOAT:
case ByteCodeConst.TYPE_FLOAT:
return ImmutablePair.of(FLOAD, 1);
case ClassUtil.TYPE_LONG:
case ByteCodeConst.TYPE_LONG:
return ImmutablePair.of(LLOAD, 2);
default:
return ImmutablePair.of(ALOAD, 1);

View File

@ -4,6 +4,7 @@ import com.alibaba.testable.agent.model.MethodInfo;
import com.alibaba.testable.agent.model.ModifiedInsnNodes;
import com.alibaba.testable.agent.util.BytecodeUtil;
import com.alibaba.testable.agent.util.ClassUtil;
import com.alibaba.testable.agent.util.MethodUtil;
import com.alibaba.testable.core.util.LogUtil;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
@ -115,7 +116,7 @@ public class SourceClassHandler extends BaseClassHandler {
String nodeOwner = ClassUtil.fitCompanionClassName(node.owner);
String nodeName = ClassUtil.fitKotlinAccessorName(node.name);
// Kotlin accessor method will append a extra type parameter
String nodeDesc = nodeName.equals(node.name) ? node.desc : ClassUtil.removeFirstParameter(node.desc);
String nodeDesc = nodeName.equals(node.name) ? node.desc : MethodUtil.removeFirstParameter(node.desc);
if (m.getClazz().equals(nodeOwner) && m.getName().equals(nodeName) && m.getDesc().equals(nodeDesc)) {
return m;
}
@ -161,7 +162,7 @@ public class SourceClassHandler extends BaseClassHandler {
}
private int getInitialStackLevel(MethodInsnNode instruction) {
int stackLevel = ClassUtil.getParameterTypes((instruction).desc).size();
int stackLevel = MethodUtil.getParameterTypes((instruction).desc).size();
switch (instruction.getOpcode()) {
case Opcodes.INVOKESPECIAL:
case Opcodes.INVOKEVIRTUAL:
@ -194,7 +195,7 @@ public class SourceClassHandler extends BaseClassHandler {
}
private int stackEffectOfInvocation(String desc) {
return ClassUtil.getParameterTypes(desc).size() - (ClassUtil.getReturnType(desc).equals(VOID_RES) ? 0 : 1);
return MethodUtil.getParameterTypes(desc).size() - (MethodUtil.getReturnType(desc).equals(VOID_RES) ? 0 : 1);
}
private ModifiedInsnNodes replaceNewOps(MethodNode mn, MethodInfo newOperatorInjectMethod,

View File

@ -6,6 +6,7 @@ import com.alibaba.testable.agent.tool.ImmutablePair;
import com.alibaba.testable.agent.util.AnnotationUtil;
import com.alibaba.testable.agent.util.ClassUtil;
import com.alibaba.testable.agent.util.DiagnoseUtil;
import com.alibaba.testable.agent.util.MethodUtil;
import com.alibaba.testable.core.util.LogUtil;
import com.alibaba.testable.core.util.MockAssociationUtil;
import org.objectweb.asm.Type;
@ -92,7 +93,7 @@ public class MockClassParser {
String fullClassName = toDotSeparateFullClassName(an.desc);
if (fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) {
LogUtil.verbose(" Mock constructor \"%s\" as \"(%s)V\" for \"%s\"", mn.name,
ClassUtil.extractParameters(mn.desc), ClassUtil.getReturnType(mn.desc));
MethodUtil.extractParameters(mn.desc), MethodUtil.getReturnType(mn.desc));
addMockConstructor(methodInfos, cn, mn);
} else if (fullClassName.equals(ConstPool.MOCK_METHOD)) {
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, getTargetMethodDesc(mn, an));
@ -114,7 +115,7 @@ public class MockClassParser {
private String getTargetMethodDesc(MethodNode mn, AnnotationNode mockMethodAnnotation) {
Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS,
null, Type.class);
return type == null ? ClassUtil.removeFirstParameter(mn.desc) : mn.desc;
return type == null ? MethodUtil.removeFirstParameter(mn.desc) : mn.desc;
}
private MethodInfo getMethodInfo(MethodNode mn, AnnotationNode an, String targetMethod) {
@ -131,7 +132,7 @@ public class MockClassParser {
// "targetClass" found, use it as target class type
String slashSeparatedName = ClassUtil.toSlashSeparatedName(targetType.getClassName());
return new MethodInfo(slashSeparatedName, targetMethod, mn.desc, mn.name,
ClassUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(slashSeparatedName)), isStatic);
MethodUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(slashSeparatedName)), isStatic);
}
}

View File

@ -7,11 +7,10 @@ import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodInsnNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.alibaba.testable.agent.constant.ByteCodeConst.*;
import static com.alibaba.testable.core.constant.ConstPool.MOCK_POSTFIX;
import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX;
import static org.objectweb.asm.Opcodes.*;
@ -21,20 +20,6 @@ import static org.objectweb.asm.Opcodes.*;
*/
public class ClassUtil {
public static final byte TYPE_BYTE = 'B';
public static final byte TYPE_CHAR = 'C';
public static final byte TYPE_DOUBLE = 'D';
public static final byte TYPE_FLOAT = 'F';
public static final byte TYPE_INT = 'I';
public static final byte TYPE_LONG = 'J';
public static final byte TYPE_CLASS = 'L';
public static final byte TYPE_SHORT = 'S';
public static final byte TYPE_BOOL = 'Z';
public static final byte TYPE_VOID = 'V';
private static final byte PARAM_END = ')';
private static final byte CLASS_END = ';';
private static final byte TYPE_ARRAY = '[';
public static final String CLASS_OBJECT = "java/lang/Object";
private static final String CLASS_BYTE = "java/lang/Byte";
private static final String CLASS_CHARACTER = "java/lang/Character";
@ -150,69 +135,6 @@ public class ClassUtil {
return testClassName.substring(0, testClassName.length() - TEST_POSTFIX.length());
}
/**
* parse method desc, fetch parameter types
* @param desc method description
* @return list of parameter types
*/
public static List<Byte> getParameterTypes(String desc) {
List<Byte> parameterTypes = new ArrayList<Byte>();
boolean travelingClass = false;
boolean travelingArray = false;
for (byte b : desc.getBytes()) {
if (travelingClass) {
if (b == CLASS_END) {
travelingClass = false;
travelingArray = false;
}
} else {
if (isPrimaryType(b)) {
// should treat primary array as class (issue-48)
parameterTypes.add(travelingArray ? TYPE_CLASS : b);
travelingArray = false;
} else if (b == TYPE_CLASS) {
travelingClass = true;
parameterTypes.add(b);
} else if (b == TYPE_ARRAY) {
travelingArray = true;
} else if (b == PARAM_END) {
break;
}
}
}
return parameterTypes;
}
/**
* extract parameter part of method desc
* @param desc method description
* @return parameter value
*/
public static String extractParameters(String desc) {
int returnTypeEdge = desc.lastIndexOf(PARAM_END);
return desc.substring(1, returnTypeEdge);
}
/**
* parse method desc, fetch return value types
* @param desc method description
* @return types of return value
*/
public static String getReturnType(String desc) {
int returnTypeEdge = desc.lastIndexOf(PARAM_END);
return desc.substring(returnTypeEdge + 1);
}
/**
* parse method desc, fetch first parameter type
* @param desc method description
* @return types of first parameter
*/
public static String getFirstParameter(String desc) {
int typeEdge = desc.indexOf(CLASS_END);
return desc.substring(1, typeEdge + 1);
}
/**
* get wrapper class of specified private type
* @param primaryType byte code of private type
@ -279,15 +201,6 @@ public class ClassUtil {
return (char)TYPE_CLASS + toSlashSeparatedName(className) + (char)CLASS_END;
}
/**
* convert byte code class name to slash separated human readable name
* @param className original name
* @return converted name
*/
public static String toSlashSeparateFullClassName(String className) {
return className.substring(1, className.length() - 1);
}
/**
* convert byte code class name to dot separated human readable name
* @param className original name
@ -297,25 +210,6 @@ public class ClassUtil {
return toDotSeparatedName(className).substring(1, className.length() - 1);
}
/**
* remove first parameter from method descriptor
* @param desc original descriptor
* @return descriptor without first parameter
*/
public static String removeFirstParameter(String desc) {
return "(" + desc.substring(desc.indexOf(";") + 1);
}
/**
* add extra parameter to the beginning of method descriptor
* @param desc original descriptor
* @param type byte code class name
* @return descriptor with specified parameter at begin
*/
public static String addParameterAtBegin(String desc, String type) {
return "(" + type + desc.substring(1);
}
/**
* Read class from current context
* @param className class name
@ -335,8 +229,4 @@ public class ClassUtil {
return "(" + (char)type.byteValue() + ")L" + objectType + ";";
}
private static boolean isPrimaryType(byte b) {
return b == TYPE_BYTE || b == TYPE_CHAR || b == TYPE_DOUBLE || b == TYPE_FLOAT
|| b == TYPE_INT || b == TYPE_LONG || b == TYPE_SHORT || b == TYPE_BOOL;
}
}

View File

@ -2,12 +2,107 @@ package com.alibaba.testable.agent.util;
import org.objectweb.asm.tree.MethodNode;
import java.util.ArrayList;
import java.util.List;
import static com.alibaba.testable.agent.constant.ByteCodeConst.*;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
public class MethodUtil {
/**
* Judge whether a method is static
* @param mn method to check
* @return is static or not
*/
public static boolean isStaticMethod(MethodNode mn) {
return (mn.access & ACC_STATIC) != 0;
}
/**
* parse method desc, fetch parameter types
* @param desc method description
* @return list of parameter types
*/
public static List<Byte> getParameterTypes(String desc) {
List<Byte> parameterTypes = new ArrayList<Byte>();
boolean travelingClass = false;
boolean travelingArray = false;
for (byte b : desc.getBytes()) {
if (travelingClass) {
if (b == CLASS_END) {
travelingClass = false;
travelingArray = false;
}
} else {
if (isPrimaryType(b)) {
// should treat primary array as class (issue-48)
parameterTypes.add(travelingArray ? TYPE_CLASS : b);
travelingArray = false;
} else if (b == TYPE_CLASS) {
travelingClass = true;
parameterTypes.add(b);
} else if (b == TYPE_ARRAY) {
travelingArray = true;
} else if (b == PARAM_END) {
break;
}
}
}
return parameterTypes;
}
/**
* extract parameter part of method desc
* @param desc method description
* @return parameter value
*/
public static String extractParameters(String desc) {
int returnTypeEdge = desc.lastIndexOf(PARAM_END);
return desc.substring(1, returnTypeEdge);
}
/**
* parse method desc, fetch return value types
* @param desc method description
* @return types of return value
*/
public static String getReturnType(String desc) {
int returnTypeEdge = desc.lastIndexOf(PARAM_END);
return desc.substring(returnTypeEdge + 1);
}
/**
* parse method desc, fetch first parameter type
* @param desc method description
* @return types of first parameter
*/
public static String getFirstParameter(String desc) {
int typeEdge = desc.indexOf(CLASS_END);
return desc.substring(1, typeEdge + 1);
}
/**
* remove first parameter from method descriptor
* @param desc original descriptor
* @return descriptor without first parameter
*/
public static String removeFirstParameter(String desc) {
return "(" + desc.substring(desc.indexOf(";") + 1);
}
/**
* add extra parameter to the beginning of method descriptor
* @param desc original descriptor
* @param type byte code class name
* @return descriptor with specified parameter at begin
*/
public static String addParameterAtBegin(String desc, String type) {
return "(" + type + desc.substring(1);
}
private static boolean isPrimaryType(byte b) {
return b == TYPE_BYTE || b == TYPE_CHAR || b == TYPE_DOUBLE || b == TYPE_FLOAT
|| b == TYPE_INT || b == TYPE_LONG || b == TYPE_SHORT || b == TYPE_BOOL;
}
}

View File

@ -2,39 +2,10 @@ package com.alibaba.testable.agent.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ClassUtilTest {
@Test
void should_able_to_get_parameter_count() {
assertEquals(0, ClassUtil.getParameterTypes("()V").size());
assertEquals(1, ClassUtil.getParameterTypes("(Ljava/lang/String;)V").size());
assertEquals(6, ClassUtil.getParameterTypes("(Ljava/lang/String;IDLjava/lang/String;ZLjava/net/URL;)V").size());
assertEquals(10, ClassUtil.getParameterTypes("(ZLjava/lang/String;IJFDCSBZ)V").size());
assertEquals(3, ClassUtil.getParameterTypes("(Ljava/lang/String;[I[Ljava/lang/String;)V").size());
}
@Test
void should_able_to_extract_parameter() {
assertEquals("", ClassUtil.extractParameters("()I"));
assertEquals("Ljava/lang/String;", ClassUtil.extractParameters("(Ljava/lang/String;)I"));
}
@Test
void should_able_to_get_return_type() {
assertEquals("V", ClassUtil.getReturnType("(Ljava/lang/String;)V"));
assertEquals("I", ClassUtil.getReturnType("(Ljava/lang/String;)I"));
assertEquals("[I", ClassUtil.getReturnType("(Ljava/lang/String;)[I"));
assertEquals("Ljava/lang/String;", ClassUtil.getReturnType("(Ljava/lang/String;)Ljava/lang/String;"));
assertEquals("[Ljava/lang/String;", ClassUtil.getReturnType("(Ljava/lang/String;)[Ljava/lang/String;"));
}
@Test
void should_able_to_get_first_parameter() {
assertEquals("Ljava/lang/String;", ClassUtil.getFirstParameter("(Ljava/lang/String;Ljava/lang/Object;I)V"));
}
@Test
void should_able_to_convert_class_name() {
assertEquals("Ljava/lang/String;", ClassUtil.toByteCodeClassName("java.lang.String"));

View File

@ -0,0 +1,39 @@
package com.alibaba.testable.agent.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class MethodUtilTest {
@Test
void should_able_to_get_parameter_count() {
assertEquals(0, MethodUtil.getParameterTypes("()V").size());
assertEquals(1, MethodUtil.getParameterTypes("(Ljava/lang/String;)V").size());
assertEquals(6, MethodUtil.getParameterTypes("(Ljava/lang/String;IDLjava/lang/String;ZLjava/net/URL;)V").size());
assertEquals(10, MethodUtil.getParameterTypes("(ZLjava/lang/String;IJFDCSBZ)V").size());
assertEquals(3, MethodUtil.getParameterTypes("(Ljava/lang/String;[I[Ljava/lang/String;)V").size());
}
@Test
void should_able_to_extract_parameter() {
assertEquals("", MethodUtil.extractParameters("()I"));
assertEquals("Ljava/lang/String;", MethodUtil.extractParameters("(Ljava/lang/String;)I"));
}
@Test
void should_able_to_get_return_type() {
assertEquals("V", MethodUtil.getReturnType("(Ljava/lang/String;)V"));
assertEquals("I", MethodUtil.getReturnType("(Ljava/lang/String;)I"));
assertEquals("[I", MethodUtil.getReturnType("(Ljava/lang/String;)[I"));
assertEquals("Ljava/lang/String;", MethodUtil.getReturnType("(Ljava/lang/String;)Ljava/lang/String;"));
assertEquals("[Ljava/lang/String;", MethodUtil.getReturnType("(Ljava/lang/String;)[Ljava/lang/String;"));
}
@Test
void should_able_to_get_first_parameter() {
assertEquals("Ljava/lang/String;", MethodUtil.getFirstParameter("(Ljava/lang/String;Ljava/lang/Object;I)V"));
}
}