feat: handle many special cases of system class creation

This commit is contained in:
金戟
2022-12-04 07:34:42 +08:00
parent ada70f88c0
commit 1446382f53
4 changed files with 54 additions and 41 deletions

View File

@@ -37,31 +37,21 @@ public class OmniClassHandler extends BaseClassHandler {
private static final String CLASS_ABSTRACT_COLLECTION = "java/util/AbstractCollection";
private static final String CLASS_NUMBER = "java/lang/Number";
private static final String CLASS_HASH_SET = "java/util/HashSet";
private static final String CLASS_READER = "java/io/Reader";
private static final String CLASS_WRITER = "java/io/Writer";
private static final String CLASS_BUFFERED_READER = "java/io/BufferedReader";
private static final String CLASS_BUFFERED_WRITER = "java/io/BufferedWriter";
private static final String CLASS_INPUT_STREAM = "java/io/InputStream";
private static final String CLASS_OUTPUT_STREAM = "java/io/OutputStream";
private static final String CLASS_BUFFERED_INPUT_STREAM = "java/io/BufferedInputStream";
private static final String CLASS_BUFFERED_OUTPUT_STREAM = "java/io/BufferedOutputStream";
private static final String CLASS_THREAD = "java/lang/Thread";
private static final String CLASS_FILE = "java/io/File";
// 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(CLASS_READER, CollectionUtil.<String>arrayOf()),
entryOf(CLASS_WRITER, CollectionUtil.<String>arrayOf()),
entryOf(CLASS_INPUT_STREAM, CollectionUtil.<String>arrayOf()),
entryOf(CLASS_OUTPUT_STREAM, CollectionUtil.<String>arrayOf()),
entryOf(CLASS_THREAD, CollectionUtil.<String>arrayOf()),
entryOf(CLASS_FILE, arrayOf("Ljava/lang/String;")),
entryOf(CLASS_BUFFERED_READER, arrayOf("Ljava/io/Reader;")),
entryOf(CLASS_BUFFERED_WRITER, arrayOf("Ljava/io/Reader;")),
entryOf(CLASS_BUFFERED_INPUT_STREAM, arrayOf("Ljava/io/Reader;")),
entryOf(CLASS_BUFFERED_OUTPUT_STREAM, arrayOf("Ljava/io/Reader;"))
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/lang/Thread", CollectionUtil.<String>arrayOf()),
entryOf("java/io/File", arrayOf("Ljava/lang/String;")),
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;"))
);
private static final String[] JUNIT_TEST_ANNOTATIONS = new String[] {
@@ -86,7 +76,7 @@ public class OmniClassHandler extends BaseClassHandler {
LabelNode start = new LabelNode(new Label());
LabelNode end = new LabelNode(new Label());
if (PRELOADED_CLASSES.containsKey(cn.superName)) {
constructor.instructions = invokeSuperWithoutParameter(cn.superName, start, end);
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, start, end);
constructor.localVariables = createLocalVariables(cn, start, end);
constructor.maxStack = 1 + PRELOADED_CLASSES.get(cn.superName).length;
} else {
@@ -136,7 +126,7 @@ public class OmniClassHandler extends BaseClassHandler {
return false;
}
private InsnList invokeSuperWithoutParameter(String superName, LabelNode start, LabelNode end) {
private InsnList invokeSuperWithoutTestableParameter(String superName, LabelNode start, LabelNode end) {
String[] parameters = PRELOADED_CLASSES.get(superName);
InsnList il = new InsnList();
il.add(start);

View File

@@ -67,9 +67,9 @@ public class AnnotationUtil {
* @return yes or no
*/
public static boolean hasAllAnnotationParameters(AnnotationNode an, String... keys) {
boolean found = false;
if (an != null && an.values != null) {
for (String key : keys) {
boolean found = false;
for (int i = 0; i < an.values.size(); i += 2) {
if (an.values.get(i).equals(key)) {
found = true;
@@ -80,8 +80,9 @@ public class AnnotationUtil {
return false;
}
}
return true;
}
return true;
return false;
}
/**

View File

@@ -8,6 +8,7 @@ import com.alibaba.testable.core.util.LogUtil;
import com.alibaba.testable.core.util.TypeUtil;
import java.lang.reflect.*;
import java.nio.charset.Charset;
import java.util.*;
import static com.alibaba.testable.core.constant.ConstPool.DOLLAR;
@@ -61,6 +62,10 @@ public class OmniConstructor {
return null;
}
classPool.add(clazz);
T ins = createSpecialClass(clazz);
if (ins != null) {
return ins;
}
try {
if (clazz.isPrimitive()) {
return newPrimitive(clazz);
@@ -229,10 +234,6 @@ public class OmniConstructor {
private static Object createInstance(Class<?> clazz, Set<Class<?>> classPool, ConstructionOption[] options)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
Object ins = createSpecialClass(clazz);
if (ins != null) {
return ins;
}
Constructor<?> constructor = getBestConstructor(clazz);
if (constructor == null) {
throw new ClassConstructionException("Fail to invoke constructor of " + clazz.getName());
@@ -250,14 +251,31 @@ public class OmniConstructor {
}
}
private static Object createSpecialClass(Class<?> clazz) throws InstantiationException, IllegalAccessException, InvocationTargetException {
try {
// below classes are loaded before OmniClassHandler, cannot be instrumented
if (clazz.getName().equals("java.net.URL")) {
private static <T> T createSpecialClass(Class<T> clazz) {
if (clazz.getName().equals("java.net.URL")) {
// loaded before OmniClassHandler, cannot be instrumented
try {
return clazz.getDeclaredConstructor(String.class).newInstance("https://");
} catch (Exception e) {
return null;
}
} else if (clazz.getName().equals("java.nio.charset.Charset")) {
// better to use its default instance
return (T) Charset.defaultCharset();
} else if (clazz.getName().equals("java.nio.ByteBuffer")) {
// has package-private abstract methods
try {
return (T) newInstance(Class.forName("java.nio.DirectByteBuffer"));
} catch (ClassNotFoundException e) {
return null;
}
} else if (clazz.getName().equals("java.nio.CharBuffer")) {
// has package-private abstract methods
try {
return (T) newInstance(Class.forName("java.nio.StringCharBuffer"));
} catch (ClassNotFoundException e) {
return null;
}
} catch (NoSuchMethodException e) {
return null;
}
return null;
}

View File

@@ -42,20 +42,23 @@ public class ConstructionUtil {
}
}
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> finalMethods, Map<String, String> genericTypes) {
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> methodPool, Map<String, String> genericTypes) {
Set<String> methods = new HashSet<String>();
// in a very special situation, getDeclaredMethods() could fetch method declaration in the parent interface
// as none-abstract, that will cause the corresponding abstract method in current class be skipped.
// it happens to e.g. CharSequence.subSequence(int,int) and CharBuffer.subSequence(int,int)
// so we would pass the methods to pool after all method in current level have been handled.
Set<String> thisLevelMethodPool = new HashSet<String>();
for (Method m : clazz.getDeclaredMethods()) {
StringBuilder methodSignatureBuilder = new StringBuilder(m.getName());
for (Type p : m.getGenericParameterTypes()) {
methodSignatureBuilder.append("#").append(getParameterName(p, genericTypes));
}
String methodSignature = methodSignatureBuilder.toString();
if (finalMethods.contains(methodSignature)) {
if (methodPool.contains(methodSignature)) {
continue;
}
if (Modifier.isFinal(m.getModifiers())) {
finalMethods.add(methodSignature);
}
thisLevelMethodPool.add(methodSignature);
if (Modifier.isAbstract(m.getModifiers())) {
StringBuilder sourceCode = new StringBuilder();
sourceCode.append("\tpublic ")
@@ -82,6 +85,7 @@ public class ConstructionUtil {
methods.add(sourceCode.toString());
}
}
methodPool.addAll(thisLevelMethodPool);
List<Type> superTypes = new ArrayList<Type>(Arrays.asList(clazz.getGenericInterfaces()));
if (clazz.getGenericSuperclass() != null) {
superTypes.add(clazz.getGenericSuperclass());
@@ -89,9 +93,9 @@ public class ConstructionUtil {
for (Type t : superTypes) {
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), finalMethods, parseGenericTypes(pt)));
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), methodPool, parseGenericTypes(pt)));
} else if (t instanceof Class) {
methods.addAll(generateMethodsOf((Class<?>) t, finalMethods, Collections.<String, String>emptyMap()));
methods.addAll(generateMethodsOf((Class<?>) t, methodPool, Collections.<String, String>emptyMap()));
}
}
return methods;