diff --git a/agent/pom.xml b/agent/pom.xml index 4af2ea1..4e81fbd 100755 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -8,11 +8,15 @@ 0.0.2-SNAPSHOT jar + + 8.0.1 + + org.ow2.asm - asm - 8.0.1 + asm-tree + ${asm-lib-version} diff --git a/agent/src/main/java/com/alibaba/testable/constant/Const.java b/agent/src/main/java/com/alibaba/testable/constant/Const.java new file mode 100644 index 0000000..9ab1b5c --- /dev/null +++ b/agent/src/main/java/com/alibaba/testable/constant/Const.java @@ -0,0 +1,11 @@ +package com.alibaba.testable.constant; + +/** + * @author flin + */ +public class Const { + + public static final String DOT = "."; + public static final String SLASH = "/"; + +} diff --git a/agent/src/main/java/com/alibaba/testable/transformer/TestableClassTransformer.java b/agent/src/main/java/com/alibaba/testable/transformer/TestableClassTransformer.java new file mode 100644 index 0000000..77acb45 --- /dev/null +++ b/agent/src/main/java/com/alibaba/testable/transformer/TestableClassTransformer.java @@ -0,0 +1,33 @@ +package com.alibaba.testable.transformer; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; + +import java.io.IOException; + +/** + * @author flin + */ +public class TestableClassTransformer implements Opcodes { + + private final ClassNode cn = new ClassNode(); + + public TestableClassTransformer(String className) throws IOException { + ClassReader cr = new ClassReader(className); + cr.accept(cn, 0); + } + + public byte[] getBytes() { + transform(); + ClassWriter cw = new ClassWriter( 0); + cn.accept(cw); + return cw.toByteArray(); + } + + private void transform() { + + } + +} diff --git a/agent/src/main/java/com/alibaba/testable/transformer/TestableFileTransformer.java b/agent/src/main/java/com/alibaba/testable/transformer/TestableFileTransformer.java index 9fa54bf..147a8da 100644 --- a/agent/src/main/java/com/alibaba/testable/transformer/TestableFileTransformer.java +++ b/agent/src/main/java/com/alibaba/testable/transformer/TestableFileTransformer.java @@ -1,14 +1,13 @@ package com.alibaba.testable.transformer; -import com.alibaba.testable.visitor.MethodRecordVisitor; -import com.alibaba.testable.visitor.TestableVisitor; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; +import com.alibaba.testable.util.ClassUtil; -import java.lang.annotation.Annotation; +import java.io.IOException; import java.lang.instrument.ClassFileTransformer; +import java.net.URLClassLoader; import java.security.ProtectionDomain; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -17,55 +16,40 @@ import java.util.Set; public class TestableFileTransformer implements ClassFileTransformer { private static final String ENABLE_TESTABLE = "com.alibaba.testable.annotation.EnableTestable"; - private static final String DOT = "."; - private static final String SLASH = "/"; + private static final String ENABLE_TESTABLE_INJECT = "com.alibaba.testable.annotation.EnableTestableInject"; private static final String TEST_POSTFIX = "Test"; private static final Set loadedClassNames = new HashSet(); public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { - if (null == loader || null == className) { + if (isSystemClass(loader, className)) { // Ignore system class return null; } - String dotClassName = className.replace(SLASH, DOT); - loadedClassNames.add(dotClassName); - MethodRecordVisitor methodRecordVisitor = getMemberMethods(classfileBuffer, checkTestClass(dotClassName)); - if (!methodRecordVisitor.isNeedTransform()) { + + List annotations = ClassUtil.getAnnotations(className); + List testAnnotations = ClassUtil.getAnnotations(className + TEST_POSTFIX); + if (!isNeedTransform(annotations, testAnnotations)) { // Neither EnableTestable on test class, nor EnableTestableInject on source class return null; } - ClassReader reader = new ClassReader(classfileBuffer); - ClassWriter writer = new ClassWriter(reader, 0); - reader.accept(new TestableVisitor(writer, methodRecordVisitor.getMethods()), 0); - return writer.toByteArray(); - } - - private boolean checkTestClass(String dotClassName) { - String testClassName = dotClassName + TEST_POSTFIX; - if (loadedClassNames.contains(testClassName)) { - try { - Class testClazz = Class.forName(testClassName); - for (Annotation a : testClazz.getAnnotations()) { - if (a.annotationType().getName().equals(ENABLE_TESTABLE)) { - return true; - } - } - } catch (ClassNotFoundException e) { - return false; - } + try { + return new TestableClassTransformer(className).getBytes(); + } catch (IOException e) { + return null; } - return false; } - private MethodRecordVisitor getMemberMethods(byte[] classfileBuffer, boolean needTransform) { - ClassReader reader = new ClassReader(classfileBuffer); - ClassWriter writer = new ClassWriter(reader, 0); - MethodRecordVisitor visitor = new MethodRecordVisitor(writer, needTransform); - reader.accept(visitor, 0); - return visitor; + private boolean isSystemClass(ClassLoader loader, String className) { + return !(loader instanceof URLClassLoader) || null == className || className.startsWith("jdk/"); + } + + private boolean isNeedTransform(List annotations, List testAnnotations) { + return annotations != null && + (annotations.contains(ENABLE_TESTABLE_INJECT) || + (testAnnotations != null && testAnnotations.contains(ENABLE_TESTABLE))); } } diff --git a/agent/src/main/java/com/alibaba/testable/util/ClassUtil.java b/agent/src/main/java/com/alibaba/testable/util/ClassUtil.java new file mode 100644 index 0000000..651da3b --- /dev/null +++ b/agent/src/main/java/com/alibaba/testable/util/ClassUtil.java @@ -0,0 +1,33 @@ +package com.alibaba.testable.util; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.alibaba.testable.constant.Const.*; + +/** + * @author flin + */ +public class ClassUtil { + + public static List getAnnotations(String className) { + try { + List annotations = new ArrayList(); + ClassNode cn = new ClassNode(); + new ClassReader(className).accept(cn, 0); + for (AnnotationNode an : cn.visibleAnnotations) { + String annotationName = an.desc.replace(SLASH, DOT).substring(1, an.desc.length() - 1); + annotations.add(annotationName); + } + return annotations; + } catch (IOException e) { + return null; + } + } + +} diff --git a/agent/src/main/java/com/alibaba/testable/visitor/MethodRecordVisitor.java b/agent/src/main/java/com/alibaba/testable/visitor/MethodRecordVisitor.java deleted file mode 100755 index 4eb1124..0000000 --- a/agent/src/main/java/com/alibaba/testable/visitor/MethodRecordVisitor.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.alibaba.testable.visitor; - -import com.alibaba.testable.model.MethodInfo; -import org.objectweb.asm.*; - -import java.util.ArrayList; -import java.util.List; - -public class MethodRecordVisitor extends ClassVisitor { - - /** - * Member methods - */ - private final List methods = new ArrayList(); - private boolean needTransform; - - private static final String ENABLE_TESTABLE_INJECT = "Lcom/alibaba/testable/annotation/EnableTestableInject;"; - - public List getMethods() { - return methods; - } - - public boolean isNeedTransform() { - return needTransform; - } - - public MethodRecordVisitor(ClassWriter cw, boolean needTransform) { - super(Opcodes.ASM8, cw); - this.needTransform = needTransform; - } - - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - if (descriptor.equals(ENABLE_TESTABLE_INJECT)) { - needTransform = true; - } - return super.visitAnnotation(descriptor, visible); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - methods.add(new MethodInfo(access, name, desc, signature, exceptions)); - return super.visitMethod(access, name, desc, signature, exceptions); - } -}