diff --git a/agent/src/main/java/com/alibaba/testable/handler/TestableClassHandler.java b/agent/src/main/java/com/alibaba/testable/handler/TestableClassHandler.java new file mode 100644 index 0000000..dbb6659 --- /dev/null +++ b/agent/src/main/java/com/alibaba/testable/handler/TestableClassHandler.java @@ -0,0 +1,93 @@ +package com.alibaba.testable.transformer; + +import com.alibaba.testable.model.TravelStatus; +import com.alibaba.testable.util.ClassUtil; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.alibaba.testable.constant.Const.SYS_CLASSES; + +/** + * @author flin + */ +public class TestableClassTransformer implements Opcodes { + + private static final String CONSTRUCTOR = ""; + private static final String TESTABLE_NE = "n/e"; + 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() { + List methodNames = new ArrayList(); + for (MethodNode m : cn.methods) { + if (!CONSTRUCTOR.equals(m.name)) { + methodNames.add(m.name); + } + } + for (MethodNode m : cn.methods) { + transformMethod(m, methodNames); + } + } + + private void transformMethod(MethodNode mn, List methodNames) { + AbstractInsnNode[] instructions = mn.instructions.toArray(); + TravelStatus status = TravelStatus.INIT; + String target = ""; + int rangeStart = 0; + int i = 0; + do { + if (instructions[i].getOpcode() == Opcodes.NEW) { + TypeInsnNode node = (TypeInsnNode)instructions[i]; + if (!SYS_CLASSES.contains(node.desc)) { + target = node.desc; + status = TravelStatus.NEW_REP; + rangeStart = i; + } + } else if (instructions[i].getOpcode() == Opcodes.INVOKESPECIAL) { + MethodInsnNode node = (MethodInsnNode)instructions[i]; + if (methodNames.contains(node.name) && cn.name.equals(node.owner)) { + status = TravelStatus.MEM_REP; + } else if (TravelStatus.NEW_REP == status && CONSTRUCTOR.equals(node.name) && target.equals(node.owner)) { + instructions = replaceNewOps(mn, instructions, rangeStart, i); + i = rangeStart; + status = TravelStatus.INIT; + } + } + i++; + } while (i < instructions.length); + } + + private AbstractInsnNode[] replaceNewOps(MethodNode mn, AbstractInsnNode[] instructions, int start, int end) { + String classType = ((TypeInsnNode)instructions[start]).desc; + String paramTypes = ((MethodInsnNode)instructions[end]).desc; + mn.instructions.insertBefore(instructions[start], new LdcInsnNode(Type.getType("L" + classType + ";"))); + InsnList il = new InsnList(); + il.add(new MethodInsnNode(INVOKESTATIC, TESTABLE_NE, "w", ClassUtil.generateTargetDesc(paramTypes), false)); + il.add(new TypeInsnNode(CHECKCAST, classType)); + mn.instructions.insertBefore(instructions[end], il); + mn.instructions.remove(instructions[start]); + mn.instructions.remove(instructions[start + 1]); + mn.instructions.remove(instructions[end]); + mn.maxStack += 1; + return mn.instructions.toArray(); + } + +} diff --git a/agent/src/main/java/com/alibaba/testable/transformer/TestableClassTransformer.java b/agent/src/main/java/com/alibaba/testable/transformer/TestableClassTransformer.java index dbb6659..47e8197 100644 --- a/agent/src/main/java/com/alibaba/testable/transformer/TestableClassTransformer.java +++ b/agent/src/main/java/com/alibaba/testable/transformer/TestableClassTransformer.java @@ -1,93 +1,56 @@ package com.alibaba.testable.transformer; -import com.alibaba.testable.model.TravelStatus; import com.alibaba.testable.util.ClassUtil; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; import java.io.IOException; -import java.util.ArrayList; +import java.lang.instrument.ClassFileTransformer; +import java.net.URLClassLoader; +import java.security.ProtectionDomain; +import java.util.HashSet; import java.util.List; - -import static com.alibaba.testable.constant.Const.SYS_CLASSES; +import java.util.Set; /** * @author flin */ -public class TestableClassTransformer implements Opcodes { +public class TestableFileTransformer implements ClassFileTransformer { - private static final String CONSTRUCTOR = ""; - private static final String TESTABLE_NE = "n/e"; - private final ClassNode cn = new ClassNode(); + private static final String ENABLE_TESTABLE = "com.alibaba.testable.annotation.EnableTestable"; + private static final String ENABLE_TESTABLE_INJECT = "com.alibaba.testable.annotation.EnableTestableInject"; + private static final String TEST_POSTFIX = "Test"; - public TestableClassTransformer(String className) throws IOException { - ClassReader cr = new ClassReader(className); - cr.accept(cn, 0); - } + private static final Set loadedClassNames = new HashSet(); - public byte[] getBytes() { - transform(); - ClassWriter cw = new ClassWriter( 0); - cn.accept(cw); - return cw.toByteArray(); - } - - private void transform() { - List methodNames = new ArrayList(); - for (MethodNode m : cn.methods) { - if (!CONSTRUCTOR.equals(m.name)) { - methodNames.add(m.name); - } + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) { + if (isSystemClass(loader, className) || loadedClassNames.contains(className)) { + // Ignore system class and duplicate class + return null; } - for (MethodNode m : cn.methods) { - transformMethod(m, methodNames); + + 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; + } + + try { + loadedClassNames.add(className); + return new TestableClassTransformer(className).getBytes(); + } catch (IOException e) { + return null; } } - private void transformMethod(MethodNode mn, List methodNames) { - AbstractInsnNode[] instructions = mn.instructions.toArray(); - TravelStatus status = TravelStatus.INIT; - String target = ""; - int rangeStart = 0; - int i = 0; - do { - if (instructions[i].getOpcode() == Opcodes.NEW) { - TypeInsnNode node = (TypeInsnNode)instructions[i]; - if (!SYS_CLASSES.contains(node.desc)) { - target = node.desc; - status = TravelStatus.NEW_REP; - rangeStart = i; - } - } else if (instructions[i].getOpcode() == Opcodes.INVOKESPECIAL) { - MethodInsnNode node = (MethodInsnNode)instructions[i]; - if (methodNames.contains(node.name) && cn.name.equals(node.owner)) { - status = TravelStatus.MEM_REP; - } else if (TravelStatus.NEW_REP == status && CONSTRUCTOR.equals(node.name) && target.equals(node.owner)) { - instructions = replaceNewOps(mn, instructions, rangeStart, i); - i = rangeStart; - status = TravelStatus.INIT; - } - } - i++; - } while (i < instructions.length); + private boolean isSystemClass(ClassLoader loader, String className) { + return !(loader instanceof URLClassLoader) || null == className || className.startsWith("jdk/"); } - private AbstractInsnNode[] replaceNewOps(MethodNode mn, AbstractInsnNode[] instructions, int start, int end) { - String classType = ((TypeInsnNode)instructions[start]).desc; - String paramTypes = ((MethodInsnNode)instructions[end]).desc; - mn.instructions.insertBefore(instructions[start], new LdcInsnNode(Type.getType("L" + classType + ";"))); - InsnList il = new InsnList(); - il.add(new MethodInsnNode(INVOKESTATIC, TESTABLE_NE, "w", ClassUtil.generateTargetDesc(paramTypes), false)); - il.add(new TypeInsnNode(CHECKCAST, classType)); - mn.instructions.insertBefore(instructions[end], il); - mn.instructions.remove(instructions[start]); - mn.instructions.remove(instructions[start + 1]); - mn.instructions.remove(instructions[end]); - mn.maxStack += 1; - return mn.instructions.toArray(); + 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/transformer/TestableFileTransformer.java b/agent/src/main/java/com/alibaba/testable/transformer/TestableFileTransformer.java deleted file mode 100644 index 47e8197..0000000 --- a/agent/src/main/java/com/alibaba/testable/transformer/TestableFileTransformer.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.alibaba.testable.transformer; - -import com.alibaba.testable.util.ClassUtil; - -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; - -/** - * @author flin - */ -public class TestableFileTransformer implements ClassFileTransformer { - - private static final String ENABLE_TESTABLE = "com.alibaba.testable.annotation.EnableTestable"; - 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 (isSystemClass(loader, className) || loadedClassNames.contains(className)) { - // Ignore system class and duplicate class - return null; - } - - 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; - } - - try { - loadedClassNames.add(className); - return new TestableClassTransformer(className).getBytes(); - } catch (IOException e) { - return null; - } - } - - 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))); - } - -}