avoid source class be re-transformed

This commit is contained in:
金戟 2020-12-27 11:34:25 +08:00
parent 13fa8a76ce
commit 28bb6454c8
4 changed files with 29 additions and 22 deletions

View File

@ -1,17 +1,35 @@
package com.alibaba.testable.agent.handler; package com.alibaba.testable.agent.handler;
import com.alibaba.testable.core.util.LogUtil;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator;
/** /**
* @author flin * @author flin
*/ */
abstract public class BaseClassHandler implements Opcodes { abstract public class BaseClassHandler implements Opcodes {
protected static final String TESTABLE_MARK_FIELD = "__testable";
protected boolean wasTransformed(ClassNode cn) {
Iterator<FieldNode> iterator = cn.fields.iterator();
if (iterator.hasNext()) {
if (TESTABLE_MARK_FIELD.equals(iterator.next().name)) {
// avoid duplicate injection
LogUtil.verbose("Duplicate injection found, ignore " + cn.name);
return true;
}
}
cn.fields.add(new FieldNode(ACC_PRIVATE, TESTABLE_MARK_FIELD, "I", null, null));
return false;
}
public byte[] getBytes(byte[] classFileBuffer) throws IOException { public byte[] getBytes(byte[] classFileBuffer) throws IOException {
ClassReader cr = new ClassReader(classFileBuffer); ClassReader cr = new ClassReader(classFileBuffer);
ClassNode cn = new ClassNode(); ClassNode cn = new ClassNode();

View File

@ -36,6 +36,9 @@ public class SourceClassHandler extends BaseClassHandler {
*/ */
@Override @Override
protected void transform(ClassNode cn) { protected void transform(ClassNode cn) {
if (wasTransformed(cn)) {
return;
}
Set<MethodInfo> memberInjectMethods = new HashSet<MethodInfo>(); Set<MethodInfo> memberInjectMethods = new HashSet<MethodInfo>();
Set<MethodInfo> newOperatorInjectMethods = new HashSet<MethodInfo>(); Set<MethodInfo> newOperatorInjectMethods = new HashSet<MethodInfo>();
for (MethodInfo mi : injectMethods) { for (MethodInfo mi : injectMethods) {

View File

@ -7,7 +7,6 @@ import com.alibaba.testable.agent.util.ClassUtil;
import com.alibaba.testable.core.util.LogUtil; import com.alibaba.testable.core.util.LogUtil;
import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.*;
import java.util.Iterator;
import java.util.List; import java.util.List;
import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName; import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName;
@ -20,7 +19,6 @@ public class TestClassHandler extends BaseClassHandler {
private static final String CLASS_TESTABLE_TOOL = "com/alibaba/testable/core/tool/TestableTool"; private static final String CLASS_TESTABLE_TOOL = "com/alibaba/testable/core/tool/TestableTool";
private static final String CLASS_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil"; private static final String CLASS_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil";
private static final String CLASS_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil"; private static final String CLASS_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil";
private static final String TESTABLE_MARK_FIELD = "__testable";
private static final String FIELD_TEST_CASE = "TEST_CASE"; private static final String FIELD_TEST_CASE = "TEST_CASE";
private static final String FIELD_SOURCE_METHOD = "SOURCE_METHOD"; private static final String FIELD_SOURCE_METHOD = "SOURCE_METHOD";
private static final String METHOD_CURRENT_TEST_CASE_NAME = "currentTestCaseName"; private static final String METHOD_CURRENT_TEST_CASE_NAME = "currentTestCaseName";
@ -40,32 +38,19 @@ public class TestClassHandler extends BaseClassHandler {
return; return;
} }
for (MethodNode mn : cn.methods) { for (MethodNode mn : cn.methods) {
handleMockMethod(mn); handleMockMethod(cn, mn);
handleInstruction(cn, mn); handleInstruction(cn, mn);
} }
} }
private boolean wasTransformed(ClassNode cn) { private void handleMockMethod(ClassNode cn, MethodNode mn) {
Iterator<FieldNode> iterator = cn.fields.iterator();
if (iterator.hasNext()) {
if (TESTABLE_MARK_FIELD.equals(iterator.next().name)) {
// avoid duplicate injection
LogUtil.verbose("Duplicate injection found, ignore " + cn.name);
return true;
}
}
cn.fields.add(new FieldNode(ACC_PRIVATE, TESTABLE_MARK_FIELD, "I", null, null));
return false;
}
private void handleMockMethod(MethodNode mn) {
if (isMockMethod(mn)) { if (isMockMethod(mn)) {
toPublicStatic(mn); toPublicStatic(cn, mn);
injectInvokeRecorder(mn); injectInvokeRecorder(mn);
} }
} }
private void toPublicStatic(MethodNode mn) { private void toPublicStatic(ClassNode cn, MethodNode mn) {
mn.access &= ~ACC_PRIVATE; mn.access &= ~ACC_PRIVATE;
mn.access &= ~ACC_PROTECTED; mn.access &= ~ACC_PROTECTED;
mn.access |= ACC_PUBLIC; mn.access |= ACC_PUBLIC;

View File

@ -45,7 +45,6 @@ public class TestableClassTransformer implements ClassFileTransformer {
ProtectionDomain protectionDomain, byte[] classFileBuffer) { ProtectionDomain protectionDomain, byte[] classFileBuffer) {
if (isSystemClass(className)) { if (isSystemClass(className)) {
// Ignore system class and reloaded class // Ignore system class and reloaded class
LogUtil.verbose("Ignore class: " + (className == null ? "<lambda>" : className));
return null; return null;
} }
LogUtil.verbose("Handle class: " + className); LogUtil.verbose("Handle class: " + className);
@ -65,8 +64,9 @@ public class TestableClassTransformer implements ClassFileTransformer {
dumpByte(className, bytes); dumpByte(className, bytes);
resetMockContext(); resetMockContext();
} }
} catch (IOException e) { } catch (Throwable t) {
LogUtil.warn("Failed to transform class " + className); LogUtil.warn("Failed to transform class " + className);
LogUtil.diagnose(t.toString());
} }
return bytes; return bytes;
} }
@ -77,7 +77,8 @@ public class TestableClassTransformer implements ClassFileTransformer {
return; return;
} }
try { try {
String dumpFile = StringUtil.joinPath(dumpDir, className.replaceAll("/", "_") + ".class"); String dumpFile = StringUtil.joinPath(dumpDir, className.replaceAll("/", ".") + ".class");
LogUtil.verbose("Dump class: " + dumpFile);
FileOutputStream stream = new FileOutputStream(dumpFile); FileOutputStream stream = new FileOutputStream(dumpFile);
stream.write(bytes); stream.write(bytes);
stream.close(); stream.close();