feat: find best constructor of super class

This commit is contained in:
金戟
2022-12-11 08:10:46 +08:00
parent 12989aa612
commit c53f1e01f0
4 changed files with 80 additions and 42 deletions

View File

@@ -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.JUnit4Framework;
import com.alibaba.testable.agent.handler.test.JUnit5Framework; import com.alibaba.testable.agent.handler.test.JUnit5Framework;
import com.alibaba.testable.agent.util.AnnotationUtil; 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.agent.util.ClassUtil;
import com.alibaba.testable.core.util.CollectionUtil; import com.alibaba.testable.core.util.CollectionUtil;
import com.alibaba.testable.core.util.StringUtil; import com.alibaba.testable.core.util.StringUtil;
import com.alibaba.testable.core.util.TypeUtil;
import org.objectweb.asm.Label; import org.objectweb.asm.Label;
import org.objectweb.asm.Type; import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.*;
@@ -36,21 +38,7 @@ public class OmniClassHandler extends BaseClassHandler {
// below classes are loaded before OmniClassHandler, cannot be instrumented // below classes are loaded before OmniClassHandler, cannot be instrumented
// map of class name to constructor parameters // map of class name to constructor parameters
private static final Map<String, String[]> PRELOADED_CLASSES = mapOf( private static final Map<String, String[]> PRELOADED_CLASSES = mapOf(
entryOf(CLASS_OBJECT, CollectionUtil.<String>arrayOf()), 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())
); );
private static final String[] JUNIT_TEST_ANNOTATIONS = new String[] { 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); METHOD_START + ClassUtil.toByteCodeClassName(VOID_TYPE) + VOID_METHOD_END, null, null);
LabelNode start = new LabelNode(new Label()); LabelNode start = new LabelNode(new Label());
LabelNode end = new LabelNode(new Label()); LabelNode end = new LabelNode(new Label());
int extraParameterCount = 2;
if (PRELOADED_CLASSES.containsKey(cn.superName)) { if (PRELOADED_CLASSES.containsKey(cn.superName)) {
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, start, end); constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, PRELOADED_CLASSES.get(cn.superName), start, end);
constructor.localVariables = createLocalVariables(cn, start, end); extraParameterCount = PRELOADED_CLASSES.get(cn.superName).length;
constructor.maxStack = 1 + 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 { } else {
constructor.instructions = invokeSuperWithTestableVoidParameter(cn.superName, start, end); 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; constructor.maxLocals = 2;
cn.methods.add(constructor); 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) { private boolean isUninstantiableClass(ClassNode cn) {
// if the class has no even default constructor, skip it // if the class has no even default constructor, skip it
for (MethodNode mn : cn.methods) { for (MethodNode mn : cn.methods) {
@@ -122,8 +144,7 @@ public class OmniClassHandler extends BaseClassHandler {
return false; return false;
} }
private InsnList invokeSuperWithoutTestableParameter(String superName, LabelNode start, LabelNode end) { private InsnList invokeSuperWithoutTestableParameter(String superName, String[] parameters, LabelNode start, LabelNode end) {
String[] parameters = PRELOADED_CLASSES.get(superName);
InsnList il = new InsnList(); InsnList il = new InsnList();
il.add(start); il.add(start);
il.add(new VarInsnNode(ALOAD, 0)); il.add(new VarInsnNode(ALOAD, 0));

View File

@@ -10,6 +10,7 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.agent.constant.ConstPool.*;
import static com.alibaba.testable.core.constant.ConstPool.*; import static com.alibaba.testable.core.constant.ConstPool.*;
import static com.alibaba.testable.core.util.PathUtil.createFolder; import static com.alibaba.testable.core.util.PathUtil.createFolder;
@@ -186,6 +187,17 @@ public class BytecodeUtil {
put(IFNONNULL, -1); 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 * Get stack impact of a specified ops code
* @param bytecode ops code to check * @param bytecode ops code to check

View File

@@ -235,7 +235,7 @@ public class OmniConstructor {
private static Object createInstance(Class<?> clazz, Set<Class<?>> classPool, ConstructionOption[] options) private static Object createInstance(Class<?> clazz, Set<Class<?>> classPool, ConstructionOption[] options)
throws InstantiationException, IllegalAccessException, InvocationTargetException { throws InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<?> constructor = getBestConstructor(clazz); Constructor<?> constructor = TypeUtil.getBestConstructor(clazz);
if (constructor == null) { if (constructor == null) {
throw new ClassConstructionException("Fail to invoke constructor of " + clazz.getName()); throw new ClassConstructionException("Fail to invoke constructor of " + clazz.getName());
} }
@@ -284,23 +284,4 @@ public class OmniConstructor {
return null; 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;
}
} }

View File

@@ -111,6 +111,30 @@ public class TypeUtil {
|| clazz.equals(Class.class) || clazz.equals(String.class); || 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 * type equals
* @param classesLeft class to be compared * @param classesLeft class to be compared