mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-19 09:43:29 +08:00
feat: find best constructor of super class
This commit is contained in:
@@ -3,9 +3,11 @@ package com.alibaba.testable.agent.handler;
|
||||
import com.alibaba.testable.agent.handler.test.JUnit4Framework;
|
||||
import com.alibaba.testable.agent.handler.test.JUnit5Framework;
|
||||
import com.alibaba.testable.agent.util.AnnotationUtil;
|
||||
import com.alibaba.testable.agent.util.BytecodeUtil;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import com.alibaba.testable.core.util.CollectionUtil;
|
||||
import com.alibaba.testable.core.util.StringUtil;
|
||||
import com.alibaba.testable.core.util.TypeUtil;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.*;
|
||||
@@ -36,21 +38,7 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
// below classes are loaded before OmniClassHandler, cannot be instrumented
|
||||
// map of class name to constructor parameters
|
||||
private static final Map<String, String[]> PRELOADED_CLASSES = mapOf(
|
||||
entryOf(CLASS_OBJECT, CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/io/Reader", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/io/Writer", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/io/InputStream", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/io/OutputStream", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/io/BufferedReader", arrayOf("Ljava/io/Reader;")),
|
||||
entryOf("java/io/BufferedWriter", arrayOf("Ljava/io/Reader;")),
|
||||
entryOf("java/io/BufferedInputStream", arrayOf("Ljava/io/Reader;")),
|
||||
entryOf("java/io/BufferedOutputStream", arrayOf("Ljava/io/Reader;")),
|
||||
entryOf("java/io/File", arrayOf("Ljava/lang/String;")),
|
||||
entryOf("java/lang/Thread", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/lang/Number", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/nio/CharBuffer", arrayOf("I", "I", "I", "I")),
|
||||
entryOf("java/util/AbstractCollection", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/util/HashSet", CollectionUtil.<String>arrayOf())
|
||||
entryOf(CLASS_OBJECT, CollectionUtil.<String>arrayOf())
|
||||
);
|
||||
|
||||
private static final String[] JUNIT_TEST_ANNOTATIONS = new String[] {
|
||||
@@ -71,19 +59,53 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
METHOD_START + ClassUtil.toByteCodeClassName(VOID_TYPE) + VOID_METHOD_END, null, null);
|
||||
LabelNode start = new LabelNode(new Label());
|
||||
LabelNode end = new LabelNode(new Label());
|
||||
int extraParameterCount = 2;
|
||||
|
||||
if (PRELOADED_CLASSES.containsKey(cn.superName)) {
|
||||
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, start, end);
|
||||
constructor.localVariables = createLocalVariables(cn, start, end);
|
||||
constructor.maxStack = 1 + PRELOADED_CLASSES.get(cn.superName).length;
|
||||
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, PRELOADED_CLASSES.get(cn.superName), start, end);
|
||||
extraParameterCount = PRELOADED_CLASSES.get(cn.superName).length;
|
||||
} else if (cn.superName.startsWith("java/")) {
|
||||
try {
|
||||
Class<?> superClazz = Class.forName(ClassUtil.toDotSeparatedName(cn.superName));
|
||||
Class<?>[] constructorParameterTypes = TypeUtil.getBestConstructor(superClazz).getParameterTypes();
|
||||
if (constructorParameterTypes.length == 0) {
|
||||
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, new String[0], start, end);
|
||||
extraParameterCount = 0;
|
||||
} else if (constructorParameterTypes.length == 1 &&
|
||||
constructorParameterTypes[0].equals(Void.class)) {
|
||||
constructor.instructions = invokeSuperWithTestableVoidParameter(cn.superName, start, end);
|
||||
} else {
|
||||
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName,
|
||||
toByteCodeClassNames(constructorParameterTypes), start, end);
|
||||
extraParameterCount = constructorParameterTypes.length;
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
constructor.instructions = invokeSuperWithTestableVoidParameter(cn.superName, start, end);
|
||||
}
|
||||
} else {
|
||||
constructor.instructions = invokeSuperWithTestableVoidParameter(cn.superName, start, end);
|
||||
constructor.localVariables = createLocalVariables(cn, start, end);
|
||||
constructor.maxStack = 3;
|
||||
}
|
||||
|
||||
constructor.localVariables = createLocalVariables(cn, start, end);
|
||||
constructor.maxStack = 1 + extraParameterCount;
|
||||
constructor.maxLocals = 2;
|
||||
cn.methods.add(constructor);
|
||||
}
|
||||
|
||||
private String[] toByteCodeClassNames(Class<?>[] classes) {
|
||||
String[] names = new String[classes.length];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
if (classes[i].isPrimitive()) {
|
||||
names[i] = BytecodeUtil.PRIMITIVE_TYPE_NAME_MAP.get(classes[i].getName());
|
||||
} else if (classes[i].isArray()) {
|
||||
names[i] = ClassUtil.toSlashSeparatedName(classes[i].getName());
|
||||
} else {
|
||||
names[i] = ClassUtil.toByteCodeClassName(classes[i].getName());
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private boolean isUninstantiableClass(ClassNode cn) {
|
||||
// if the class has no even default constructor, skip it
|
||||
for (MethodNode mn : cn.methods) {
|
||||
@@ -122,8 +144,7 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
private InsnList invokeSuperWithoutTestableParameter(String superName, LabelNode start, LabelNode end) {
|
||||
String[] parameters = PRELOADED_CLASSES.get(superName);
|
||||
private InsnList invokeSuperWithoutTestableParameter(String superName, String[] parameters, LabelNode start, LabelNode end) {
|
||||
InsnList il = new InsnList();
|
||||
il.add(start);
|
||||
il.add(new VarInsnNode(ALOAD, 0));
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ByteCodeConst.*;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
||||
import static com.alibaba.testable.core.constant.ConstPool.*;
|
||||
import static com.alibaba.testable.core.util.PathUtil.createFolder;
|
||||
@@ -186,6 +187,17 @@ public class BytecodeUtil {
|
||||
put(IFNONNULL, -1);
|
||||
}};
|
||||
|
||||
public static Map<String, String> PRIMITIVE_TYPE_NAME_MAP = new HashMap<String, String>() {{
|
||||
put("byte", String.valueOf((char)TYPE_BYTE));
|
||||
put("char", String.valueOf((char)TYPE_CHAR));
|
||||
put("double", String.valueOf((char)TYPE_DOUBLE));
|
||||
put("float", String.valueOf((char)TYPE_FLOAT));
|
||||
put("int", String.valueOf((char)TYPE_INT));
|
||||
put("long", String.valueOf((char)TYPE_LONG));
|
||||
put("short", String.valueOf((char)TYPE_SHORT));
|
||||
put("boolean", String.valueOf((char)TYPE_BOOL));
|
||||
}};
|
||||
|
||||
/**
|
||||
* Get stack impact of a specified ops code
|
||||
* @param bytecode ops code to check
|
||||
|
||||
@@ -235,7 +235,7 @@ public class OmniConstructor {
|
||||
|
||||
private static Object createInstance(Class<?> clazz, Set<Class<?>> classPool, ConstructionOption[] options)
|
||||
throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
Constructor<?> constructor = getBestConstructor(clazz);
|
||||
Constructor<?> constructor = TypeUtil.getBestConstructor(clazz);
|
||||
if (constructor == null) {
|
||||
throw new ClassConstructionException("Fail to invoke constructor of " + clazz.getName());
|
||||
}
|
||||
@@ -284,23 +284,4 @@ public class OmniConstructor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Constructor<?> getBestConstructor(Class<?> clazz) {
|
||||
Constructor<?> bestConstructor = null;
|
||||
int minimalExceptionCount = 999;
|
||||
int minimalParameterCount = 999;
|
||||
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
|
||||
Class<?>[] parameterTypes = constructor.getParameterTypes();
|
||||
Class<?>[] exceptionTypes = constructor.getExceptionTypes();
|
||||
if (parameterTypes.length == 1 && parameterTypes[0].equals(Void.class)) {
|
||||
return constructor;
|
||||
} else if (exceptionTypes.length < minimalExceptionCount
|
||||
|| (exceptionTypes.length == minimalExceptionCount && parameterTypes.length < minimalParameterCount)) {
|
||||
minimalExceptionCount = exceptionTypes.length;
|
||||
minimalParameterCount = parameterTypes.length;
|
||||
bestConstructor = constructor;
|
||||
}
|
||||
}
|
||||
return bestConstructor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -111,6 +111,30 @@ public class TypeUtil {
|
||||
|| clazz.equals(Class.class) || clazz.equals(String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* find the simplest constructor for specified class
|
||||
* @param clazz any class
|
||||
* @return best constructor
|
||||
*/
|
||||
public static Constructor<?> getBestConstructor(Class<?> clazz) {
|
||||
Constructor<?> bestConstructor = null;
|
||||
int minimalExceptionCount = 999;
|
||||
int minimalParameterCount = 999;
|
||||
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
|
||||
Class<?>[] parameterTypes = constructor.getParameterTypes();
|
||||
Class<?>[] exceptionTypes = constructor.getExceptionTypes();
|
||||
if (parameterTypes.length == 1 && parameterTypes[0].equals(Void.class)) {
|
||||
return constructor;
|
||||
} else if (exceptionTypes.length < minimalExceptionCount
|
||||
|| (exceptionTypes.length == minimalExceptionCount && parameterTypes.length < minimalParameterCount)) {
|
||||
minimalExceptionCount = exceptionTypes.length;
|
||||
minimalParameterCount = parameterTypes.length;
|
||||
bestConstructor = constructor;
|
||||
}
|
||||
}
|
||||
return bestConstructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* type equals
|
||||
* @param classesLeft class to be compared
|
||||
|
||||
Reference in New Issue
Block a user