diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java index 19f3aeb..dd42dd0 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java @@ -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 PRELOADED_CLASSES = mapOf( - entryOf(CLASS_OBJECT, CollectionUtil.arrayOf()), - entryOf("java/io/Reader", CollectionUtil.arrayOf()), - entryOf("java/io/Writer", CollectionUtil.arrayOf()), - entryOf("java/io/InputStream", CollectionUtil.arrayOf()), - entryOf("java/io/OutputStream", CollectionUtil.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.arrayOf()), - entryOf("java/lang/Number", CollectionUtil.arrayOf()), - entryOf("java/nio/CharBuffer", arrayOf("I", "I", "I", "I")), - entryOf("java/util/AbstractCollection", CollectionUtil.arrayOf()), - entryOf("java/util/HashSet", CollectionUtil.arrayOf()) + entryOf(CLASS_OBJECT, CollectionUtil.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)); diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/util/BytecodeUtil.java b/testable-agent/src/main/java/com/alibaba/testable/agent/util/BytecodeUtil.java index e7a814b..da8d5bb 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/util/BytecodeUtil.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/util/BytecodeUtil.java @@ -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 PRIMITIVE_TYPE_NAME_MAP = new HashMap() {{ + 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 diff --git a/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java b/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java index d5cfcec..a7540e5 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java @@ -235,7 +235,7 @@ public class OmniConstructor { private static Object createInstance(Class clazz, Set> 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; - } - } diff --git a/testable-core/src/main/java/com/alibaba/testable/core/util/TypeUtil.java b/testable-core/src/main/java/com/alibaba/testable/core/util/TypeUtil.java index 3834c38..cae3a80 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/util/TypeUtil.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/util/TypeUtil.java @@ -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