add bytekit module

This commit is contained in:
hengyunabc 2020-04-12 03:42:51 +08:00
parent 43c8c88afa
commit e330bdc2f6
107 changed files with 9530 additions and 0 deletions

100
bytekit/pom.xml Normal file
View File

@ -0,0 +1,100 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-all</artifactId>
<version>3.1.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>arthas-bytekit</artifactId>
<name>arthas-bytekit</name>
<dependencies>
<dependency>
<groupId>com.alibaba.arthas</groupId>
<artifactId>arthas-repackage-asm</artifactId>
</dependency>
<dependency>
<groupId>org.benf</groupId>
<artifactId>cfr</artifactId>
<version>0.132</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.7.10</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.7.10</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
<scope>provided</scope>
</dependency>
<!-- Test Artifacts -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.9.1</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>1.5.9.RELEASE</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>arthas-bytekit</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<encoding>UTF-8</encoding>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,22 @@
package com.taobao.arthas.bytekit;
import java.util.List;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorClassParser;
import com.taobao.arthas.bytekit.asm.matcher.ClassMatcher;
import com.taobao.arthas.bytekit.asm.matcher.MethodMatcher;
public class ByteKit {
private ClassMatcher classMatcher;
private MethodMatcher methodMatcher;
private Class<?> interceptorClass;
private InterceptorClassParser interceptorClassParser;
private List<InterceptorProcessor> interceptorProcessors;
}

View File

@ -0,0 +1,86 @@
package com.taobao.arthas.bytekit.asm;
import com.alibaba.arthas.deps.org.objectweb.asm.Label;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.commons.LocalVariablesSorter;
/**
* Adapter for to be inlined code.
*
* This adapter does all parameter renaming and replacing of the RETURN opcodes
*
*
*/
public class InliningAdapter extends LocalVariablesSorter {
private final Label end;
private LocalVariablesSorter lvs;
public InliningAdapter(LocalVariablesSorter mv, int access, String desc, Label end) {
super(Opcodes.ASM7, access, desc, mv);
this.end = end;
this.lvs = mv;
// int off = (access & Opcodes.ACC_STATIC) != 0 ?
// 0 : 1;
// Type[] args = Type.getArgumentTypes(desc);
// for (int i = args.length - 1; i >= 0; i--) {
// super.visitVarInsn(args[i].getOpcode(
// Opcodes.ISTORE), i + off);
// }
// if (off > 0) {
// super.visitVarInsn(Opcodes.ASTORE, 0);
// }
// save args to local vars
int off = (access & Opcodes.ACC_STATIC) != 0 ? 0 : 1;
Type[] args = Type.getArgumentTypes(desc);
int argsOff = off;
for(int i = 0; i < args.length; ++i) {
argsOff += args[i].getSize();
}
for(int i = args.length - 1; i >= 0; --i) {
argsOff -= args[i].getSize();
this.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), argsOff);
}
// this
if (off > 0) {
this.visitVarInsn(Opcodes.ASTORE, 0);
}
}
@Override
public void visitInsn(int opcode) {
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
super.visitJumpInsn(Opcodes.GOTO, end);
} else {
super.visitInsn(opcode);
}
}
@Override
public void visitMaxs(int stack, int locals) {
// super.visitMaxs(stack, locals);
}
@Override
protected int newLocalMapping(Type type) {
return lvs.newLocal(type);
}
@Override
public void visitVarInsn(int opcode, int var) {
super.visitVarInsn(opcode, var + this.firstLocal);
}
@Override
public void visitIincInsn(int var, int increment) {
super.visitIincInsn(var + this.firstLocal, increment);
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
super.visitLocalVariable(name, desc, signature, start, end, index + this.firstLocal);
}
}

View File

@ -0,0 +1,97 @@
package com.taobao.arthas.bytekit.asm;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Label;
import com.alibaba.arthas.deps.org.objectweb.asm.MethodVisitor;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.commons.GeneratorAdapter;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
/**
* @author hengyunabc 2018-01-31
*
*/
public abstract class MethodCallInliner extends GeneratorAdapter {
public class CatchBlock {
private Label start;
private Label handler;
private String type;
private Label end;
public CatchBlock(Label start, Label end, Label handler, String type) {
this.start = start;
this.end = end;
this.handler = handler;
this.type = type;
}
}
private final MethodNode toBeInlined;
private List<CatchBlock> blocks = new ArrayList<CatchBlock>();
private boolean inlining;
private boolean afterInlining;
public MethodCallInliner(int access, String name, String desc, MethodVisitor mv,
MethodNode toBeInlined) {
super(Opcodes.ASM7, mv, access, name, desc);
this.toBeInlined = toBeInlined;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (!shouldBeInlined(owner, name, desc)) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
// if (this.analyzerAdapter != null) {
// mv = new MergeFrameAdapter(this.api, this.analyzerAdapter,
// (MethodVisitor)mv);
// }
Label end = new Label();
inlining = true;
toBeInlined.instructions.resetLabels();
// pass the to be inlined method through the inlining adapter to this
toBeInlined.accept(new InliningAdapter(this, toBeInlined.access, toBeInlined.desc, end));
inlining = false;
afterInlining = true;
// visit the end label
super.visitLabel(end);
// box the return value if necessary
// Type returnType =
// Type.getMethodType(toBeInlined.desc).getReturnType();
// valueOf(returnType);
}
abstract boolean shouldBeInlined(String owner, String name, String desc);
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
if (!inlining) {
blocks.add(new CatchBlock(start, end, handler, type));
} else {
super.visitTryCatchBlock(start, end, handler, type);
}
}
@Override
public void visitMaxs(int stack, int locals) {
for (CatchBlock b : blocks)
super.visitTryCatchBlock(b.start, b.end, b.handler, b.type);
super.visitMaxs(stack, locals);
}
@Override
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
// swallow
}
}

View File

@ -0,0 +1,48 @@
package com.taobao.arthas.bytekit.asm;
/**
*
* @author hengyunabc 2019-03-18
*
*/
public class MethodInfo {
private String owner;
private int access;
private String name;
private String desc;
public int getAccess() {
return access;
}
public void setAccess(int access) {
this.access = access;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}

View File

@ -0,0 +1,766 @@
package com.taobao.arthas.bytekit.asm;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.commons.Method;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.FrameNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.IntInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.JumpInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LdcInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.TryCatchBlockNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.TypeInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.VarInsnNode;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
public class MethodProcessor {
private String owner;
/**
* maybe null
*/
private ClassNode classNode;
private MethodNode methodNode;
private final Type[] argumentTypes;
private final Type returnType;
private int nextLocals;
private static final Type BYTE_TYPE = Type.getObjectType("java/lang/Byte");
private static final Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean");
private static final Type SHORT_TYPE = Type.getObjectType("java/lang/Short");
private static final Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character");
private static final Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer");
private static final Type FLOAT_TYPE = Type.getObjectType("java/lang/Float");
private static final Type LONG_TYPE = Type.getObjectType("java/lang/Long");
private static final Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double");
private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
private static final Type STRING_TYPE = Type.getObjectType("java/lang/String");
private static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable");
private static final Type NUMBER_TYPE = Type.getObjectType("java/lang/Number");
private static final Type OBJECT_ARRAY_TYPE = Type.getType(Object[].class);
private static final Method BOOLEAN_VALUE = Method.getMethod("boolean booleanValue()");
private static final Method CHAR_VALUE = Method.getMethod("char charValue()");
private static final Method INT_VALUE = Method.getMethod("int intValue()");
private static final Method FLOAT_VALUE = Method.getMethod("float floatValue()");
private static final Method LONG_VALUE = Method.getMethod("long longValue()");
private static final Method DOUBLE_VALUE = Method.getMethod("double doubleValue()");
public static final String DEFAULT_INNER_VARIABLE_PREFIX = "_$bytekit$_";
private final LabelNode interceptorVariableStartLabelNode = new LabelNode();
private final LabelNode interceptorVariableEndLabelNode = new LabelNode();
private AbstractInsnNode enterInsnNode;
// TODO 这里应该直接从 InsnList 里来取因为插入代码之后这个会改变的
// TODO 这个没有被使用到是不是没用的
private AbstractInsnNode lastInsnNode;
/**
* 保留中间生成的 variable的名字
*/
private boolean keepLocalVariableNames;
private String innerVariablePrefix;
private String returnVariableName;
private String throwVariableName;
private String invokeArgsVariableName;
private String monitorVariableName;
private LocalVariableNode returnVariableNode = null;
private LocalVariableNode throwVariableNode = null;
private LocalVariableNode invokeArgsVariableNode = null;
private LocalVariableNode monitorVariableNode = null; // for synchronized
private String invokeReturnVariablePrefix;
private Map<String, LocalVariableNode> invokeReturnVariableNodeMap = new HashMap<String, LocalVariableNode>();
private TryCatchBlock tryCatchBlock = null;
public MethodProcessor(final ClassNode classNode, final MethodNode methodNode) {
this(classNode, methodNode, false);
}
public MethodProcessor(final ClassNode classNode, final MethodNode methodNode, boolean keepLocalVariableNames) {
this(classNode.name, methodNode, keepLocalVariableNames);
this.classNode = classNode;
}
public MethodProcessor(final String owner, final MethodNode methodNode, boolean keepLocalVariableNames) {
this.owner = owner;
this.methodNode = methodNode;
this.nextLocals = methodNode.maxLocals;
this.argumentTypes = Type.getArgumentTypes(methodNode.desc);
this.returnType = Type.getReturnType(methodNode.desc);
this.keepLocalVariableNames = keepLocalVariableNames;
// find enter & exit instruction.
if (isConstructor()) {
this.enterInsnNode = findInitConstructorInstruction();
} else {
this.enterInsnNode = methodNode.instructions.getFirst();
}
// when the method is empty, both enterInsnNode and lastInsnNode are Opcodes.RETURN ;
this.lastInsnNode = methodNode.instructions.getLast();
// setup interceptor variables start/end label.
this.methodNode.instructions.insertBefore(this.enterInsnNode, this.interceptorVariableStartLabelNode);
this.methodNode.instructions.insert(this.lastInsnNode, this.interceptorVariableEndLabelNode);
initInnerVariablePrefix();
}
public MethodProcessor(final String owner, final MethodNode methodNode) {
this(owner, methodNode, false);
}
private void initInnerVariablePrefix() {
String prefix = DEFAULT_INNER_VARIABLE_PREFIX;
int count = 0;
while(existLocalVariableWithPrefix(prefix)) {
prefix = DEFAULT_INNER_VARIABLE_PREFIX + count + "_";
count++;
}
this.innerVariablePrefix = prefix;
returnVariableName = innerVariablePrefix + "_return";
throwVariableName = innerVariablePrefix + "_throw";
invokeArgsVariableName = innerVariablePrefix + "_invokeArgs";
monitorVariableName = innerVariablePrefix + "_monitor";
invokeReturnVariablePrefix = innerVariablePrefix + "_invokeReturn_";
}
private boolean existLocalVariableWithPrefix(String prefix) {
for (LocalVariableNode variableNode : this.methodNode.localVariables) {
if (variableNode.name.startsWith(prefix)) {
return true;
}
}
return false;
}
public LocalVariableNode initMonitorVariableNode() {
if (monitorVariableNode == null) {
monitorVariableNode = this.addInterceptorLocalVariable(monitorVariableName, OBJECT_TYPE.getDescriptor());
}
return monitorVariableNode;
}
public LocalVariableNode initThrowVariableNode() {
if (throwVariableNode == null) {
throwVariableNode = this.addInterceptorLocalVariable(throwVariableName, THROWABLE_TYPE.getDescriptor());
}
return throwVariableNode;
}
public LocalVariableNode initInvokeArgsVariableNode() {
if (invokeArgsVariableNode == null) {
invokeArgsVariableNode = this.addInterceptorLocalVariable(invokeArgsVariableName,
OBJECT_ARRAY_TYPE.getDescriptor());
}
return invokeArgsVariableNode;
}
public LocalVariableNode initReturnVariableNode() {
if (returnVariableNode == null) {
returnVariableNode = this.addInterceptorLocalVariable(returnVariableName, returnType.getDescriptor());
}
return returnVariableNode;
}
/**
*
* @param name
* @param type
* @return
*/
public LocalVariableNode initInvokeReturnVariableNode(String name, Type type) {
String key = this.invokeReturnVariablePrefix + name;
LocalVariableNode variableNode = invokeReturnVariableNodeMap.get(key);
if (variableNode == null) {
variableNode = this.addInterceptorLocalVariable(key, type.getDescriptor());
invokeReturnVariableNodeMap.put(key, variableNode);
}
return variableNode;
}
public TryCatchBlock initTryCatchBlock() {
return initTryCatchBlock(THROWABLE_TYPE.getInternalName());
}
public TryCatchBlock initTryCatchBlock(String exception) {
if( this.tryCatchBlock == null) {
this.tryCatchBlock = new TryCatchBlock(methodNode, exception);
this.methodNode.instructions.insertBefore(this.getEnterInsnNode(), tryCatchBlock.getStartLabelNode());
this.methodNode.instructions.insert(this.getLastInsnNode(), tryCatchBlock.getEndLabelNode());
InsnList instructions = new InsnList();
AsmOpUtils.throwException(instructions);
this.methodNode.instructions.insert(tryCatchBlock.getEndLabelNode(), instructions);
tryCatchBlock.sort();
}
return tryCatchBlock;
}
AbstractInsnNode findInitConstructorInstruction() {
int nested = 0;
for (AbstractInsnNode insnNode = this.methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode
.getNext()) {
if (insnNode instanceof TypeInsnNode) {
if (insnNode.getOpcode() == Opcodes.NEW) {
// new object().
nested++;
}
} else if (insnNode instanceof MethodInsnNode) {
final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
if (methodInsnNode.getOpcode() == Opcodes.INVOKESPECIAL && methodInsnNode.name.equals("<init>")) {
if (--nested < 0) {
// find this() or super().
return insnNode.getNext();
}
}
}
}
return null;
}
public AbstractInsnNode getEnterInsnNode() {
return enterInsnNode;
}
public AbstractInsnNode getLastInsnNode() {
return lastInsnNode;
}
public String[] getParameterTypes() {
final String[] parameterTypes = new String[this.argumentTypes.length];
for (int i = 0; i < this.argumentTypes.length; i++) {
parameterTypes[i] = this.argumentTypes[i].getClassName();
}
return parameterTypes;
}
public String[] getParameterNames() {
if (this.argumentTypes.length == 0) {
return new String[0];
}
final List<LocalVariableNode> localVariableNodes = this.methodNode.localVariables;
int localVariableStartIndex = 1;
if (isStatic()) {
// static method is none this.
localVariableStartIndex = 0;
}
if (localVariableNodes == null || localVariableNodes.size() <= localVariableStartIndex
|| (this.argumentTypes.length + localVariableStartIndex) > localVariableNodes.size()) {
// make simple argument names.
final String[] names = new String[this.argumentTypes.length];
for (int i = 0; i < this.argumentTypes.length; i++) {
final String className = this.argumentTypes[i].getClassName();
if (className != null) {
final int findIndex = className.lastIndexOf('.');
if (findIndex == -1) {
names[i] = className;
} else {
names[i] = className.substring(findIndex + 1);
}
} else {
names[i] = this.argumentTypes[i].getDescriptor();
}
}
return names;
}
// sort by index.
Collections.sort(localVariableNodes, new Comparator<LocalVariableNode>() {
@Override
public int compare(LocalVariableNode o1, LocalVariableNode o2) {
return o1.index - o2.index;
}
});
String[] names = new String[this.argumentTypes.length];
for (int i = 0; i < this.argumentTypes.length; i++) {
final String name = localVariableNodes.get(localVariableStartIndex++).name;
if (name != null) {
names[i] = name;
} else {
names[i] = "";
}
}
return names;
}
public Type getReturnType() {
return this.returnType;
}
private boolean hasLocalVariable(String name) {
List<LocalVariableNode> localVariableNodes = this.methodNode.localVariables;
if (localVariableNodes == null) {
return false;
}
for (LocalVariableNode node : localVariableNodes) {
if (node.name.equals(name)) {
return true;
}
}
return false;
}
public void loadThis(final InsnList instructions) {
if (isConstructor()) {
// load this.
loadVar(instructions, 0);
} else {
if (isStatic()) {
// load null.
loadNull(instructions);
} else {
// load this.
loadVar(instructions, 0);
}
}
}
void storeVar(final InsnList instructions, final int index) {
instructions.add(new VarInsnNode(Opcodes.ASTORE, index));
}
void storeInt(final InsnList instructions, final int index) {
instructions.add(new VarInsnNode(Opcodes.ISTORE, index));
}
void loadNull(final InsnList instructions) {
instructions.add(new InsnNode(Opcodes.ACONST_NULL));
}
void loadVar(final InsnList instructions, final int index) {
instructions.add(new VarInsnNode(Opcodes.ALOAD, index));
}
void loadInt(final InsnList instructions, final int index) {
instructions.add(new VarInsnNode(Opcodes.ILOAD, index));
}
boolean isReturnCode(final int opcode) {
return opcode == Opcodes.IRETURN || opcode == Opcodes.LRETURN || opcode == Opcodes.FRETURN
|| opcode == Opcodes.DRETURN || opcode == Opcodes.ARETURN || opcode == Opcodes.RETURN;
}
Type getBoxedType(final Type type) {
switch (type.getSort()) {
case Type.BYTE:
return BYTE_TYPE;
case Type.BOOLEAN:
return BOOLEAN_TYPE;
case Type.SHORT:
return SHORT_TYPE;
case Type.CHAR:
return CHARACTER_TYPE;
case Type.INT:
return INTEGER_TYPE;
case Type.FLOAT:
return FLOAT_TYPE;
case Type.LONG:
return LONG_TYPE;
case Type.DOUBLE:
return DOUBLE_TYPE;
}
return type;
}
void push(InsnList insnList, final int value) {
if (value >= -1 && value <= 5) {
insnList.add(new InsnNode(Opcodes.ICONST_0 + value));
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
insnList.add(new IntInsnNode(Opcodes.BIPUSH, value));
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
insnList.add(new IntInsnNode(Opcodes.SIPUSH, value));
} else {
insnList.add(new LdcInsnNode(value));
}
}
void push(InsnList insnList, final String value) {
if (value == null) {
insnList.add(new InsnNode(Opcodes.ACONST_NULL));
} else {
insnList.add(new LdcInsnNode(value));
}
}
void newArray(final InsnList insnList, final Type type) {
insnList.add(new TypeInsnNode(Opcodes.ANEWARRAY, type.getInternalName()));
}
void dup(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP));
}
void dup2(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP2));
}
void dupX1(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP_X1));
}
void dupX2(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP_X2));
}
void pop(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.POP));
}
void swap(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.SWAP));
}
void loadArgsVar(final InsnList instructions) {
if (this.argumentTypes.length == 0) {
// null.
loadNull(instructions);
return;
}
push(instructions, this.argumentTypes.length);
// new array
newArray(instructions, OBJECT_TYPE);
for (int i = 0; i < this.argumentTypes.length; i++) {
Type type = this.argumentTypes[i];
dup(instructions);
push(instructions, i);
// loadArg
loadArg(instructions, this.argumentTypes, i);
// box
box(instructions, type);
// arrayStore
arrayStore(instructions, OBJECT_TYPE);
}
}
void loadArgs(final InsnList instructions) {
for (int i = 0; i < this.argumentTypes.length; i++) {
loadArg(instructions, this.argumentTypes, i);
}
}
void loadArg(final InsnList instructions, Type[] argumentTypes, int i) {
final int index = getArgIndex(argumentTypes, i);
final Type type = argumentTypes[i];
instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), index));
}
int getArgIndex(final Type[] argumentTypes, final int arg) {
int index = isStatic() ? 0 : 1;
for (int i = 0; i < arg; i++) {
index += argumentTypes[i].getSize();
}
return index;
}
void box(final InsnList instructions, Type type) {
if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return;
}
if (type == Type.VOID_TYPE) {
// push null
instructions.add(new InsnNode(Opcodes.ACONST_NULL));
} else {
Type boxed = getBoxedType(type);
// new instance.
newInstance(instructions, boxed);
if (type.getSize() == 2) {
// Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
// dupX2
dupX2(instructions);
// dupX2
dupX2(instructions);
// pop
pop(instructions);
} else {
// p -> po -> opo -> oop -> o
// dupX1
dupX1(instructions);
// swap
swap(instructions);
}
invokeConstructor(instructions, boxed, new Method("<init>", Type.VOID_TYPE, new Type[] { type }));
}
}
void unbox(final InsnList instructions, Type type) {
Type t = NUMBER_TYPE;
Method sig = null;
switch (type.getSort()) {
case Type.VOID:
return;
case Type.CHAR:
t = CHARACTER_TYPE;
sig = CHAR_VALUE;
break;
case Type.BOOLEAN:
t = BOOLEAN_TYPE;
sig = BOOLEAN_VALUE;
break;
case Type.DOUBLE:
sig = DOUBLE_VALUE;
break;
case Type.FLOAT:
sig = FLOAT_VALUE;
break;
case Type.LONG:
sig = LONG_VALUE;
break;
case Type.INT:
case Type.SHORT:
case Type.BYTE:
sig = INT_VALUE;
}
if (sig == null) {
instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, type.getInternalName()));
} else {
instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, t.getInternalName()));
instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, t.getInternalName(), sig.getName(),
sig.getDescriptor(), false));
}
}
void arrayStore(final InsnList instructions, final Type type) {
instructions.add(new InsnNode(type.getOpcode(Opcodes.IASTORE)));
}
void arrayLoad(final InsnList instructions, final Type type) {
instructions.add(new InsnNode(type.getOpcode(Opcodes.IALOAD)));
}
void newInstance(final InsnList instructions, final Type type) {
instructions.add(new TypeInsnNode(Opcodes.NEW, type.getInternalName()));
}
void invokeConstructor(final InsnList instructions, final Type type, final Method method) {
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
instructions
.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, owner, method.getName(), method.getDescriptor(), false));
}
LocalVariableNode addInterceptorLocalVariable(final String name, final String desc) {
return addLocalVariable(name, desc, this.interceptorVariableStartLabelNode,
this.interceptorVariableEndLabelNode);
}
LocalVariableNode addLocalVariable(final String name, final String desc, final LabelNode start,
final LabelNode end) {
Type type = Type.getType(desc);
int index = this.nextLocals;
this.nextLocals += type.getSize();
methodNode.maxLocals = this.nextLocals;
final LocalVariableNode node = new LocalVariableNode(name, desc, null, start, end, index);
if (keepLocalVariableNames) {
this.methodNode.localVariables.add(node);
}
return node;
}
public void returnValue(final InsnList instructions) {
instructions.add(new InsnNode(this.returnType.getOpcode(Opcodes.IRETURN)));
}
public boolean isStatic() {
return (this.methodNode.access & Opcodes.ACC_STATIC) != 0;
}
public boolean isConstructor() {
return this.methodNode.name != null && this.methodNode.name.equals("<init>");
}
public MethodNode getMethodNode() {
return methodNode;
}
public void setMethodNode(MethodNode methodNode) {
this.methodNode = methodNode;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public ClassNode getClassNode() {
return classNode;
}
public void setClassNode(ClassNode classNode) {
this.classNode = classNode;
}
/**
* TODO 可以考虑实现修改值的功能原理是传入的 args实际转化为一个stack上的slot只要在inline之后 stack上面的对应的slot保存到想要保存的位置就可以了
* @param owner
* @param tmpToInlineMethodNode
*/
public void inline(String owner, MethodNode toInlineMethodNode) {
ListIterator<AbstractInsnNode> originMethodIter = this.methodNode.instructions.iterator();
while(originMethodIter.hasNext()) {
AbstractInsnNode originMethodInsnNode = originMethodIter.next();
if (originMethodInsnNode instanceof MethodInsnNode) {
MethodInsnNode methodInsnNode = (MethodInsnNode) originMethodInsnNode;
if (methodInsnNode.owner.equals(owner) && methodInsnNode.name.equals(toInlineMethodNode.name)
&& methodInsnNode.desc.equals(toInlineMethodNode.desc)) {
// 要copy一份否则inline多次会出问题
MethodNode tmpToInlineMethodNode = AsmUtils.copy(toInlineMethodNode);
tmpToInlineMethodNode = AsmUtils.removeLineNumbers(tmpToInlineMethodNode);
LabelNode end = new LabelNode();
this.methodNode.instructions.insert(methodInsnNode, end);
InsnList instructions = new InsnList();
// 要先记录好当前的 maxLocals 然后再依次把 栈上的 args保存起来 后面调整 VarInsnNode index里要加上当前的 maxLocals
// save args to local vars
int currentMaxLocals = this.nextLocals;
int off = (tmpToInlineMethodNode.access & Opcodes.ACC_STATIC) != 0 ? 0 : 1;
Type[] args = Type.getArgumentTypes(tmpToInlineMethodNode.desc);
int argsOff = off;
for(int i = 0; i < args.length; ++i) {
argsOff += args[i].getSize();
}
// 记录新的 maxLocals
this.nextLocals += argsOff;
methodNode.maxLocals = this.nextLocals;
for(int i = args.length - 1; i >= 0; --i) {
argsOff -= args[i].getSize();
// this.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), argsOff);
AsmOpUtils.storeVar(instructions, args[i], currentMaxLocals + argsOff);
}
// this
if (off > 0) {
// this.visitVarInsn(Opcodes.ASTORE, 0);
AsmOpUtils.storeVar(instructions, OBJECT_TYPE, currentMaxLocals);
}
ListIterator<AbstractInsnNode> inlineIterator = tmpToInlineMethodNode.instructions.iterator();
while(inlineIterator.hasNext()) {
AbstractInsnNode abstractInsnNode = inlineIterator.next();
if(abstractInsnNode instanceof FrameNode) {
continue;
}
if(abstractInsnNode instanceof VarInsnNode) {
VarInsnNode varInsnNode = (VarInsnNode) abstractInsnNode;
varInsnNode.var += currentMaxLocals;
}
int opcode = abstractInsnNode.getOpcode();
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
// super.visitJumpInsn(Opcodes.GOTO, end);
// instructions.add(new JumpInsnNode(Opcodes.GOTO, end));
inlineIterator.remove();
instructions.add(new JumpInsnNode(Opcodes.GOTO, end));
continue;
}
inlineIterator.remove();
instructions.add(abstractInsnNode);
}
// 插入inline之后的代码再删除掉原来的 MethodInsnNode
this.methodNode.instructions.insertBefore(methodInsnNode, instructions);
originMethodIter.remove();
// try catch 块加上然后排序
if(this.methodNode.tryCatchBlocks != null && tmpToInlineMethodNode.tryCatchBlocks != null) {
this.methodNode.tryCatchBlocks.addAll(tmpToInlineMethodNode.tryCatchBlocks);
}
this.sortTryCatchBlock();
}
}
}
}
public void sortTryCatchBlock() {
if (this.methodNode.tryCatchBlocks == null) {
return;
}
// Compares TryCatchBlockNodes by the length of their "try" block.
Collections.sort(this.methodNode.tryCatchBlocks, new Comparator<TryCatchBlockNode>() {
@Override
public int compare(TryCatchBlockNode t1, TryCatchBlockNode t2) {
int len1 = blockLength(t1);
int len2 = blockLength(t2);
return len1 - len2;
}
private int blockLength(TryCatchBlockNode block) {
final int startidx = methodNode.instructions.indexOf(block.start);
final int endidx = methodNode.instructions.indexOf(block.end);
return endidx - startidx;
}
});
// Updates the 'target' of each try catch block annotation.
for (int i = 0; i < this.methodNode.tryCatchBlocks.size(); i++) {
this.methodNode.tryCatchBlocks.get(i).updateIndex(i);
}
}
}

View File

@ -0,0 +1,64 @@
package com.taobao.arthas.bytekit.asm;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.TryCatchBlockNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class MyTryCatchBlock {
private final MethodNode methodNode;
private final LabelNode startLabelNode = new LabelNode();
private final LabelNode endLabelNode = new LabelNode();
private final LabelNode handlerLabelNode = new LabelNode();
public MyTryCatchBlock(final MethodNode methodNode) {
this.methodNode = methodNode;
final TryCatchBlockNode tryCatchBlockNode = new TryCatchBlockNode(this.startLabelNode, this.endLabelNode, this.handlerLabelNode, "java/lang/Throwable");
if (this.methodNode.tryCatchBlocks == null) {
this.methodNode.tryCatchBlocks = new ArrayList<TryCatchBlockNode>();
}
this.methodNode.tryCatchBlocks.add(tryCatchBlockNode);
}
public LabelNode getStartLabelNode() {
return this.startLabelNode;
}
public LabelNode getEndLabelNode() {
return this.endLabelNode;
}
public LabelNode getHandlerLabelNode() {
return this.handlerLabelNode;
}
public void sort() {
if (this.methodNode.tryCatchBlocks == null) {
return;
}
// Compares TryCatchBlockNodes by the length of their "try" block.
Collections.sort(this.methodNode.tryCatchBlocks, new Comparator<TryCatchBlockNode>() {
@Override
public int compare(TryCatchBlockNode t1, TryCatchBlockNode t2) {
int len1 = blockLength(t1);
int len2 = blockLength(t2);
return len1 - len2;
}
private int blockLength(TryCatchBlockNode block) {
final int startidx = methodNode.instructions.indexOf(block.start);
final int endidx = methodNode.instructions.indexOf(block.end);
return endidx - startidx;
}
});
// Updates the 'target' of each try catch block annotation.
for (int i = 0; i < this.methodNode.tryCatchBlocks.size(); i++) {
this.methodNode.tryCatchBlocks.get(i).updateIndex(i);
}
}
}

View File

@ -0,0 +1,66 @@
package com.taobao.arthas.bytekit.asm;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.TryCatchBlockNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class TryCatchBlock {
private final MethodNode methodNode;
private final LabelNode startLabelNode = new LabelNode();
private final LabelNode endLabelNode = new LabelNode();
public TryCatchBlock(final MethodNode methodNode) {
this(methodNode, Type.getType(Throwable.class).getInternalName());
}
public TryCatchBlock(final MethodNode methodNode, String exception) {
this.methodNode = methodNode;
final TryCatchBlockNode tryCatchBlockNode = new TryCatchBlockNode(this.startLabelNode, this.endLabelNode,
this.endLabelNode, exception);
if (this.methodNode.tryCatchBlocks == null) {
this.methodNode.tryCatchBlocks = new ArrayList<TryCatchBlockNode>();
}
this.methodNode.tryCatchBlocks.add(tryCatchBlockNode);
}
public LabelNode getStartLabelNode() {
return this.startLabelNode;
}
public LabelNode getEndLabelNode() {
return this.endLabelNode;
}
public void sort() {
if (this.methodNode.tryCatchBlocks == null) {
return;
}
// Compares TryCatchBlockNodes by the length of their "try" block.
Collections.sort(this.methodNode.tryCatchBlocks, new Comparator<TryCatchBlockNode>() {
@Override
public int compare(TryCatchBlockNode t1, TryCatchBlockNode t2) {
int len1 = blockLength(t1);
int len2 = blockLength(t2);
return len1 - len2;
}
private int blockLength(TryCatchBlockNode block) {
final int startidx = methodNode.instructions.indexOf(block.start);
final int endidx = methodNode.instructions.indexOf(block.end);
return endidx - startidx;
}
});
// Updates the 'target' of each try catch block annotation.
for (int i = 0; i < this.methodNode.tryCatchBlocks.size(); i++) {
this.methodNode.tryCatchBlocks.get(i).updateIndex(i);
}
}
}

View File

@ -0,0 +1,431 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2008-10 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @authors Andrew Dinn
*/
package com.taobao.arthas.bytekit.asm;
/**
* Helpoer class providing static methods for manipulating type and class names,
* field and method descriptor names etc
*/
public class TypeHelper {
public static boolean equalDescriptors(String desc1, String desc2)
{
int idx1 = 0, idx2 = 0;
int len1 = desc1.length(), len2 = desc2.length();
while (idx1 < len1) {
// check the other has not dropped off the end
if (idx2 == len2) {
if ((idx1 == (len1 - 1)) && (desc1.charAt(idx1) == '$')) {
return true;
}
return false;
}
// check type is the same
char char1 = desc1.charAt(idx1);
char char2 = desc2.charAt(idx2);
// if we have a $ at the end of the descriptor then this means any return
// type so special case this
if ((char1 == '$' && idx1 == len1 - 1) || (char2 == '$' && idx2 == len2 - 1)) {
return true;
}
// otherwise the chars must match
if (char1 != char2) {
return false;
}
// however an L indicates a class name and we allow a classname without a package
// to match a class name with a package
if (char1 == 'L') {
// ok, ensure the names must match modulo a missing package
int end1 = idx1 + 1;
int end2 = idx2 + 1;
while (end1 < len1 && desc1.charAt(end1) != ';') {
end1++;
}
while (end2 < len2 && desc2.charAt(end2) != ';') {
end2++;
}
if (end1 == len1 || end2 == len2) {
// bad format for desc!!
return false;
}
String typeName1 = desc1.substring(idx1 + 1, end1);
String typeName2 = desc2.substring(idx2 + 1, end2);
if (!typeName1.equals(typeName2)) {
int tailIdx1 = typeName1.lastIndexOf('/');
int tailIdx2 = typeName2.lastIndexOf('/');
if (tailIdx1 > 0) {
if (tailIdx2 > 0) {
// both specify packages so they must be different types
return false;
} else {
// only type 1 specifies a package so type 2 should match the tail
if (!typeName2.equals(typeName1.substring(tailIdx1 + 1))) {
return false;
}
}
} else {
if (tailIdx2 > 0) {
// only type 2 specifies a package so type 1 should match the tail
if (!typeName1.equals(typeName2.substring(tailIdx2 + 1))) {
return false;
}
} else {
// neither specify packages so they must be different types
return false;
}
}
}
// skp past ';'s
idx1 = end1;
idx2 = end2;
}
idx1++;
idx2++;
}
// check the other has not reached the end
if (idx2 != len2) {
return false;
}
return true;
}
/**
* convert a classname from canonical form to the form used to represent it externally i.e. replace
* all dots with slashes
*
* @param className the canonical name
* @return the external name
*/
public static String externalizeClass(String className)
{
return className.replace('.', '/');
}
/**
* convert a classname from external form to canonical form i.e. replace
* all slashes with dots
*
* @param className the external name
* @return the canonical name
*/
public static String internalizeClass(String className)
{
String result = className;
int length = result.length();
if (result.charAt(length - 1) == ';') {
result = result.substring(1, length - 2);
}
result = result.replace('/', '.');
return result;
}
/**
* convert a type name from canonical form to the form used to represent it externally i.e.
* replace primitive type names by the appropriate single letter types, class names
* by the externalized class name bracketed by 'L' and ';' and array names by the
* base type name preceded by '['.
*
* @param typeName the type name
* @return the external name
*/
public static String externalizeType(String typeName)
{
String externalName = "";
String[] typeAndArrayIndices = typeName.split("\\[");
String baseType = typeAndArrayIndices[0].trim();
for (int i = 1; i< typeAndArrayIndices.length; i++) {
String arrayIdx = typeAndArrayIndices[i];
if (arrayIdx.indexOf("\\]") != 0) {
externalName += '[';
}
}
for (int i = 0; i < internalNames.length; i++) {
if (internalNames[i].equals(baseType)) {
externalName += externalNames[i];
return externalName;
}
}
externalName += "L" + externalizeClass(baseType) + ";";
return externalName;
}
/**
* list of well known typenames as written in Java code
*/
final static private String[] internalNames = {
"", /* equivalent to void */
"void",
"byte",
"char",
"short",
"int",
"long",
"float",
"double",
"boolean",
"Byte",
"Character",
"Short",
"Integer",
"Long",
"Float",
"Double",
"String",
"java.lang.Byte",
"java.lang.Character",
"java.lang.Short",
"java.lang.Integer",
"java.lang.Long",
"java.lang.Float",
"java.lang.Double",
"java.lang.String"
};
/**
* list of typenames in external form corresponding to entries ni previous list
*/
final static private String[] externalNames = {
"$",
"V",
"B",
"C",
"S",
"I",
"J",
"F",
"D",
"Z",
"Ljava/lang/Byte;",
"Ljava/lang/Character;",
"Ljava/lang/Short;",
"Ljava/lang/Integer;",
"Ljava/lang/Long;",
"Ljava/lang/Float;",
"Ljava/lang/Double;",
"Ljava/lang/String;",
"Ljava/lang/Byte;",
"Ljava/lang/Character;",
"Ljava/lang/Short;",
"Ljava/lang/Integer;",
"Ljava/lang/Long;",
"Ljava/lang/Float;",
"Ljava/lang/Double;",
"Ljava/lang/String;"
};
/**
* convert a method descriptor from canonical form to the form used to represent it externally
*
* @param desc the method descriptor which must be trimmed of any surrounding white space
* @return an externalised form for the descriptor
*/
public static String externalizeDescriptor(String desc)
{
// the descriptor will start with '(' and the arguments list should end with ')' and,
// if it is not void be followed by a return type
int openIdx = desc.indexOf('(');
int closeIdx = desc.indexOf(')');
int length = desc.length();
if (openIdx != 0) {
return "";
}
if (closeIdx < 0) {
return "";
}
String retType = (closeIdx < length ? desc.substring(closeIdx + 1).trim() : "");
String externalRetType = externalizeType(retType);
String argString = desc.substring(1, closeIdx).trim();
String externalArgs = "";
if (argString.equals("")) {
externalArgs = argString;
} else {
String[] args = desc.substring(1, closeIdx).trim().split(",");
for (int i = 0; i < args.length ; i++) {
externalArgs += externalizeType(args[i]);
}
}
return "(" + externalArgs + ")" + externalRetType;
}
/**
* convert a method descriptor from the form used to represent it externally to canonical form
*
* @param desc the method descriptor which must be trimmed of any surrounding white space and start with "(".
* it must end either with ")" or with ") " followed by an exernalized return type
* @return an internalised form for the descriptor, possibly followed by a space and externalized return type
*/
public static String internalizeDescriptor(String desc)
{
StringBuffer buffer = new StringBuffer();
String sepr = "";
int argStart = desc.indexOf('(');
int argEnd = desc.indexOf(')');
int max = desc.length();
if (argStart < 0 || argEnd < 0) {
return "(...)";
}
int arrayCount = 0;
boolean addSepr = false;
buffer.append("(");
for (int idx = argStart + 1; idx < max;) {
char next = desc.charAt(idx);
if (addSepr) {
while (arrayCount > 0) {
buffer.append("[]");
arrayCount--;
}
buffer.append(sepr);
}
addSepr = true;
switch(next) {
case 'B':
{
buffer.append("byte");
}
break;
case 'C':
{
buffer.append("char");
}
break;
case 'S':
{
buffer.append("short");
}
break;
case 'I':
{
buffer.append("int");
}
break;
case 'J':
{
buffer.append("long");
}
break;
case 'Z':
{
buffer.append("boolean");
}
break;
case 'F':
{
buffer.append("float");
}
break;
case 'D':
{
buffer.append("double");
}
case 'V':
{
buffer.append("void");
}
break;
case 'L':
{
int tailIdx = idx+1;
while (tailIdx < max) {
char tailChar = desc.charAt(tailIdx);
if (tailChar == ';') {
break;
}
if (tailChar == '/')
{
tailChar = '.';
}
buffer.append(tailChar);
tailIdx++;
}
idx = tailIdx;
}
break;
case '[':
{
arrayCount++;
addSepr = false;
}
break;
case ')':
{
if (idx == argEnd - 1) {
buffer.append(")");
} else {
// leave room for return type
buffer.append(") ");
}
addSepr = false;
}
break;
default:
{
addSepr = false;
}
}
idx++;
if (idx < argEnd) {
sepr = ",";
} else {
sepr = "";
}
}
return buffer.toString();
}
/**
* split off the method name preceding the signature and return it
* @param targetMethod - the unqualified method name, possibly including signature
* @return the method name
*/
public static String parseMethodName(String targetMethod) {
int sigIdx = targetMethod.indexOf("(");
if (sigIdx > 0) {
return targetMethod.substring(0, sigIdx).trim();
} else {
return targetMethod;
}
}
/**
* split off the signature following the method name and return it
* @param targetMethod - the unqualified method name, possibly including signature
* @return the signature
*/
public static String parseMethodDescriptor(String targetMethod) {
int descIdx = targetMethod.indexOf("(");
if (descIdx >= 0) {
String desc = targetMethod.substring(descIdx, targetMethod.length()).trim();
return externalizeDescriptor(desc);
} else {
return "";
}
}
}

View File

@ -0,0 +1,33 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
public class ArgNamesBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
String[] parameterNames = bindingContext.getMethodProcessor().getParameterNames();
AsmOpUtils.push(instructions, parameterNames.length);
AsmOpUtils.newArray(instructions, AsmOpUtils.STRING_TYPE);
for(int i = 0; i < parameterNames.length; ++i) {
AsmOpUtils.dup(instructions);
AsmOpUtils.push(instructions, i);
AsmOpUtils.push(instructions, parameterNames[i]);
AsmOpUtils.arrayStore(instructions, AsmOpUtils.STRING_TYPE);
}
}
@Override
public Type getType(BindingContext bindingContext) {
return AsmOpUtils.STRING_ARRAY_TYPE;
}
}

View File

@ -0,0 +1,20 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
public class ArgsBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
AsmOpUtils.loadArgArray(instructions, bindingContext.getMethodProcessor().getMethodNode());
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(Object[].class);
}
}

View File

@ -0,0 +1,50 @@
package com.taobao.arthas.bytekit.asm.binding;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
/**
* TODO 这个判断是否要从stack上取数据要看 其它的binding是否需要 是否 optional这个应该是由 ArrayBinding 整体设定
* @author hengyunabc
*
*/
public class ArrayBinding extends Binding{
// TODO 数组的 type是什么
// private Type type;
List<Binding> bindingList = new ArrayList<Binding>();
public ArrayBinding(List<Binding> bindingList) {
this.bindingList = bindingList;
}
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
AsmOpUtils.push(instructions, bindingList.size());
AsmOpUtils.newArray(instructions, AsmOpUtils.OBJECT_TYPE);
for(int i = 0; i < bindingList.size(); ++i) {
AsmOpUtils.dup(instructions);
AsmOpUtils.push(instructions, i);
Binding binding = bindingList.get(i);
binding.pushOntoStack(instructions, bindingContext);
AsmOpUtils.box(instructions, binding.getType(bindingContext));
AsmOpUtils.arrayStore(instructions, AsmOpUtils.OBJECT_TYPE);
}
}
@Override
public Type getType(BindingContext bindingContext) {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -0,0 +1,323 @@
package com.taobao.arthas.bytekit.asm.binding;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.taobao.arthas.bytekit.asm.binding.annotation.BindingParser;
import com.taobao.arthas.bytekit.asm.binding.annotation.BindingParserHandler;
public abstract class Binding {
/**
* 是否可选的当不符合条件或者获取不到值时会转为 null这个不支持原始类型就像java.util.Optional 一样
* @return
*/
public boolean optional() {
return false;
}
/**
* 检查当前条件下这个binding是否可以工作比如检查field是否有这个field
* @return
*/
public boolean check(BindingContext bindingContext) {
return true;
}
/**
* 把这个binding本身放到栈上
* @param instructions
* @param bindingContext
*/
public abstract void pushOntoStack(InsnList instructions, BindingContext bindingContext);
public abstract Type getType( BindingContext bindingContext);
public boolean fromStack() {
return false;
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = ArgsBindingParser.class)
public @interface Args {
boolean optional() default false;
}
public static class ArgsBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new ArgsBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = ArgNamesBindingParser.class)
public @interface ArgNames {
boolean optional() default false;
}
public static class ArgNamesBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new ArgNamesBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = LocalVarsBindingParser.class)
public @interface LocalVars {
boolean optional() default false;
}
public static class LocalVarsBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new LocalVarsBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = LocalVarNamesBindingParser.class)
public @interface LocalVarNames {
boolean optional() default false;
}
public static class LocalVarNamesBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new LocalVarNamesBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = ClassBindingParser.class)
public @interface Class {
boolean optional() default false;
}
public static class ClassBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new ClassBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = FieldBindingParser.class)
public @interface Field {
boolean optional() default false;
java.lang.Class<?> owner() default Void.class;
java.lang.Class<?> type() default Void.class;
String name();
boolean isStatic() default false;
boolean box() default false;
}
public static class FieldBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
Field field = (Field) annotation;
Type ownerType = Type.getType(field.owner());
if(field.owner().equals(Void.class)) {
ownerType = null;
}
Type fieldType = Type.getType(field.type());
if(field.type().equals(Void.class)) {
fieldType = null;
}
return new FieldBinding(ownerType, field.name(), fieldType,
field.isStatic(), field.box());
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = InvokeArgsBindingParser.class)
public @interface InvokeArgs {
boolean optional() default false;
}
public static class InvokeArgsBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new InvokeArgsBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = InvokeReturnBindingParser.class)
public @interface InvokeReturn {
boolean optional() default false;
}
public static class InvokeReturnBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new InvokeReturnBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = InvokeMethodDeclarationBindingParser.class)
public @interface InvokeMethodDeclaration {
boolean optional() default false;
}
public static class InvokeMethodDeclarationBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new InvokeMethodDeclarationBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = MethodBindingParser.class)
public @interface Method {
boolean optional() default false;
}
public static class MethodBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new MethodBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = ReturnBindingParser.class)
public @interface Return {
boolean optional() default false;
}
public static class ReturnBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new ReturnBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = ThisBindingParser.class)
public @interface This {
}
public static class ThisBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new ThisBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = ThrowableBindingParser.class)
public @interface Throwable {
boolean optional() default false;
}
public static class ThrowableBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new ThrowableBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = LineBindingParser.class)
public @interface Line {
boolean optional() default false;
}
public static class LineBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new LineBinding();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.PARAMETER)
@BindingParserHandler(parser = MonitorBindingParser.class)
public @interface Monitor {
boolean optional() default false;
}
public static class MonitorBindingParser implements BindingParser {
@Override
public Binding parse(Annotation annotation) {
return new MonitorBinding();
}
}
}

View File

@ -0,0 +1,41 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.location.Location;
public class BindingContext {
private MethodProcessor methodProcessor;
private Location location;
private StackSaver stackSaver;
public BindingContext(Location location, MethodProcessor methodProcessor, StackSaver stackSaver) {
this.location = location;
this.methodProcessor = methodProcessor;
this.stackSaver = stackSaver;
}
public MethodProcessor getMethodProcessor() {
return methodProcessor;
}
public void setMethodProcessor(MethodProcessor methodProcessor) {
this.methodProcessor = methodProcessor;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public StackSaver getStackSaver() {
return stackSaver;
}
public void setStackSaver(StackSaver stackSaver) {
this.stackSaver = stackSaver;
}
}

View File

@ -0,0 +1,21 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
public class ClassBinding extends Binding{
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
String owner = bindingContext.getMethodProcessor().getOwner();
AsmOpUtils.ldc(instructions, Type.getObjectType(owner));
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(Class.class);
}
}

View File

@ -0,0 +1,95 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
public class FieldBinding extends Binding {
/**
* maybe null
*/
private Type owner;
private boolean box = false;
private String name;
private boolean isStatic = false;
/**
* maybe null
*/
private Type type;
public FieldBinding(Type owner, String name, Type type, boolean isStatic, boolean box) {
this.owner = owner;
this.name = name;
this.isStatic = isStatic;
this.box = box;
this.type = type;
}
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
Type onwerType = owner;
Type fieldType = type;
boolean fieldIsStatic = isStatic;
if (owner == null) {
onwerType = Type.getObjectType(bindingContext.getMethodProcessor().getOwner());
}
// 当type是null里需要从ClassNode里查找到files确定type
MethodProcessor methodProcessor = bindingContext.getMethodProcessor();
if (fieldType == null) {
ClassNode classNode = methodProcessor.getClassNode();
if (classNode == null) {
throw new IllegalArgumentException(
"classNode is null, cann not get owner type. FieldBinding name:" + name);
}
FieldNode field = AsmUtils.findField(classNode.fields, name);
if (field == null) {
throw new IllegalArgumentException("can not find field in ClassNode. FieldBinding name:" + name);
}
fieldType = Type.getType(field.desc);
if ((field.access & Opcodes.ACC_STATIC) != 0) {
fieldIsStatic = true;
}else {
fieldIsStatic = false;
}
}
if (fieldIsStatic) {
AsmOpUtils.getStatic(instructions, onwerType, name, fieldType);
} else {
methodProcessor.loadThis(instructions);
AsmOpUtils.getField(instructions, onwerType, name, fieldType);
}
if (box) {
AsmOpUtils.box(instructions, fieldType);
}
}
@Override
public Type getType(BindingContext bindingContext) {
Type fieldType = type;
if (fieldType == null) {
ClassNode classNode = bindingContext.getMethodProcessor().getClassNode();
if (classNode == null) {
throw new IllegalArgumentException(
"classNode is null, cann not get owner type. FieldBinding name:" + name);
}
FieldNode field = AsmUtils.findField(classNode.fields, name);
if (field == null) {
throw new IllegalArgumentException("can not find field in ClassNode. FieldBinding name:" + name);
}
fieldType = Type.getType(field.desc);
}
return fieldType;
}
}

View File

@ -0,0 +1,36 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
public class IntBinding extends Binding {
private int value;
private boolean box = true;
public IntBinding(int value) {
this(value, true);
}
public IntBinding(int value, boolean box) {
this.value = value;
this.box = box;
}
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
AsmOpUtils.push(instructions, value);
if (box) {
AsmOpUtils.box(instructions, Type.INT_TYPE);
}
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.INT_TYPE;
}
}

View File

@ -0,0 +1,47 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.taobao.arthas.bytekit.asm.location.Location;
import com.taobao.arthas.bytekit.asm.location.Location.InvokeLocation;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
/**
* invoke 传入的参数列表有严格的限制只能在 invoke 之前
*
* TODO static 函数时在数组前传一个null进去 不然不好区分是否 static 函数调用
*
* @author hengyunabc
*
*/
public class InvokeArgsBinding extends Binding {
@Override
public boolean fromStack() {
return true;
}
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
Location location = bindingContext.getLocation();
if(location instanceof InvokeLocation) {
InvokeLocation invokeLocation = (InvokeLocation) location;
if(invokeLocation.isWhenComplete()) {
throw new IllegalArgumentException("InvokeArgsBinding can not work on InvokeLocation whenComplete is true.");
}
}else {
throw new IllegalArgumentException("current location is not invoke location. location: " + location);
}
LocalVariableNode invokeArgsVariableNode = bindingContext.getMethodProcessor().initInvokeArgsVariableNode();
AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index);
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(Object[].class);
}
}

View File

@ -0,0 +1,38 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
/**
*
* @author hengyunabc
*
*/
public class InvokeMethodDeclarationBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode();
if (insnNode instanceof MethodInsnNode) {
MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
String methodDeclaration = AsmUtils.methodDeclaration(methodInsnNode);
AsmOpUtils.push(instructions, methodDeclaration);
} else {
throw new IllegalArgumentException(
"InvokeMethodDeclarationBinding location is not MethodInsnNode, insnNode: " + insnNode);
}
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(String.class);
}
}

View File

@ -0,0 +1,62 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
/**
* invoke 的返回值
* @author hengyunabc
*
*/
public class InvokeReturnBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode();
MethodProcessor methodProcessor = bindingContext.getMethodProcessor();
if (insnNode instanceof MethodInsnNode) {
MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
String uniqueNameForMethod = AsmUtils.uniqueNameForMethod(methodInsnNode.owner, methodInsnNode.name,
methodInsnNode.desc);
Type invokeReturnType = Type.getMethodType(methodInsnNode.desc).getReturnType();
if(invokeReturnType.equals(Type.VOID_TYPE)) {
AsmOpUtils.push(instructions, null);
}else {
LocalVariableNode invokeReturnVariableNode = methodProcessor.initInvokeReturnVariableNode(
uniqueNameForMethod, Type.getMethodType(methodInsnNode.desc).getReturnType());
AsmOpUtils.loadVar(instructions, invokeReturnType, invokeReturnVariableNode.index);
}
} else {
throw new IllegalArgumentException(
"InvokeReturnBinding location is not MethodInsnNode, insnNode: " + insnNode);
}
}
@Override
public boolean fromStack() {
return true;
}
@Override
public Type getType(BindingContext bindingContext) {
AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode();
if (insnNode instanceof MethodInsnNode) {
MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
Type invokeReturnType = Type.getMethodType(methodInsnNode.desc).getReturnType();
return invokeReturnType;
} else {
throw new IllegalArgumentException(
"InvokeReturnBinding location is not MethodInsnNode, insnNode: " + insnNode);
}
}
}

View File

@ -0,0 +1,30 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LineNumberNode;
import com.taobao.arthas.bytekit.asm.location.Location;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
public class LineBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
Location location = bindingContext.getLocation();
AbstractInsnNode insnNode = location.getInsnNode();
if (insnNode instanceof LineNumberNode) {
AsmOpUtils.push(instructions, ((LineNumberNode) insnNode).line);
} else {
throw new IllegalArgumentException("LineBinding location is not LineNumberNode, insnNode: " + insnNode);
}
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(int.class);
}
}

View File

@ -0,0 +1,38 @@
package com.taobao.arthas.bytekit.asm.binding;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
public class LocalVarNamesBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
AbstractInsnNode currentInsnNode = bindingContext.getLocation().getInsnNode();
List<LocalVariableNode> results = AsmOpUtils
.validVariables(bindingContext.getMethodProcessor().getMethodNode().localVariables, currentInsnNode);
AsmOpUtils.push(instructions, results.size());
AsmOpUtils.newArray(instructions, AsmOpUtils.STRING_TYPE);
for (int i = 0; i < results.size(); ++i) {
AsmOpUtils.dup(instructions);
AsmOpUtils.push(instructions, i);
AsmOpUtils.push(instructions, results.get(i).name);
AsmOpUtils.arrayStore(instructions, AsmOpUtils.STRING_TYPE);
}
}
@Override
public Type getType(BindingContext bindingContext) {
return AsmOpUtils.STRING_ARRAY_TYPE;
}
}

View File

@ -0,0 +1,49 @@
package com.taobao.arthas.bytekit.asm.binding;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
/**
* TODO 增加一个配置是否包含 method args
* @author hengyunabc
*
*/
public class LocalVarsBinding extends Binding{
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
AbstractInsnNode currentInsnNode = bindingContext.getLocation().getInsnNode();
List<LocalVariableNode> results = AsmOpUtils
.validVariables(bindingContext.getMethodProcessor().getMethodNode().localVariables, currentInsnNode);
AsmOpUtils.push(instructions, results.size());
AsmOpUtils.newArray(instructions, AsmOpUtils.OBJECT_TYPE);
for (int i = 0; i < results.size(); ++i) {
AsmOpUtils.dup(instructions);
AsmOpUtils.push(instructions, i);
LocalVariableNode variableNode = results.get(i);
AsmOpUtils.loadVar(instructions, Type.getType(variableNode.desc), variableNode.index);
AsmOpUtils.box(instructions, Type.getType(variableNode.desc));
AsmOpUtils.arrayStore(instructions, AsmOpUtils.OBJECT_TYPE);
}
}
@Override
public Type getType(BindingContext bindingContext) {
return AsmOpUtils.OBJECT_TYPE;
}
}

View File

@ -0,0 +1,52 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
/**
* @author hengyunabc
*
*/
public class MethodBinding extends Binding{
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
// 先获取类本身的 class 再调用 getDeclaredMethod 它需要一个变长参数实际上要传一个数组
/**
* @see java.lang.Class.getDeclaredMethod(String, Class<?>...)
*/
MethodProcessor methodProcessor = bindingContext.getMethodProcessor();
AsmOpUtils.ldc(instructions, Type.getObjectType(methodProcessor.getOwner()));
AsmOpUtils.push(instructions, methodProcessor.getMethodNode().name);
Type[] argumentTypes = Type.getMethodType(methodProcessor.getMethodNode().desc).getArgumentTypes();
AsmOpUtils.push(instructions, argumentTypes.length);
AsmOpUtils.newArray(instructions, Type.getType(Class.class));
for(int i = 0; i < argumentTypes.length; ++i) {
AsmOpUtils.dup(instructions);
AsmOpUtils.push(instructions, i);
AsmOpUtils.ldc(instructions, argumentTypes[i]);
AsmOpUtils.arrayStore(instructions, Type.getType(Class.class));
}
MethodInsnNode declaredMethodInsnNode = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getType(Class.class).getInternalName(),
"getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
instructions.add(declaredMethodInsnNode);
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(java.lang.reflect.Method.class);
}
}

View File

@ -0,0 +1,32 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
/**
* 提供一个完整的 method 的string包含类名并不是desc用户可以自己提取descs method的定义前面是 public
* /static 这些关键字是有限的几个后面是 throws 的异常信息
*
* @author hengyunabc
*
*/
public class MethodDeclarationBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
MethodProcessor methodProcessor = bindingContext.getMethodProcessor();
AsmOpUtils.ldc(instructions, AsmUtils.methodDeclaration(Type.getObjectType(methodProcessor.getOwner()),
methodProcessor.getMethodNode()));
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(String.class);
}
}

View File

@ -0,0 +1,41 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.taobao.arthas.bytekit.asm.location.Location;
import com.taobao.arthas.bytekit.asm.location.Location.SyncEnterLocation;
import com.taobao.arthas.bytekit.asm.location.Location.SyncExitLocation;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
public class MonitorBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
Location location = bindingContext.getLocation();
if (location.isWhenComplete()) {
throw new IllegalArgumentException("MonitorBinding only support location whenComplete is false.");
}
if (location instanceof SyncEnterLocation || location instanceof SyncExitLocation) {
LocalVariableNode monitorVariableNode = bindingContext.getMethodProcessor().initMonitorVariableNode();
AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_TYPE, monitorVariableNode.index);
} else {
throw new IllegalArgumentException(
"MonitorBinding only support SyncEnterLocation or SyncExitLocation. location: " + location);
}
}
@Override
public boolean fromStack() {
return true;
}
@Override
public Type getType(BindingContext bindingContext) {
return AsmOpUtils.OBJECT_TYPE;
}
}

View File

@ -0,0 +1,43 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.taobao.arthas.bytekit.asm.location.Location;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
public class ReturnBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
//check location
Location location = bindingContext.getLocation();
if (!AsmOpUtils.isReturnCode(location.getInsnNode().getOpcode())) {
throw new IllegalArgumentException("current location is not return location. location: " + location);
}
Type returnType = bindingContext.getMethodProcessor().getReturnType();
if(returnType.equals(Type.VOID_TYPE)) {
AsmOpUtils.push(instructions, null);
}else {
LocalVariableNode returnVariableNode = bindingContext.getMethodProcessor().initReturnVariableNode();
AsmOpUtils.storeVar(instructions, returnType, returnVariableNode.index);
}
}
@Override
public boolean fromStack() {
return true;
}
@Override
public Type getType(BindingContext bindingContext) {
return bindingContext.getMethodProcessor().getReturnType();
}
}

View File

@ -0,0 +1,23 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
/**
* return/throw/invoke 等location时需要把栈上的值保存到locals里
* @author hengyunabc
*
*/
public interface StackSaver {
/**
* 有可能在两个地方被调用1: 在最开始保存栈上的值时 2: callback函数有返回值想更新这个值时stackSaver自己内部要保证保存的locals index是一致的
* @param instructions
* @param bindingContext
*/
public void store(InsnList instructions, BindingContext bindingContext);
public void load(InsnList instructions, BindingContext bindingContext);
public Type getType(BindingContext bindingContext);
}

View File

@ -0,0 +1,18 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
public class ThisBinding extends Binding {
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
bindingContext.getMethodProcessor().loadThis(instructions);
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(Object.class);
}
}

View File

@ -0,0 +1,30 @@
package com.taobao.arthas.bytekit.asm.binding;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
/**
* TODO 要检查 location 是否是合法的
* @author hengyunabc
*
*/
public class ThrowableBinding extends Binding {
@Override
public boolean fromStack() {
return true;
}
@Override
public void pushOntoStack(InsnList instructions, BindingContext bindingContext) {
// TODO 这里从 StackSaver 里取是否合理
bindingContext.getStackSaver().load(instructions, bindingContext);
// 是否要 check cast ?
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(Throwable.class);
}
}

View File

@ -0,0 +1,11 @@
package com.taobao.arthas.bytekit.asm.binding.annotation;
import java.lang.annotation.Annotation;
import com.taobao.arthas.bytekit.asm.binding.Binding;
public interface BindingParser {
public Binding parse(Annotation annotation);
}

View File

@ -0,0 +1,15 @@
package com.taobao.arthas.bytekit.asm.binding.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.ANNOTATION_TYPE)
public @interface BindingParserHandler {
Class<? extends BindingParser> parser();
}

View File

@ -0,0 +1,35 @@
package com.taobao.arthas.bytekit.asm.inst;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 在这里支持配置一个 error hander 做为插入的异常处理的
*
* 按名字匹配按模糊匹配有没有这样子的需求按interface匹配按基础类继承的匹配
*
* 函数的匹配直接是名字一样desc 一样的 匹配有 annotation
*
* 只有 NewField 才是加新的field原来类里有的field就直接写上就可以了
* @author hengyunabc
*
*/
@Target({ java.lang.annotation.ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Instrument {
// Instrumentation
// MatchType type() default MatchType.ExactClass;
String[] Class() default {};
String[] BaseClass() default {};
String[] Interface() default {};
String originalName() default "";
Class<? extends Throwable> suppress() default Throwable.class;
Class<?> suppressHandler() default Void.class;
}

View File

@ -0,0 +1,32 @@
package com.taobao.arthas.bytekit.asm.inst;
/**
*
* <pre>
* 实现这个 invokeOrigin()需要多步处理
*
* 传入要被替换的类读取到标记了 @Instrument 的类 类名不一样的话先替换类名
*
* 然后查找所有的 field如果有标记了 @NewField 则增加到要被替换的类里
*
* 然后查找所有的函数 再查找是否在 旧类里有同样签名的如果有则执行清除行号 替换 invokeOrigin() inline 原来的旧函数
*
* 再替换函数到 旧类里
*
* 类名有可能要替换
*
* </pre>
*
*
*
*
*
* @author hengyunabc 2019-02-25
*
*/
public class InstrumentApi {
public static final <T> T invokeOrigin() {
return null;
}
}

View File

@ -0,0 +1,10 @@
package com.taobao.arthas.bytekit.asm.inst;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ java.lang.annotation.ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface NewField {
}

View File

@ -0,0 +1,125 @@
package com.taobao.arthas.bytekit.asm.inst.impl;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.commons.Method;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.TypeInsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
/**
*
* @author hengyunabc 2019-03-15
*
*/
public class InstrumentImpl {
public static MethodNode replaceInvokeOrigin(String originOwner, MethodNode originMethodNode,
MethodNode apmMethodNode) {
// 查找到所有的 InstrumentApi.invokeOrigin() 指令
List<MethodInsnNode> methodInsnNodes = AsmUtils.findMethodInsnNode(apmMethodNode,
"com/taobao/arthas/bytekit/asm/inst/InstrumentApi", "invokeOrigin", "()Ljava/lang/Object;");
Type originReturnType = Type.getMethodType(originMethodNode.desc).getReturnType();
for (MethodInsnNode methodInsnNode : methodInsnNodes) {
InsnList instructions = new InsnList();
AbstractInsnNode secondInsnNode = methodInsnNode.getNext();
// 如果是 static 则要 load this
boolean isStatic = AsmUtils.isStatic(originMethodNode);
int opcode = isStatic ? Opcodes.INVOKESTATIC : Opcodes.INVOKEVIRTUAL;
if (!isStatic) {
AsmOpUtils.loadThis(instructions);
}
AsmOpUtils.loadArgs(instructions, originMethodNode);
MethodInsnNode originMethodInsnNode = new MethodInsnNode(opcode, originOwner, originMethodNode.name,
originMethodNode.desc, false);
// 调用原来的函数
instructions.add(originMethodInsnNode);
int sort = originReturnType.getSort();
if (sort == Type.VOID) {
if (secondInsnNode != null) {
if (secondInsnNode.getOpcode() == Opcodes.POP) {
// TODO 原来的函数没有返回值这里要把 POP去掉有没有可能是 POP2 ?
apmMethodNode.instructions.remove(secondInsnNode);
} else {
// TODO 原来函数没有返回值这里有没有可能要赋值是否要 push null?
AsmOpUtils.pushNUll(instructions);
}
}
} else if (sort >= Type.BOOLEAN && sort <= Type.DOUBLE) {
if (secondInsnNode.getOpcode() == Opcodes.POP) {
// 原来是 pop掉一个栈如果函数返回的是 long则要pop2
if (originReturnType.getSize() == 2) {
apmMethodNode.instructions.insert(secondInsnNode, new InsnNode(Opcodes.POP2));
apmMethodNode.instructions.remove(secondInsnNode);
}
} else {
/**
* 需要把下面两条cast和unbox的指令删掉
*
* <pre>
* CHECKCAST java/lang/Integer
* INVOKEVIRTUAL java/lang/Integer.intValue ()I
* </pre>
*/
boolean removeCheckCast = false;
if (secondInsnNode.getOpcode() == Opcodes.CHECKCAST) {
TypeInsnNode typeInsnNode = (TypeInsnNode) secondInsnNode;
// 从原始函数的返回值获取到它对应的自动box的类
Type boxedType = AsmOpUtils.getBoxedType(originReturnType);
if (Type.getObjectType(typeInsnNode.desc).equals(boxedType)) {
AbstractInsnNode thridInsnNode = secondInsnNode.getNext();
if (thridInsnNode != null && thridInsnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
MethodInsnNode valueInsnNode = (MethodInsnNode) thridInsnNode;
Method unBoxMethod = AsmOpUtils.getUnBoxMethod(originReturnType);
if (unBoxMethod.getDescriptor().equals(valueInsnNode.desc)
&& valueInsnNode.owner.equals(boxedType.getInternalName())) {
apmMethodNode.instructions.remove(thridInsnNode);
apmMethodNode.instructions.remove(secondInsnNode);
removeCheckCast = true;
}
}
}
}
if (!removeCheckCast) {
// 没有被转换为原始类型也没有pop则说明赋值给了一个对象用类似Long.valudOf转换为Object
AsmOpUtils.box(instructions, originReturnType);
}
}
} else {// ARRAY/OBJECT
// 移掉可能有的 check cast
if (secondInsnNode.getOpcode() == Opcodes.CHECKCAST) {
TypeInsnNode typeInsnNode = (TypeInsnNode) secondInsnNode;
if (Type.getObjectType(typeInsnNode.desc).equals(originReturnType)) {
apmMethodNode.instructions.remove(secondInsnNode);
}
}
}
apmMethodNode.instructions.insertBefore(methodInsnNode, instructions);
apmMethodNode.instructions.remove(methodInsnNode);
}
MethodProcessor methodProcessor = new MethodProcessor(originOwner, apmMethodNode);
methodProcessor.inline(originOwner, originMethodNode);
return apmMethodNode;
}
}

View File

@ -0,0 +1,52 @@
package com.taobao.arthas.bytekit.asm.inst.impl;
import com.alibaba.arthas.deps.org.objectweb.asm.Label;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
/**
*
* @author hengyunabc 2019-03-18
*
*/
public class MethodReplaceResult {
private boolean success;
private Label start;
private Label end;
private MethodNode methodNode;
public Label getStart() {
return start;
}
public void setStart(Label start) {
this.start = start;
}
public Label getEnd() {
return end;
}
public void setEnd(Label end) {
this.end = end;
}
public MethodNode getMethodNode() {
return methodNode;
}
public void setMethodNode(MethodNode methodNode) {
this.methodNode = methodNode;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}

View File

@ -0,0 +1,5 @@
package com.taobao.arthas.bytekit.asm.interceptor;
public class EnterInteceptor {
}

View File

@ -0,0 +1,5 @@
package com.taobao.arthas.bytekit.asm.interceptor;
public class ExceptionInterceptor {
}

View File

@ -0,0 +1,5 @@
package com.taobao.arthas.bytekit.asm.interceptor;
public class ExitInterceptor {
}

View File

@ -0,0 +1,5 @@
package com.taobao.arthas.bytekit.asm.interceptor;
public interface Inteceptor {
}

View File

@ -0,0 +1,71 @@
package com.taobao.arthas.bytekit.asm.interceptor;
import java.util.List;
import com.taobao.arthas.bytekit.asm.binding.Binding;
public class InterceptorMethodConfig {
private boolean inline;
private String owner;
private String methodName;
private String methodDesc;
private List<Binding> bindings;
/**
* 插入的代码用 try/catch 包围的异常类型
*/
private String suppress;
public boolean isInline() {
return inline;
}
public void setInline(boolean inline) {
this.inline = inline;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getMethodDesc() {
return methodDesc;
}
public void setMethodDesc(String methodDesc) {
this.methodDesc = methodDesc;
}
public List<Binding> getBindings() {
return bindings;
}
public void setBindings(List<Binding> bindings) {
this.bindings = bindings;
}
public String getSuppress() {
return suppress;
}
public void setSuppress(String suppress) {
this.suppress = suppress;
}
}

View File

@ -0,0 +1,253 @@
package com.taobao.arthas.bytekit.asm.interceptor;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.JumpInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.TryCatchBlock;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.binding.BindingContext;
import com.taobao.arthas.bytekit.asm.binding.StackSaver;
import com.taobao.arthas.bytekit.asm.location.Location;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
import com.taobao.arthas.bytekit.utils.Decompiler;
public class InterceptorProcessor {
private LocationMatcher locationMatcher;
/**
* 插入的回调函数的配置
*/
private InterceptorMethodConfig interceptorMethodConfig;
/**
* 插入的代码被 try/catch 包围的配置注意有一些location插入try/catch会可能失败因为不能确切知道栈上的情况
*/
private InterceptorMethodConfig exceptionHandlerConfig;
/**
* 加载inlne类所需要的ClassLoader
*/
private ClassLoader classLoader;
public InterceptorProcessor(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void process(MethodProcessor methodProcessor) throws Exception {
List<Location> locations = locationMatcher.match(methodProcessor);
List<Binding> interceptorBindings = interceptorMethodConfig.getBindings();
for (Location location : locations) {
// 有三小段代码1: 保存当前栈上的值的 , 2: 插入的回调的 3恢复当前栈的
InsnList toInsert = new InsnList();
InsnList stackSaveInsnList = new InsnList();
InsnList stackLoadInsnList = new InsnList();
StackSaver stackSaver = null;
if(location.isStackNeedSave()) {
stackSaver = location.getStackSaver();
}
BindingContext bindingContext = new BindingContext(location, methodProcessor, stackSaver);
if(stackSaver != null) {
stackSaver.store(stackSaveInsnList, bindingContext);
stackSaver.load(stackLoadInsnList, bindingContext);
}
Type methodType = Type.getMethodType(interceptorMethodConfig.getMethodDesc());
Type[] argumentTypes = methodType.getArgumentTypes();
// 检查回调函数的参数和 binding数一致
if(interceptorBindings.size() != argumentTypes.length) {
throw new IllegalArgumentException("interceptorBindings size no equals with interceptorMethod args size.");
}
// 把当前栈上的数据保存起来
int fromStackBindingCount = 0;
for (Binding binding : interceptorBindings) {
if(binding.fromStack()) {
fromStackBindingCount++;
}
}
// 只允许一个binding从栈上保存数据
if(fromStackBindingCount > 1) {
throw new IllegalArgumentException("interceptorBindings have more than one from stack Binding.");
}
// 组装好要调用的 static 函数的参数
for(int i = 0 ; i < argumentTypes.length; ++i) {
Binding binding = interceptorBindings.get(i);
binding.pushOntoStack(toInsert, bindingContext);
// 检查 回调函数的参数类型看是否要box一下 检查是否原始类型就可以了
// 只有类型不一样时才需要判断比如两个都是 long则不用判断
Type bindingType = binding.getType(bindingContext);
if(!bindingType.equals(argumentTypes[i])) {
if(AsmOpUtils.needBox(bindingType)) {
AsmOpUtils.box(toInsert, binding.getType(bindingContext));
}
}
}
// TODO 要检查 binding 回调的函数的参数类型是否一致回调函数的类型可以是 Object或者super但是不允许一些明显的类型问题比如array转到int
toInsert.add(new MethodInsnNode(Opcodes.INVOKESTATIC, interceptorMethodConfig.getOwner(), interceptorMethodConfig.getMethodName(),
interceptorMethodConfig.getMethodDesc(), false));
if (!methodType.getReturnType().equals(Type.VOID_TYPE)) {
if (location.canChangeByReturn()) {
// 当回调函数有返回值时需要更新到之前保存的栈上
// TODO 这里应该有 type 的问题需要检查是否要 box
Type returnType = methodType.getReturnType();
Type stackSaverType = stackSaver.getType(bindingContext);
if (!returnType.equals(stackSaverType)) {
AsmOpUtils.unbox(toInsert, stackSaverType);
}
stackSaver.store(toInsert, bindingContext);
} else {
// 没有使用到回调函数的返回值的话则需要从栈上清理掉
int size = methodType.getReturnType().getSize();
if (size == 1) {
AsmOpUtils.pop(toInsert);
} else if (size == 2) {
AsmOpUtils.pop2(toInsert);
}
}
}
TryCatchBlock errorHandlerTryCatchBlock = null;
// 生成的代码用try/catch包围起来
if( exceptionHandlerConfig != null) {
LabelNode gotoDest = new LabelNode();
errorHandlerTryCatchBlock = new TryCatchBlock(methodProcessor.getMethodNode(), exceptionHandlerConfig.getSuppress());
toInsert.insertBefore(toInsert.getFirst(), errorHandlerTryCatchBlock.getStartLabelNode());
toInsert.add(new JumpInsnNode(Opcodes.GOTO, gotoDest));
toInsert.add(errorHandlerTryCatchBlock.getEndLabelNode());
// 这里怎么把栈上的数据保存起来还是强制回调函数的第一个参数是 exception后面的binding可以随便搞
// MethodInsnNode printStackTrace = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false);
// toInsert.add(printStackTrace);
errorHandler(methodProcessor, toInsert);
toInsert.add(gotoDest);
}
// System.err.println(Decompiler.toString(toInsert));
stackSaveInsnList.add(toInsert);
stackSaveInsnList.add(stackLoadInsnList);
if (location.isWhenComplete()) {
methodProcessor.getMethodNode().instructions.insert(location.getInsnNode(), stackSaveInsnList);
}else {
methodProcessor.getMethodNode().instructions.insertBefore(location.getInsnNode(), stackSaveInsnList);
}
if( exceptionHandlerConfig != null) {
errorHandlerTryCatchBlock.sort();
}
// inline callback
if(interceptorMethodConfig.isInline()) {
// Class<?> forName = Class.forName(Type.getObjectType(interceptorMethodConfig.getOwner()).getClassName());
Class<?> forName = classLoader.loadClass(Type.getObjectType(interceptorMethodConfig.getOwner()).getClassName());
MethodNode toInlineMethodNode = AsmUtils.findMethod(AsmUtils.loadClass(forName).methods, interceptorMethodConfig.getMethodName(), interceptorMethodConfig.getMethodDesc());
methodProcessor.inline(interceptorMethodConfig.getOwner(), toInlineMethodNode);
}
if(exceptionHandlerConfig != null && exceptionHandlerConfig.isInline()) {
// Class<?> forName = Class.forName(Type.getObjectType(exceptionHandlerConfig.getOwner()).getClassName());
Class<?> forName = classLoader.loadClass(Type.getObjectType(exceptionHandlerConfig.getOwner()).getClassName());
MethodNode toInlineMethodNode = AsmUtils.findMethod(AsmUtils.loadClass(forName).methods, exceptionHandlerConfig.getMethodName(), exceptionHandlerConfig.getMethodDesc());
methodProcessor.inline(exceptionHandlerConfig.getOwner(), toInlineMethodNode);
}
// System.err.println(Decompiler.toString(methodProcessor.getMethodNode()));
// System.err.println(AsmUtils.toASMCode(methodProcessor.getMethodNode()));
}
}
private void errorHandler(MethodProcessor methodProcessor, InsnList insnList) {
// MethodInsnNode printStackTrace = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false);
// insnList.add(printStackTrace);
// 第一个参数要求是 throwable 或者一个exception
// 有很多 binding 并不能使用的因为location不生效
BindingContext bindingContext = new BindingContext(null, methodProcessor, null);
Type methodType = Type.getMethodType(this.exceptionHandlerConfig.getMethodDesc());
Type[] argumentTypes = methodType.getArgumentTypes();
List<Binding> bindings = this.exceptionHandlerConfig.getBindings();
if(bindings.size() + 1 != argumentTypes.length) {
throw new IllegalArgumentException("errorHandler bindings size do not match error method args size.");
}
if(!argumentTypes[0].equals(Type.getType(Throwable.class))) {
throw new IllegalArgumentException("errorHandler method first arg type must be Throwable.");
}
// 组装好要调用的 static 函数的参数
for(Binding binding: bindings) {
if(binding.fromStack()) {
throw new IllegalArgumentException("errorHandler binding can not load value from stack!");
}
binding.pushOntoStack(insnList, bindingContext);
// 检查 回调函数的参数类型看是否要box一下 检查是否原始类型就可以了
if(AsmOpUtils.needBox(binding.getType(bindingContext))) {
AsmOpUtils.box(insnList, binding.getType(bindingContext));
}
}
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, exceptionHandlerConfig.getOwner(), exceptionHandlerConfig.getMethodName(),
exceptionHandlerConfig.getMethodDesc(), false));
int size = methodType.getReturnType().getSize();
if (size == 1) {
AsmOpUtils.pop(insnList);
} else if (size == 2) {
AsmOpUtils.pop2(insnList);
}
}
public LocationMatcher getLocationMatcher() {
return locationMatcher;
}
public void setLocationMatcher(LocationMatcher locationMatcher) {
this.locationMatcher = locationMatcher;
}
public InterceptorMethodConfig getInterceptorMethodConfig() {
return interceptorMethodConfig;
}
public void setInterceptorMethodConfig(InterceptorMethodConfig interceptorMethodConfig) {
this.interceptorMethodConfig = interceptorMethodConfig;
}
public InterceptorMethodConfig getExceptionHandlerConfig() {
return exceptionHandlerConfig;
}
public void setExceptionHandlerConfig(InterceptorMethodConfig exceptionHandlerConfig) {
this.exceptionHandlerConfig = exceptionHandlerConfig;
}
}

View File

@ -0,0 +1,64 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter.EnterInterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.location.EnterLocationMatcher;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
@InterceptorParserHander(parserHander = EnterInterceptorProcessorParser.class)
public @interface AtEnter {
boolean inline() default true;
Class<? extends Throwable> suppress() default None.class;
Class<?> suppressHandler() default Void.class;
class EnterInterceptorProcessorParser implements InterceptorProcessorParser {
@Override
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) {
InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader());
InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig();
interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig);
interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
interceptorMethodConfig.setMethodName(method.getName());
interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
LocationMatcher locationMatcher = new EnterLocationMatcher();
interceptorProcessor.setLocationMatcher(locationMatcher);
AtEnter atEnter = (AtEnter) annotationOnMethod;
interceptorMethodConfig.setInline(atEnter.inline());
List<Binding> bindings = BindingParserUtils.parseBindings(method);
interceptorMethodConfig.setBindings(bindings);
InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils
.errorHandlerMethodConfig(atEnter.suppress(), atEnter.suppressHandler());
if (errorHandlerMethodConfig != null) {
interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig);
}
return interceptorProcessor;
}
}
}

View File

@ -0,0 +1,69 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit.ExceptionExitInterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.location.ExceptionExitLocationMatcher;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
@InterceptorParserHander(parserHander = ExceptionExitInterceptorProcessorParser.class)
public @interface AtExceptionExit {
boolean inline() default true;
Class<? extends Throwable> suppress() default None.class;
Class<?> suppressHandler() default Void.class;
Class<? extends Throwable> onException() default Throwable.class;
class ExceptionExitInterceptorProcessorParser implements InterceptorProcessorParser {
@Override
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) {
InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader());
InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig();
interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig);
interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
interceptorMethodConfig.setMethodName(method.getName());
interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
AtExceptionExit atExceptionExit = (AtExceptionExit) annotationOnMethod;
interceptorMethodConfig.setInline(atExceptionExit.inline());
LocationMatcher locationMatcher = new ExceptionExitLocationMatcher(Type.getInternalName(atExceptionExit.onException()));;
interceptorProcessor.setLocationMatcher(locationMatcher);
List<Binding> bindings = BindingParserUtils.parseBindings(method);
interceptorMethodConfig.setBindings(bindings);
InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils
.errorHandlerMethodConfig(atExceptionExit.suppress(), atExceptionExit.suppressHandler());
if (errorHandlerMethodConfig != null) {
interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig);
}
return interceptorProcessor;
}
}
}

View File

@ -0,0 +1,63 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit.ExitInterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.location.ExitLocationMatcher;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
@InterceptorParserHander(parserHander = ExitInterceptorProcessorParser.class)
public @interface AtExit {
boolean inline() default true;
Class<? extends Throwable> suppress() default None.class;
Class<?> suppressHandler() default Void.class;
class ExitInterceptorProcessorParser implements InterceptorProcessorParser {
@Override
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) {
InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader());
InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig();
interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig);
interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
interceptorMethodConfig.setMethodName(method.getName());
interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
LocationMatcher locationMatcher = new ExitLocationMatcher();
interceptorProcessor.setLocationMatcher(locationMatcher);
AtExit atExit = (AtExit) annotationOnMethod;
interceptorMethodConfig.setInline(atExit.inline());
List<Binding> bindings = BindingParserUtils.parseBindings(method);
interceptorMethodConfig.setBindings(bindings);
InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils
.errorHandlerMethodConfig(atExit.suppress(), atExit.suppressHandler());
if (errorHandlerMethodConfig != null) {
interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig);
}
return interceptorProcessor;
}
}
}

View File

@ -0,0 +1,91 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtFieldAccess.FieldAccessInterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.location.FieldAccessLocationMatcher;
import com.taobao.arthas.bytekit.asm.location.Location;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
@InterceptorParserHander(parserHander = FieldAccessInterceptorProcessorParser.class)
public @interface AtFieldAccess {
boolean inline() default true;
Class<? extends Throwable> suppress() default None.class;
Class<?> suppressHandler() default Void.class;
java.lang.Class<?> owner() default Void.class;
java.lang.Class<?> type() default Void.class;
String name();
int count() default -1;
int flags() default Location.ACCESS_READ | Location.ACCESS_WRITE;
boolean whenComplete() default false;
class FieldAccessInterceptorProcessorParser implements InterceptorProcessorParser {
@Override
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) {
InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader());
InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig();
interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig);
interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
interceptorMethodConfig.setMethodName(method.getName());
interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
AtFieldAccess atFieldAccess = (AtFieldAccess) annotationOnMethod;
String ownerClass = null;
String fieldDesc = null;
if(! atFieldAccess.owner().equals(Void.class)) {
ownerClass = Type.getType(atFieldAccess.owner()).getInternalName();
}
if(!atFieldAccess.type().equals(Void.class)) {
fieldDesc = Type.getType(atFieldAccess.type()).getDescriptor();
}
LocationMatcher locationMatcher = new FieldAccessLocationMatcher(
ownerClass,
fieldDesc, atFieldAccess.name(), atFieldAccess.count(),
atFieldAccess.flags(), atFieldAccess.whenComplete());
interceptorProcessor.setLocationMatcher(locationMatcher);
interceptorMethodConfig.setInline(atFieldAccess.inline());
List<Binding> bindings = BindingParserUtils.parseBindings(method);
interceptorMethodConfig.setBindings(bindings);
InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils
.errorHandlerMethodConfig(atFieldAccess.suppress(), atFieldAccess.suppressHandler());
if (errorHandlerMethodConfig != null) {
interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig);
}
return interceptorProcessor;
}
}
}

View File

@ -0,0 +1,94 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke.InvokeInterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.location.InvokeLocationMatcher;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
@InterceptorParserHander(parserHander = InvokeInterceptorProcessorParser.class)
public @interface AtInvoke {
boolean inline() default true;
Class<? extends Throwable> suppress() default None.class;
Class<?> suppressHandler() default Void.class;
Class<?> owner() default Void.class;
String name();
String desc() default "";
int count() default -1;
boolean whenComplete() default false;
String[] excludes() default {};
class InvokeInterceptorProcessorParser implements InterceptorProcessorParser {
@Override
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) {
InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader());
InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig();
interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig);
interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
interceptorMethodConfig.setMethodName(method.getName());
interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
AtInvoke atInvoke = (AtInvoke) annotationOnMethod;
String owner = null;
String desc = null;
if (!atInvoke.owner().equals(Void.class)) {
owner = Type.getType(atInvoke.owner()).getInternalName();
}
if (atInvoke.desc().isEmpty()) {
desc = null;
}
List<String> excludes = new ArrayList<String>();
for (String exclude : atInvoke.excludes()) {
excludes.add(exclude);
}
LocationMatcher locationMatcher = new InvokeLocationMatcher(owner, atInvoke.name(), desc, atInvoke.count(),
atInvoke.whenComplete(), excludes);
interceptorProcessor.setLocationMatcher(locationMatcher);
interceptorMethodConfig.setInline(atInvoke.inline());
List<Binding> bindings = BindingParserUtils.parseBindings(method);
interceptorMethodConfig.setBindings(bindings);
InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils
.errorHandlerMethodConfig(atInvoke.suppress(), atInvoke.suppressHandler());
if (errorHandlerMethodConfig != null) {
interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig);
}
return interceptorProcessor;
}
}
}

View File

@ -0,0 +1,67 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtLine.LineInterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.location.LineLocationMatcher;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
@InterceptorParserHander(parserHander = LineInterceptorProcessorParser.class)
public @interface AtLine {
boolean inline() default true;
Class<? extends Throwable> suppress() default None.class;
Class<?> suppressHandler() default Void.class;
int[] lines();
class LineInterceptorProcessorParser implements InterceptorProcessorParser {
@Override
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) {
InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader());
InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig();
interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig);
interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
interceptorMethodConfig.setMethodName(method.getName());
interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
AtLine atLine = (AtLine) annotationOnMethod;
LocationMatcher locationMatcher = new LineLocationMatcher(atLine.lines());
interceptorProcessor.setLocationMatcher(locationMatcher);
interceptorMethodConfig.setInline(atLine.inline());
List<Binding> bindings = BindingParserUtils.parseBindings(method);
interceptorMethodConfig.setBindings(bindings);
InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils
.errorHandlerMethodConfig(atLine.suppress(), atLine.suppressHandler());
if (errorHandlerMethodConfig != null) {
interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig);
}
return interceptorProcessor;
}
}
}

View File

@ -0,0 +1,70 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtSyncEnter.SyncEnterInterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
import com.taobao.arthas.bytekit.asm.location.SyncLocationMatcher;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
@InterceptorParserHander(parserHander = SyncEnterInterceptorProcessorParser.class)
public @interface AtSyncEnter {
boolean inline() default true;
Class<? extends Throwable> suppress() default None.class;
Class<?> suppressHandler() default Void.class;
int count() default -1;
boolean whenComplete() default false;
class SyncEnterInterceptorProcessorParser implements InterceptorProcessorParser {
@Override
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) {
InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader());
InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig();
interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig);
interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
interceptorMethodConfig.setMethodName(method.getName());
interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
AtSyncEnter atSyncEnter = (AtSyncEnter) annotationOnMethod;
LocationMatcher locationMatcher = new SyncLocationMatcher(Opcodes.MONITORENTER, atSyncEnter.count(), atSyncEnter.whenComplete());
interceptorProcessor.setLocationMatcher(locationMatcher);
interceptorMethodConfig.setInline(atSyncEnter.inline());
List<Binding> bindings = BindingParserUtils.parseBindings(method);
interceptorMethodConfig.setBindings(bindings);
InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils
.errorHandlerMethodConfig(atSyncEnter.suppress(), atSyncEnter.suppressHandler());
if (errorHandlerMethodConfig != null) {
interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig);
}
return interceptorProcessor;
}
}
}

View File

@ -0,0 +1,70 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtSyncExit.SyncExitInterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
import com.taobao.arthas.bytekit.asm.location.SyncLocationMatcher;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
@InterceptorParserHander(parserHander = SyncExitInterceptorProcessorParser.class)
public @interface AtSyncExit {
boolean inline() default true;
Class<? extends Throwable> suppress() default None.class;
Class<?> suppressHandler() default Void.class;
int count() default -1;
boolean whenComplete() default false;
class SyncExitInterceptorProcessorParser implements InterceptorProcessorParser {
@Override
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) {
InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader());
InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig();
interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig);
interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
interceptorMethodConfig.setMethodName(method.getName());
interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
AtSyncExit atSyncExit = (AtSyncExit) annotationOnMethod;
LocationMatcher locationMatcher = new SyncLocationMatcher(Opcodes.MONITOREXIT, atSyncExit.count(), atSyncExit.whenComplete());
interceptorProcessor.setLocationMatcher(locationMatcher);
interceptorMethodConfig.setInline(atSyncExit.inline());
List<Binding> bindings = BindingParserUtils.parseBindings(method);
interceptorMethodConfig.setBindings(bindings);
InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils
.errorHandlerMethodConfig(atSyncExit.suppress(), atSyncExit.suppressHandler());
if (errorHandlerMethodConfig != null) {
interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig);
}
return interceptorProcessor;
}
}
}

View File

@ -0,0 +1,67 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtThrow.ThrowInterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
import com.taobao.arthas.bytekit.asm.location.LocationMatcher;
import com.taobao.arthas.bytekit.asm.location.ThrowLocationMatcher;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
@InterceptorParserHander(parserHander = ThrowInterceptorProcessorParser.class)
public @interface AtThrow {
boolean inline() default true;
Class<? extends Throwable> suppress() default None.class;
Class<?> suppressHandler() default Void.class;
int count() default -1;
class ThrowInterceptorProcessorParser implements InterceptorProcessorParser {
@Override
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) {
InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader());
InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig();
interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig);
interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
interceptorMethodConfig.setMethodName(method.getName());
interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
AtThrow atThrow = (AtThrow) annotationOnMethod;
LocationMatcher locationMatcher = new ThrowLocationMatcher(atThrow.count());
interceptorProcessor.setLocationMatcher(locationMatcher);
interceptorMethodConfig.setInline(atThrow.inline());
List<Binding> bindings = BindingParserUtils.parseBindings(method);
interceptorMethodConfig.setBindings(bindings);
InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils
.errorHandlerMethodConfig(atThrow.suppress(), atThrow.suppressHandler());
if (errorHandlerMethodConfig != null) {
interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig);
}
return interceptorProcessor;
}
}
}

View File

@ -0,0 +1,36 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.binding.annotation.BindingParser;
import com.taobao.arthas.bytekit.asm.binding.annotation.BindingParserHandler;
import com.taobao.arthas.bytekit.utils.InstanceUtils;
public class BindingParserUtils {
public static List<Binding> parseBindings(Method method) {
// parameter 里解析出来 binding
List<Binding> bindings = new ArrayList<Binding>();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int parameterIndex = 0; parameterIndex < parameterAnnotations.length; ++parameterIndex) {
Annotation[] annotationsOnParameter = parameterAnnotations[parameterIndex];
for (int j = 0; j < annotationsOnParameter.length; ++j) {
Annotation[] annotationsOnBinding = annotationsOnParameter[j].annotationType().getAnnotations();
for (Annotation annotationOnBinding : annotationsOnBinding) {
if (BindingParserHandler.class.isAssignableFrom(annotationOnBinding.annotationType())) {
BindingParserHandler bindingParserHandler = (BindingParserHandler) annotationOnBinding;
BindingParser bindingParser = InstanceUtils.newInstance(bindingParserHandler.parser());
Binding binding = bindingParser.parse(annotationsOnParameter[j]);
bindings.add(binding);
}
}
}
}
return bindings;
}
}

View File

@ -0,0 +1,10 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
public class EmptySuppressHandler {
@ExceptionHandler
public static void onSuppress() {
}
}

View File

@ -0,0 +1,13 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.METHOD)
public @interface ExceptionHandler {
boolean inline() default true;
}

View File

@ -0,0 +1,85 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.binding.ThrowableBinding;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
import com.taobao.arthas.bytekit.utils.AnnotationUtils;
import com.taobao.arthas.bytekit.utils.ReflectionUtils;
import com.taobao.arthas.bytekit.utils.ReflectionUtils.MethodCallback;
import com.taobao.arthas.bytekit.utils.ReflectionUtils.MethodFilter;
public class ExceptionHandlerUtils {
public static InterceptorMethodConfig errorHandlerMethodConfig(Class<?> suppress, Class<?> handlerClass) {
// TODO 要解析 errorHander Class里的内容
final InterceptorMethodConfig errorHandlerMethodConfig = new InterceptorMethodConfig();
if(suppress.equals(None.class)) {
suppress = Throwable.class;
}
errorHandlerMethodConfig.setSuppress(Type.getType(suppress).getInternalName());
if (!handlerClass.equals(Void.class)) {
// find method with @ExceptionHandler
ReflectionUtils.doWithMethods(handlerClass, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
for (Annotation onMethodAnnotation : method.getAnnotations()) {
if (ExceptionHandler.class.isAssignableFrom(onMethodAnnotation.annotationType())) {
if (!Modifier.isStatic(method.getModifiers())) {
throw new IllegalArgumentException("method must be static. method: " + method);
}
ExceptionHandler handler = (ExceptionHandler) onMethodAnnotation;
errorHandlerMethodConfig.setInline(handler.inline());
List<Binding> errorHandlerBindings = BindingParserUtils.parseBindings(method);
// 检查第一个 bidning要是 Throwable Binding
if (errorHandlerBindings.size() == 0) {
throw new IllegalArgumentException(
"error handler bingins must have at least a binding");
}
if (!(errorHandlerBindings.get(0) instanceof ThrowableBinding)) {
throw new IllegalArgumentException(
"error handler bingins first binding must be ThrowableBinding.");
}
// 去掉第一个 ThrowableBinding
// TODO 可能要copy一下保证可以修改成功
errorHandlerBindings.remove(0);
errorHandlerMethodConfig.setBindings(errorHandlerBindings);
errorHandlerMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass()));
errorHandlerMethodConfig.setMethodName(method.getName());
errorHandlerMethodConfig.setMethodDesc(Type.getMethodDescriptor(method));
}
}
}
}, new MethodFilter() {
@Override
public boolean matches(Method method) {
return AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null;
}
});
}
if (errorHandlerMethodConfig.getMethodDesc() == null) {
return null;
}
return errorHandlerMethodConfig;
}
}

View File

@ -0,0 +1,17 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.ANNOTATION_TYPE)
public @interface InterceptorParserHander {
Class<? extends InterceptorProcessorParser> parserHander();
}

View File

@ -0,0 +1,13 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
/**
* 用于声明没有异常
* @author hengyunabc
*
*/
public class None extends Throwable {
private static final long serialVersionUID = 1L;
private None() {
}
}

View File

@ -0,0 +1,11 @@
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
import com.taobao.arthas.bytekit.asm.binding.Binding;
public class PrintSuppressHandler {
@ExceptionHandler(inline = true)
public static void onSuppress(@Binding.Throwable Throwable e) {
e.printStackTrace();
}
}

View File

@ -0,0 +1,50 @@
package com.taobao.arthas.bytekit.asm.interceptor.parser;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.InterceptorParserHander;
import com.taobao.arthas.bytekit.utils.InstanceUtils;
import com.taobao.arthas.bytekit.utils.ReflectionUtils;
import com.taobao.arthas.bytekit.utils.ReflectionUtils.MethodCallback;
public class DefaultInterceptorClassParser implements InterceptorClassParser {
@Override
public List<InterceptorProcessor> parse(Class<?> clazz) {
final List<InterceptorProcessor> result = new ArrayList<InterceptorProcessor>();
MethodCallback methodCallback = new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
for (Annotation onMethodAnnotation : method.getAnnotations()) {
for (Annotation onAnnotation : onMethodAnnotation.annotationType().getAnnotations()) {
if (InterceptorParserHander.class.isAssignableFrom(onAnnotation.annotationType())) {
if (!Modifier.isStatic(method.getModifiers())) {
throw new IllegalArgumentException("method must be static. method: " + method);
}
InterceptorParserHander handler = (InterceptorParserHander) onAnnotation;
InterceptorProcessorParser interceptorProcessorParser = InstanceUtils
.newInstance(handler.parserHander());
InterceptorProcessor interceptorProcessor = interceptorProcessorParser.parse(method,
onMethodAnnotation);
result.add(interceptorProcessor);
}
}
}
}
};
ReflectionUtils.doWithMethods(clazz, methodCallback);
return result;
}
}

View File

@ -0,0 +1,10 @@
package com.taobao.arthas.bytekit.asm.interceptor.parser;
import java.util.List;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
public interface InterceptorClassParser {
public List<InterceptorProcessor> parse(Class<?> clazz);
}

View File

@ -0,0 +1,11 @@
package com.taobao.arthas.bytekit.asm.interceptor.parser;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
public interface InterceptorProcessorParser {
public InterceptorProcessor parse(Method method, Annotation annotationOnMethod);
}

View File

@ -0,0 +1,24 @@
package com.taobao.arthas.bytekit.asm.location;
public abstract class AccessLocationMatcher implements LocationMatcher {
protected int count;
/**
* flags identifying which type of access should be used to identify the
* trigger. this is either ACCESS_READ, ACCESS_WRITE or an OR of these two
* values
*/
protected int flags;
/**
* flag which is false if the trigger should be inserted before the field
* access is performed and true if it should be inserted after
*/
protected boolean whenComplete;
AccessLocationMatcher(int count, int flags, boolean whenComplete) {
this.count = count;
this.flags = flags;
this.whenComplete = whenComplete;
}
}

View File

@ -0,0 +1,21 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.location.Location.EnterLocation;
public class EnterLocationMatcher implements LocationMatcher {
@Override
public List<Location> match(MethodProcessor methodProcessor) {
List<Location> locations = new ArrayList<Location>();
AbstractInsnNode enterInsnNode = methodProcessor.getEnterInsnNode();
EnterLocation enterLocation = new EnterLocation(enterInsnNode);
locations.add(enterLocation);
return locations;
}
}

View File

@ -0,0 +1,32 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.TryCatchBlock;
import com.taobao.arthas.bytekit.asm.location.Location.ExceptionExitLocation;
public class ExceptionExitLocationMatcher implements LocationMatcher {
private String exception;
public ExceptionExitLocationMatcher() {
this(Type.getType(Throwable.class).getInternalName());
}
public ExceptionExitLocationMatcher(String exception) {
this.exception = exception;
}
@Override
public List<Location> match(MethodProcessor methodProcessor) {
List<Location> locations = new ArrayList<Location>();
TryCatchBlock tryCatchBlock = methodProcessor.initTryCatchBlock(exception);
locations.add(new ExceptionExitLocation(tryCatchBlock.getEndLabelNode()));
return locations;
}
}

View File

@ -0,0 +1,46 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.location.Location.ExitLocation;
public class ExitLocationMatcher implements LocationMatcher {
@Override
public List<Location> match(MethodProcessor methodProcessor) {
List<Location> locations = new ArrayList<Location>();
AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode();
while (insnNode != null) {
if (insnNode instanceof InsnNode) {
InsnNode node = (InsnNode) insnNode;
if (matchExit(node)) {
ExitLocation ExitLocation = new ExitLocation(node);
locations.add(ExitLocation);
}
}
insnNode = insnNode.getNext();
}
return locations;
}
public boolean matchExit(InsnNode node) {
switch (node.getOpcode()) {
case Opcodes.RETURN: // empty stack
case Opcodes.IRETURN: // 1 before n/a after
case Opcodes.FRETURN: // 1 before n/a after
case Opcodes.ARETURN: // 1 before n/a after
case Opcodes.LRETURN: // 2 before n/a after
case Opcodes.DRETURN: // 2 before n/a after
return true;
}
return false;
}
}

View File

@ -0,0 +1,96 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldInsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.location.Location.FieldAccessLocation;
public class FieldAccessLocationMatcher extends AccessLocationMatcher {
/**
* maybe null
*/
private String ownerClass;
/**
* the name of the field being accessed at the point where the trigger point
* should be inserted
*/
private String fieldName;
/**
* The field's descriptor (see {@link org.objectweb.asm.Type}). maybe null.
*/
private String fieldDesc;
public FieldAccessLocationMatcher(String ownerClass, String fieldDesc, String fieldName, int count, int flags,
boolean whenComplete) {
super(count, flags, whenComplete);
this.ownerClass = ownerClass;
this.fieldDesc = fieldDesc;
this.fieldName = fieldName;
}
@Override
public List<Location> match(MethodProcessor methodProcessor) {
List<Location> locations = new ArrayList<Location>();
AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode();
int matchedCount = 0;
while (insnNode != null) {
if (insnNode instanceof FieldInsnNode) {
FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode;
if (matchField(fieldInsnNode)) {
matchedCount++;
if (count <= 0 || count == matchedCount) {
FieldAccessLocation fieldAccessLocation = new FieldAccessLocation(fieldInsnNode, count, flags, whenComplete);
locations.add(fieldAccessLocation);
}
}
}
insnNode = insnNode.getNext();
}
return locations;
}
private boolean matchField(FieldInsnNode fieldInsnNode) {
if (!fieldName.equals(fieldInsnNode.name)) {
return false;
}
if (this.fieldDesc != null && !this.fieldDesc.equals(fieldInsnNode.desc)) {
return false;
}
switch (fieldInsnNode.getOpcode()) {
case Opcodes.GETSTATIC:
case Opcodes.GETFIELD: {
if ((flags & Location.ACCESS_READ) == 0) {
return false;
}
}
break;
case Opcodes.PUTSTATIC:
case Opcodes.PUTFIELD: {
if ((flags & Location.ACCESS_WRITE) == 0) {
return false;
}
}
break;
}
if (ownerClass != null) {
if (!ownerClass.equals(fieldInsnNode.owner)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,161 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.location.Location.InvokeLocation;
import com.taobao.arthas.bytekit.utils.MatchUtils;
public class InvokeLocationMatcher implements LocationMatcher {
/**
* the name of the method being invoked at the point where the trigger point
* should be inserted. maybe null, when null, match all method invoke.
*/
private String methodName;
/**
* the name of the type to which the method belongs or null if any type will
* do
*/
private String owner;
/**
* the method signature in externalised form, maybe null.
*/
private String desc;
/**
* count identifying which invocation should be taken as the trigger point.
* if not specified as a parameter this defaults to the first invocation.
*/
private int count;
/**
* flag which is false if the trigger should be inserted before the method
* invocation is performed and true if it should be inserted after
*/
private boolean whenComplete;
/**
* wildcard matcher to exclude class, such as java.* to exclude jdk invoke.
*/
private List<String> excludes = new ArrayList<String>();
public InvokeLocationMatcher(String owner, String methodName, String desc, int count, boolean whenComplete,
List<String> excludes) {
super();
this.owner = owner;
this.methodName = methodName;
this.desc = desc;
this.count = count;
this.whenComplete = whenComplete;
this.excludes = excludes;
}
public InvokeLocationMatcher(String owner, String methodName, String desc, int count, boolean whenComplete) {
this(owner, methodName, desc, count, whenComplete, new ArrayList<String>());
}
@Override
public List<Location> match(MethodProcessor methodProcessor) {
List<Location> locations = new ArrayList<Location>();
AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode();
int matchedCount = 0;
while (insnNode != null) {
if (insnNode instanceof MethodInsnNode) {
MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
if (matchCall(methodInsnNode)) {
matchedCount++;
if (count <= 0 || count == matchedCount) {
InvokeLocation invokeLocation = new InvokeLocation(methodInsnNode, count,
whenComplete);
locations.add(invokeLocation);
}
}
}
insnNode = insnNode.getNext();
}
return locations;
}
private boolean matchCall(MethodInsnNode methodInsnNode) {
if(methodName == null || methodName.isEmpty()) {
return true;
}
if (!this.methodName.equals(methodInsnNode.name)) {
return false;
}
if (!excludes.isEmpty()) {
String ownerClassName = Type.getObjectType(methodInsnNode.owner).getClassName();
for (String exclude : excludes) {
if (MatchUtils.wildcardMatch(ownerClassName, exclude)) {
return false;
}
}
}
if (this.owner != null && !this.owner.equals(methodInsnNode.owner)) {
return false;
}
if (this.desc != null && !desc.equals(methodInsnNode.desc)) {
return false;
}
return true;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public boolean isWhenComplete() {
return whenComplete;
}
public void setWhenComplete(boolean whenComplete) {
this.whenComplete = whenComplete;
}
}

View File

@ -0,0 +1,60 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LineNumberNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.location.Location.LineLocation;
public class LineLocationMatcher implements LocationMatcher {
private List<Integer> targetLines = Collections.emptyList();
public LineLocationMatcher(int... targetLines) {
if (targetLines != null) {
ArrayList<Integer> result = new ArrayList<Integer>(targetLines.length);
for (int targetLine : targetLines) {
result.add(targetLine);
}
this.targetLines = result;
}
}
public LineLocationMatcher(List<Integer> targetLines) {
this.targetLines = targetLines;
}
@Override
public List<Location> match(MethodProcessor methodProcessor) {
List<Location> locations = new ArrayList<Location>();
AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode();
while (insnNode != null) {
if (insnNode instanceof LineNumberNode) {
LineNumberNode lineNumberNode = (LineNumberNode) insnNode;
if (match(lineNumberNode.line)) {
locations.add(new LineLocation(lineNumberNode, lineNumberNode.line));
}
}
insnNode = insnNode.getNext();
}
return locations;
}
private boolean match(int line) {
for (int targetLine : targetLines) {
if (targetLine == -1) {
return true;
} else if (line == targetLine) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,646 @@
package com.taobao.arthas.bytekit.asm.location;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.binding.BindingContext;
import com.taobao.arthas.bytekit.asm.binding.StackSaver;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
/**
* Specifies a location in a method at which a rule trigger should be inserted
*/
public abstract class Location {
AbstractInsnNode insnNode;
boolean whenComplete = false;
boolean stackNeedSave = false;
public Location(AbstractInsnNode insnNode) {
this(insnNode, false);
}
public Location(AbstractInsnNode insnNode, boolean whenComplete) {
this.insnNode = insnNode;
this.whenComplete = whenComplete;
}
public boolean isWhenComplete() {
return whenComplete;
}
public AbstractInsnNode getInsnNode() {
return insnNode;
}
/**
* 标记在这个location栈上原来的值可以被 callback 函数的return值替换掉
*
* @return
*/
public boolean canChangeByReturn() {
return false;
}
public boolean isStackNeedSave() {
return stackNeedSave;
}
public StackSaver getStackSaver() {
throw new UnsupportedOperationException("this location do not StackSaver, type:" + getLocationType());
}
/**
* identify the type of this location
*
* @return the type of this location
*/
public abstract LocationType getLocationType();
/**
* flag indicating that a field access location refers to field READ operations
*/
public static final int ACCESS_READ = 1;
/**
* flag indicating that a field access location refers to field WRITE operations
*/
public static final int ACCESS_WRITE = 2;
/**
* location identifying a method enter trigger point
*/
static class EnterLocation extends Location {
public EnterLocation(AbstractInsnNode enterInsnNode) {
super(enterInsnNode);
this.insnNode = enterInsnNode;
}
public LocationType getLocationType() {
return LocationType.ENTER;
}
}
/**
* location identifying a method line trigger point
*/
public static class LineLocation extends Location {
/**
* the line at which the trigger point should be inserted
*/
private int targetLine;
public LineLocation(AbstractInsnNode insnNode, int targetLine) {
super(insnNode);
this.targetLine = targetLine;
}
public LocationType getLocationType() {
return LocationType.LINE;
}
}
/**
* location identifying a generic access trigger point
*/
private static abstract class AccessLocation extends Location {
/**
* count identifying which access should be taken as the trigger point. if not
* specified as a parameter this defaults to the first access.
*/
protected int count;
/**
* flags identifying which type of access should be used to identify the
* trigger. this is either ACCESS_READ, ACCESS_WRITE or an OR of these two
* values
*/
protected int flags;
protected AccessLocation(AbstractInsnNode insnNode, int count, int flags, boolean whenComplete) {
super(insnNode, whenComplete);
this.count = count;
this.flags = flags;
}
public LocationType getLocationType() {
if ((flags & ACCESS_WRITE) != 0) {
if (whenComplete) {
return LocationType.WRITE_COMPLETED;
} else {
return LocationType.WRITE;
}
} else {
if (whenComplete) {
return LocationType.READ_COMPLETED;
} else {
return LocationType.READ;
}
}
}
}
/**
* location identifying a field access trigger point
*/
public static class FieldAccessLocation extends AccessLocation {
public FieldAccessLocation(FieldInsnNode fieldInsnNode, int count, int flags, boolean whenComplete) {
super(fieldInsnNode, count, flags, whenComplete);
}
}
/**
* location identifying a variable access trigger point
*/
private static class VariableAccessLocation extends AccessLocation {
/**
* the name of the variable being accessed at the point where the trigger point
* should be inserted
*/
private String variableName;
/**
* flag which is true if the name is a method parameter index such as $0, $1 etc
* otherwise false
*/
private boolean isIndex;
protected VariableAccessLocation(AbstractInsnNode insnNode, String variablename, int count, int flags,
boolean whenComplete) {
super(insnNode, count, flags, whenComplete);
this.variableName = variablename;
isIndex = variablename.matches("[0-9]+");
}
public LocationType getLocationType() {
if ((flags & ACCESS_WRITE) != 0) {
if (whenComplete) {
return LocationType.WRITE_COMPLETED;
} else {
return LocationType.WRITE;
}
} else {
if (whenComplete) {
return LocationType.READ_COMPLETED;
} else {
return LocationType.READ;
}
}
}
}
/**
* location identifying a method invocation trigger point
*/
public static class InvokeLocation extends Location {
/**
* count identifying which invocation should be taken as the trigger point. if
* not specified as a parameter this defaults to the first invocation.
*/
private int count;
public InvokeLocation(MethodInsnNode insnNode, int count, boolean whenComplete) {
super(insnNode, whenComplete);
this.count = count;
this.stackNeedSave = true;
}
@Override
public boolean canChangeByReturn() {
// 对于 invoke 只有在 complete 才能有返回值
return whenComplete;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public LocationType getLocationType() {
if (whenComplete) {
return LocationType.INVOKE_COMPLETED;
} else {
return LocationType.INVOKE;
}
}
@Override
public StackSaver getStackSaver() {
StackSaver stackSaver = null;
if(whenComplete) {
stackSaver = new StackSaver() {
@Override
public void store(InsnList instructions, BindingContext bindingContext) {
AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode();
MethodProcessor methodProcessor = bindingContext.getMethodProcessor();
if (insnNode instanceof MethodInsnNode) {
MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
String uniqueNameForMethod = AsmUtils.uniqueNameForMethod(methodInsnNode.owner, methodInsnNode.name,
methodInsnNode.desc);
Type invokeReturnType = Type.getMethodType(methodInsnNode.desc).getReturnType();
if(!invokeReturnType.equals(Type.VOID_TYPE)) {
LocalVariableNode invokeReturnVariableNode = methodProcessor.initInvokeReturnVariableNode(
uniqueNameForMethod, invokeReturnType);
AsmOpUtils.storeVar(instructions, invokeReturnType, invokeReturnVariableNode.index);
}
} else {
throw new IllegalArgumentException(
"InvokeReturnBinding location is not MethodInsnNode, insnNode: " + insnNode);
}
}
@Override
public void load(InsnList instructions, BindingContext bindingContext) {
AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode();
MethodProcessor methodProcessor = bindingContext.getMethodProcessor();
if (insnNode instanceof MethodInsnNode) {
MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
String uniqueNameForMethod = AsmUtils.uniqueNameForMethod(methodInsnNode.owner, methodInsnNode.name,
methodInsnNode.desc);
Type invokeReturnType = Type.getMethodType(methodInsnNode.desc).getReturnType();
if(!invokeReturnType.equals(Type.VOID_TYPE)) {
LocalVariableNode invokeReturnVariableNode = methodProcessor.initInvokeReturnVariableNode(
uniqueNameForMethod, invokeReturnType);
AsmOpUtils.loadVar(instructions, invokeReturnType, invokeReturnVariableNode.index);
}
} else {
throw new IllegalArgumentException(
"InvokeReturnBinding location is not MethodInsnNode, insnNode: " + insnNode);
}
}
@Override
public Type getType(BindingContext bindingContext) {
MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
return Type.getMethodType(methodInsnNode.desc).getReturnType();
}
};
}else {
stackSaver = new StackSaver() {
@Override
public void store(InsnList instructions, BindingContext bindingContext) {
// 需要从要调用的 函数的 des 找到参数的类型再从栈上一个个吐出来再保存到数组里
Location location = bindingContext.getLocation();
MethodProcessor methodProcessor = bindingContext.getMethodProcessor();
if(location instanceof InvokeLocation) {
InvokeLocation invokeLocation = (InvokeLocation) location;
// 如果是非 static的会有this指针
MethodInsnNode methodInsnNode = (MethodInsnNode)invokeLocation.getInsnNode();
Type methodType = Type.getMethodType(methodInsnNode.desc);
boolean isStatic = AsmUtils.isStatic(methodInsnNode);
Type[] argumentTypes = methodType.getArgumentTypes();
// 如果是非static则存放到数组的index要多 1
AsmOpUtils.push(instructions, argumentTypes.length + (isStatic ? 0 : 1));
AsmOpUtils.newArray(instructions, AsmOpUtils.OBJECT_TYPE);
LocalVariableNode invokeArgsVariableNode = methodProcessor.initInvokeArgsVariableNode();
AsmOpUtils.storeVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index);
// 从invoke的参数的后面一个个存到数组里
for(int i = argumentTypes.length - 1; i >= 0 ; --i) {
AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index);
AsmOpUtils.swap(instructions, argumentTypes[i], AsmOpUtils.OBJECT_ARRAY_TYPE);
// 如果是非static则存放到数组的index要多 1
AsmOpUtils.push(instructions, i + (isStatic ? 0 : 1));
AsmOpUtils.swap(instructions, argumentTypes[i], Type.INT_TYPE);
AsmOpUtils.box(instructions, argumentTypes[i]);
AsmOpUtils.arrayStore(instructions, AsmOpUtils.OBJECT_TYPE);
}
// 处理this
if(!isStatic) {
AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index);
AsmOpUtils.swap(instructions, AsmOpUtils.OBJECT_TYPE, AsmOpUtils.OBJECT_ARRAY_TYPE);
AsmOpUtils.push(instructions, 0);
AsmOpUtils.swap(instructions, AsmOpUtils.OBJECT_TYPE, Type.INT_TYPE);
AsmOpUtils.arrayStore(instructions, AsmOpUtils.OBJECT_TYPE);
}
}else {
throw new IllegalArgumentException("location is not a InvokeLocation, location: " + location);
}
}
@Override
public void load(InsnList instructions, BindingContext bindingContext) {
// 从数组里取出来一个个再放到栈上要检查是否要unbox
Location location = bindingContext.getLocation();
MethodProcessor methodProcessor = bindingContext.getMethodProcessor();
LocalVariableNode invokeArgsVariableNode = methodProcessor.initInvokeArgsVariableNode();
if(location instanceof InvokeLocation) {
InvokeLocation invokeLocation = (InvokeLocation) location;
// 如果是非 static的会有this指针
MethodInsnNode methodInsnNode = (MethodInsnNode)invokeLocation.getInsnNode();
Type methodType = Type.getMethodType(methodInsnNode.desc);
boolean isStatic = AsmUtils.isStatic(methodInsnNode);
Type[] argumentTypes = methodType.getArgumentTypes();
if(!isStatic) {
// 取出this
AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index);
AsmOpUtils.push(instructions, 0);
AsmOpUtils.arrayLoad(instructions, AsmOpUtils.OBJECT_TYPE);
AsmOpUtils.checkCast(instructions, Type.getObjectType(methodInsnNode.owner));
}
for(int i = 0; i < argumentTypes.length; ++i) {
AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index);
AsmOpUtils.push(instructions, i + (isStatic ? 0 : 1));
AsmOpUtils.arrayLoad(instructions, AsmOpUtils.OBJECT_TYPE);
// TODO 这里直接 unbox 就可以了unbox里带有 check cast
if(AsmOpUtils.needBox(argumentTypes[i])) {
AsmOpUtils.unbox(instructions, argumentTypes[i]);
}else {
AsmOpUtils.checkCast(instructions, argumentTypes[i]);
}
}
}else {
throw new IllegalArgumentException("location is not a InvokeLocation, location: " + location);
}
}
@Override
public Type getType(BindingContext bindingContext) {
throw new UnsupportedOperationException("InvokeLocation saver do not support getType()");
}
};
}
return stackSaver;
}
}
/**
* location identifying a synchronization trigger point
*/
public static class SyncEnterLocation extends Location {
/**
* count identifying which synchronization should be taken as the trigger point.
* if not specified as a parameter this defaults to the first synchronization.
*/
private int count;
public SyncEnterLocation(AbstractInsnNode insnNode, int count, boolean whenComplete) {
super(insnNode, whenComplete);
this.count = count;
this.whenComplete = whenComplete;
this.stackNeedSave = !whenComplete;
}
public LocationType getLocationType() {
if (whenComplete) {
return LocationType.SYNC_ENTER_COMPLETED;
} else {
return LocationType.SYNC_ENTER;
}
}
@Override
public StackSaver getStackSaver() {
return new StackSaver() {
@Override
public void store(InsnList instructions, BindingContext bindingContext) {
LocalVariableNode variableNode = bindingContext.getMethodProcessor().initMonitorVariableNode();
AsmOpUtils.storeVar(instructions, AsmOpUtils.OBJECT_TYPE, variableNode.index);
}
@Override
public void load(InsnList instructions, BindingContext bindingContext) {
LocalVariableNode variableNode = bindingContext.getMethodProcessor().initMonitorVariableNode();
AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_TYPE, variableNode.index);
}
@Override
public Type getType(BindingContext bindingContext) {
return AsmOpUtils.OBJECT_TYPE;
}
};
}
}
/**
* location identifying a synchronization trigger point
*/
public static class SyncExitLocation extends Location {
/**
* count identifying which synchronization should be taken as the trigger point.
* if not specified as a parameter this defaults to the first synchronization.
*/
private int count;
public SyncExitLocation(AbstractInsnNode insnNode, int count, boolean whenComplete) {
super(insnNode, whenComplete);
this.count = count;
this.whenComplete = whenComplete;
this.stackNeedSave = !whenComplete;
}
public LocationType getLocationType() {
if (whenComplete) {
return LocationType.SYNC_ENTER_COMPLETED;
} else {
return LocationType.SYNC_ENTER;
}
}
@Override
public StackSaver getStackSaver() {
return new StackSaver() {
@Override
public void store(InsnList instructions, BindingContext bindingContext) {
LocalVariableNode variableNode = bindingContext.getMethodProcessor().initMonitorVariableNode();
AsmOpUtils.storeVar(instructions, AsmOpUtils.OBJECT_TYPE, variableNode.index);
}
@Override
public void load(InsnList instructions, BindingContext bindingContext) {
LocalVariableNode variableNode = bindingContext.getMethodProcessor().initMonitorVariableNode();
AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_TYPE, variableNode.index);
}
@Override
public Type getType(BindingContext bindingContext) {
return AsmOpUtils.OBJECT_TYPE;
}
};
}
}
/**
* location identifying a throw trigger point
*/
public static class ThrowLocation extends Location {
/**
* count identifying which throw operation should be taken as the trigger point.
* if not specified as a parameter this defaults to the first throw.
*/
private int count;
public ThrowLocation(AbstractInsnNode insnNode, int count) {
super(insnNode);
this.count = count;
stackNeedSave = true;
}
@Override
public boolean canChangeByReturn() {
return true;
}
public LocationType getLocationType() {
return LocationType.THROW;
}
public StackSaver getStackSaver() {
StackSaver stackSaver = new StackSaver() {
@Override
public void store(InsnList instructions, BindingContext bindingContext) {
LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode();
AsmOpUtils.storeVar(instructions, Type.getType(Throwable.class), throwVariableNode.index);
}
@Override
public void load(InsnList instructions, BindingContext bindingContext) {
LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode();
AsmOpUtils.loadVar(instructions, Type.getType(Throwable.class), throwVariableNode.index);
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(Throwable.class);
}
};
return stackSaver;
}
}
/**
* location identifying a method exit trigger point
*/
public static class ExitLocation extends Location {
public ExitLocation(AbstractInsnNode insnNode) {
super(insnNode);
stackNeedSave = true;
}
@Override
public boolean canChangeByReturn() {
return true;
}
public LocationType getLocationType() {
return LocationType.EXIT;
}
public StackSaver getStackSaver() {
StackSaver stackSaver = new StackSaver() {
@Override
public void store(InsnList instructions, BindingContext bindingContext) {
Type returnType = bindingContext.getMethodProcessor().getReturnType();
if(!returnType.equals(Type.VOID_TYPE)) {
LocalVariableNode returnVariableNode = bindingContext.getMethodProcessor().initReturnVariableNode();
AsmOpUtils.storeVar(instructions, returnType, returnVariableNode.index);
}
}
@Override
public void load(InsnList instructions, BindingContext bindingContext) {
Type returnType = bindingContext.getMethodProcessor().getReturnType();
if(!returnType.equals(Type.VOID_TYPE)) {
LocalVariableNode returnVariableNode = bindingContext.getMethodProcessor().initReturnVariableNode();
AsmOpUtils.loadVar(instructions, returnType, returnVariableNode.index);
}
}
@Override
public Type getType(BindingContext bindingContext) {
return bindingContext.getMethodProcessor().getReturnType();
}
};
return stackSaver;
}
}
/**
* location identifying a method exceptional exit trigger point
*/
public static class ExceptionExitLocation extends Location {
public ExceptionExitLocation(AbstractInsnNode insnNode) {
super(insnNode, true);
stackNeedSave = true;
}
public LocationType getLocationType() {
return LocationType.EXCEPTION_EXIT;
}
public StackSaver getStackSaver() {
StackSaver stackSaver = new StackSaver() {
@Override
public void store(InsnList instructions, BindingContext bindingContext) {
LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode();
AsmOpUtils.storeVar(instructions, Type.getType(Throwable.class), throwVariableNode.index);
}
@Override
public void load(InsnList instructions, BindingContext bindingContext) {
LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode();
AsmOpUtils.loadVar(instructions, Type.getType(Throwable.class), throwVariableNode.index);
}
@Override
public Type getType(BindingContext bindingContext) {
return Type.getType(Throwable.class);
}
};
return stackSaver;
}
}
}

View File

@ -0,0 +1,11 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.List;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
public interface LocationMatcher {
public List<Location> match(MethodProcessor methodProcessor);
}

View File

@ -0,0 +1,86 @@
package com.taobao.arthas.bytekit.asm.location;
public enum LocationType {
/**
* user define.
*/
USER_DEFINE,
/**
* method enter.
*
*/
ENTER,
/**
* line number.
*
*/
LINE,
/**
* field read operation.
*
*/
READ,
/**
* field read operation.
*/
READ_COMPLETED,
/**
* field write operation.
*
*/
WRITE,
/**
* field write operation.
*
*/
WRITE_COMPLETED,
/**
* method invoke operation
*
*/
INVOKE,
/**
* method invoke operation
*
*/
INVOKE_COMPLETED,
/**
* synchronize operation
*
*/
SYNC_ENTER,
/**
* synchronize operation
*
*/
SYNC_ENTER_COMPLETED,
/**
* synchronize operation
*
*/
SYNC_EXIT,
/**
* synchronize operation
*
*/
SYNC_EXIT_COMPLETED,
/**
* throw
*/
THROW,
/**
* return
*/
EXIT,
/**
* add try/catch
*/
EXCEPTION_EXIT;
}

View File

@ -0,0 +1,9 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.List;
public class MatchResult {
List<Location> locations;
}

View File

@ -0,0 +1,46 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.location.Location.SyncEnterLocation;
public class SyncExitLocationMatcher implements LocationMatcher {
private int count;
boolean whenComplete;
public SyncExitLocationMatcher(int count, boolean whenComplete) {
this.count = count;
this.whenComplete = whenComplete;
}
@Override
public List<Location> match(MethodProcessor methodProcessor) {
List<Location> locations = new ArrayList<Location>();
AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode();
int matchedCount = 0;
while (insnNode != null) {
if (insnNode instanceof InsnNode) {
InsnNode node = (InsnNode) insnNode;
if (node.getOpcode() == Opcodes.MONITOREXIT) {
++matchedCount;
if (count <= 0 || count == matchedCount) {
SyncEnterLocation location = new SyncEnterLocation(node, matchedCount, whenComplete);
locations.add(location);
}
}
}
insnNode = insnNode.getNext();
}
return locations;
}
}

View File

@ -0,0 +1,53 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.location.Location.SyncEnterLocation;
public class SyncLocationMatcher implements LocationMatcher {
private int count;
boolean whenComplete;
int opcode;
public SyncLocationMatcher(int opcode, int count, boolean whenComplete) {
if (!(Opcodes.MONITORENTER == opcode || Opcodes.MONITOREXIT == opcode)) {
throw new IllegalArgumentException(
"SyncLocationMatcher only support Opcodes.MONITORENTER or Opcodes.MONITOREXIT.");
}
this.opcode = opcode;
this.count = count;
this.whenComplete = whenComplete;
}
@Override
public List<Location> match(MethodProcessor methodProcessor) {
List<Location> locations = new ArrayList<Location>();
AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode();
int matchedCount = 0;
while (insnNode != null) {
if (insnNode instanceof InsnNode) {
InsnNode node = (InsnNode) insnNode;
if (node.getOpcode() == opcode) {
++matchedCount;
if (count <= 0 || count == matchedCount) {
SyncEnterLocation location = new SyncEnterLocation(node, matchedCount, whenComplete);
locations.add(location);
}
}
}
insnNode = insnNode.getNext();
}
return locations;
}
}

View File

@ -0,0 +1,47 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.asm.location.Location.ThrowLocation;
public class ThrowLocationMatcher implements LocationMatcher {
public ThrowLocationMatcher(int count) {
this.count = count;
}
/**
* count identifying which invocation should be taken as the trigger point.
* if not specified as a parameter this defaults to the first invocation.
*/
private int count;
@Override
public List<Location> match(MethodProcessor methodProcessor) {
List<Location> locations = new ArrayList<Location>();
AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode();
int matchedCount = 0;
while (insnNode != null) {
if (insnNode instanceof InsnNode) {
InsnNode node = (InsnNode) insnNode;
if (node.getOpcode() == Opcodes.ATHROW) {
++matchedCount;
if (count <= 0 || count == matchedCount) {
ThrowLocation location = new ThrowLocation(node, matchedCount);
locations.add(location);
}
}
}
insnNode = insnNode.getNext();
}
return locations;
}
}

View File

@ -0,0 +1,34 @@
package com.taobao.arthas.bytekit.asm.location;
import java.util.List;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
public class VariableAccessLocationMatcher extends AccessLocationMatcher {
/**
* the name of the variable being accessed at the point where the trigger
* point should be inserted
*/
private String variableName;
/**
* flag which is true if the name is a method parameter index such as $0, $1
* etc otherwise false
*/
private boolean isIndex;
protected VariableAccessLocationMatcher(String variablename, int count, int flags, boolean whenComplete) {
super(count, flags, whenComplete);
this.variableName = variablename;
isIndex = variablename.matches("[0-9]+");
}
@Override
public List<Location> match(MethodProcessor methodProcessor) {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -0,0 +1,7 @@
package com.taobao.arthas.bytekit.asm.matcher;
public interface ClassMatcher {
boolean match(String name, ClassLoader classLoader);
}

View File

@ -0,0 +1,8 @@
package com.taobao.arthas.bytekit.asm.matcher;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
public interface MethodMatcher {
boolean match(String className, MethodNode methodNode);
}

View File

@ -0,0 +1,21 @@
package com.taobao.arthas.bytekit.utils;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import net.bytebuddy.agent.ByteBuddyAgent;
public class AgentUtils {
private static class InstrumentationHolder {
static final Instrumentation instance = ByteBuddyAgent.install();
}
public static void redefine(Class<?> theClass, byte[] theClassFile)
throws ClassNotFoundException, UnmodifiableClassException {
ClassDefinition classDefinition = new ClassDefinition(theClass, theClassFile);
InstrumentationHolder.instance.redefineClasses(classDefinition);
}
}

View File

@ -0,0 +1,12 @@
package com.taobao.arthas.bytekit.utils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class AnnotationUtils {
public static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) {
return method.getAnnotation(annotationType);
}
}

View File

@ -0,0 +1,454 @@
package com.taobao.arthas.bytekit.utils;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.commons.Method;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.IntInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LdcInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.TypeInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.VarInsnNode;
public class AsmOpUtils {
private static final Type BYTE_TYPE = Type.getObjectType("java/lang/Byte");
private static final Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean");
private static final Type SHORT_TYPE = Type.getObjectType("java/lang/Short");
private static final Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character");
private static final Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer");
private static final Type FLOAT_TYPE = Type.getObjectType("java/lang/Float");
private static final Type LONG_TYPE = Type.getObjectType("java/lang/Long");
private static final Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double");
public static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
public static final Type OBJECT_ARRAY_TYPE = Type.getType(Object[].class);
public static final Type STRING_TYPE = Type.getObjectType("java/lang/String");
public static final Type STRING_ARRAY_TYPE = Type.getType(String[].class);
private static final Type NUMBER_TYPE = Type.getObjectType("java/lang/Number");
private static final Method BOOLEAN_VALUE = Method.getMethod("boolean booleanValue()");
private static final Method CHAR_VALUE = Method.getMethod("char charValue()");
private static final Method BYTE_VALUE = Method.getMethod("byte byteValue()");
private static final Method SHORT_VALUE = Method.getMethod("short shortValue()");
private static final Method INT_VALUE = Method.getMethod("int intValue()");
private static final Method FLOAT_VALUE = Method.getMethod("float floatValue()");
private static final Method LONG_VALUE = Method.getMethod("long longValue()");
private static final Method DOUBLE_VALUE = Method.getMethod("double doubleValue()");
public static Type getBoxedType(final Type type) {
switch (type.getSort()) {
case Type.BYTE:
return BYTE_TYPE;
case Type.BOOLEAN:
return BOOLEAN_TYPE;
case Type.SHORT:
return SHORT_TYPE;
case Type.CHAR:
return CHARACTER_TYPE;
case Type.INT:
return INTEGER_TYPE;
case Type.FLOAT:
return FLOAT_TYPE;
case Type.LONG:
return LONG_TYPE;
case Type.DOUBLE:
return DOUBLE_TYPE;
}
return type;
}
public static Method getUnBoxMethod(final Type type) {
switch (type.getSort()) {
case Type.BYTE:
return BYTE_VALUE;
case Type.BOOLEAN:
return BOOLEAN_VALUE;
case Type.SHORT:
return SHORT_VALUE;
case Type.CHAR:
return CHAR_VALUE;
case Type.INT:
return INT_VALUE;
case Type.FLOAT:
return FLOAT_VALUE;
case Type.LONG:
return LONG_VALUE;
case Type.DOUBLE:
return DOUBLE_VALUE;
}
throw new IllegalArgumentException(type + " is not a primitive type.");
}
public static void newInstance(final InsnList instructions, final Type type) {
instructions.add(new TypeInsnNode(Opcodes.NEW, type.getInternalName()));
}
public static void push(InsnList insnList, final int value) {
if (value >= -1 && value <= 5) {
insnList.add(new InsnNode(Opcodes.ICONST_0 + value));
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
insnList.add(new IntInsnNode(Opcodes.BIPUSH, value));
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
insnList.add(new IntInsnNode(Opcodes.SIPUSH, value));
} else {
insnList.add(new LdcInsnNode(value));
}
}
public static void push(InsnList insnList, final String value) {
if (value == null) {
insnList.add(new InsnNode(Opcodes.ACONST_NULL));
} else {
insnList.add(new LdcInsnNode(value));
}
}
public static void pushNUll(InsnList insnList) {
insnList.add(new InsnNode(Opcodes.ACONST_NULL));
}
/**
* @see org.objectweb.asm.tree.LdcInsnNode#cst
* @param value
*/
public static void ldc(InsnList insnList, Object value) {
insnList.add(new LdcInsnNode(value));
}
public static void newArray(final InsnList insnList, final Type type) {
insnList.add(new TypeInsnNode(Opcodes.ANEWARRAY, type.getInternalName()));
}
public static void dup(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP));
}
public static void dup2(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP2));
}
public static void dupX1(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP_X1));
}
public static void dupX2(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP_X2));
}
/**
* Generates a DUP2_X1 instruction.
*/
public static void dup2X1(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP2_X1));
}
/**
* Generates a DUP2_X2 instruction.
*/
public static void dup2X2(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.DUP2_X2));
}
public static void pop(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.POP));
}
/**
* Generates a POP2 instruction.
*/
public static void pop2(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.POP2));
}
public static void swap(final InsnList insnList) {
insnList.add(new InsnNode(Opcodes.SWAP));
}
/**
* Generates the instructions to swap the top two stack values.
*
* @param prev
* type of the top - 1 stack value.
* @param type
* type of the top stack value.
*/
public static void swap(final InsnList insnList, final Type prev, final Type type) {
if (type.getSize() == 1) {
if (prev.getSize() == 1) {
swap(insnList); // same as dupX1(), pop();
} else {
dupX2(insnList);
pop(insnList);
}
} else {
if (prev.getSize() == 1) {
dup2X1(insnList);
pop2(insnList);
} else {
dup2X2(insnList);
pop2(insnList);
}
}
}
public static void box(final InsnList instructions, Type type) {
if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return;
}
if (type == Type.VOID_TYPE) {
// push null
instructions.add(new InsnNode(Opcodes.ACONST_NULL));
} else {
Type boxed = getBoxedType(type);
// new instance.
newInstance(instructions, boxed);
if (type.getSize() == 2) {
// Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
// dupX2
dupX2(instructions);
// dupX2
dupX2(instructions);
// pop
pop(instructions);
} else {
// p -> po -> opo -> oop -> o
// dupX1
dupX1(instructions);
// swap
swap(instructions);
}
invokeConstructor(instructions, boxed, new Method("<init>", Type.VOID_TYPE, new Type[] { type }));
}
}
public static void invokeConstructor(final InsnList instructions, final Type type, final Method method) {
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
instructions
.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, owner, method.getName(), method.getDescriptor(), false));
}
/**
*
* @param instructions
* @param type
* @see org.objectweb.asm.commons.GeneratorAdapter#unbox(Type)
*/
public static void unbox(final InsnList instructions, Type type) {
Type t = NUMBER_TYPE;
Method sig = null;
switch (type.getSort()) {
case Type.VOID:
return;
case Type.CHAR:
t = CHARACTER_TYPE;
sig = CHAR_VALUE;
break;
case Type.BOOLEAN:
t = BOOLEAN_TYPE;
sig = BOOLEAN_VALUE;
break;
case Type.DOUBLE:
sig = DOUBLE_VALUE;
break;
case Type.FLOAT:
sig = FLOAT_VALUE;
break;
case Type.LONG:
sig = LONG_VALUE;
break;
case Type.INT:
case Type.SHORT:
case Type.BYTE:
sig = INT_VALUE;
}
if (sig == null) {
instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, type.getInternalName()));
} else {
instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, t.getInternalName()));
instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, t.getInternalName(), sig.getName(),
sig.getDescriptor(), false));
}
}
public static boolean needBox(Type type) {
switch (type.getSort()) {
case Type.BYTE:
case Type.BOOLEAN:
case Type.SHORT:
case Type.CHAR:
case Type.INT:
case Type.FLOAT:
case Type.LONG:
case Type.DOUBLE:
return true;
}
return false;
}
public static void getStatic(final InsnList insnList, final Type owner, final String name, final Type type) {
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, owner.getInternalName(), name, type.getDescriptor()));
}
/**
* Generates the instruction to push the value of a non static field on the
* stack.
*
* @param owner
* the class in which the field is defined.
* @param name
* the name of the field.
* @param type
* the type of the field.
*/
public static void getField(final InsnList insnList, final Type owner, final String name, final Type type) {
insnList.add(new FieldInsnNode(Opcodes.GETFIELD, owner.getInternalName(), name, type.getDescriptor()));
}
public static void arrayStore(final InsnList instructions, final Type type) {
instructions.add(new InsnNode(type.getOpcode(Opcodes.IASTORE)));
}
public static void arrayLoad(final InsnList instructions, final Type type) {
instructions.add(new InsnNode(type.getOpcode(Opcodes.IALOAD)));
}
/**
* Generates the instruction to load 'this' on the stack.
* @see org.objectweb.asm.commons.GeneratorAdapter#loadThis()
* @param instructions
*/
public static void loadThis(final InsnList instructions) {
instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
}
/**
* Generates the instructions to load all the method arguments on the stack,
* as a single object array.
*
* @see org.objectweb.asm.commons.GeneratorAdapter#loadArgArray()
*/
public static void loadArgArray(final InsnList instructions, MethodNode methodNode) {
boolean isStatic = AsmUtils.isStatic(methodNode);
Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc);
push(instructions, argumentTypes.length);
newArray(instructions, OBJECT_TYPE);
for (int i = 0; i < argumentTypes.length; i++) {
dup(instructions);
push(instructions, i);
loadArg(isStatic, instructions, argumentTypes, i);
box(instructions, argumentTypes[i]);
arrayStore(instructions, OBJECT_TYPE);
}
}
public static void loadArgs(final InsnList instructions, MethodNode methodNode) {
Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc);
boolean isStatic = AsmUtils.isStatic(methodNode);
for (int i = 0; i < argumentTypes.length; i++) {
loadArg(isStatic, instructions, argumentTypes, i);
}
}
public static void loadArg(boolean staticAccess, final InsnList instructions, Type[] argumentTypes, int i) {
final int index = getArgIndex(staticAccess, argumentTypes, i);
final Type type = argumentTypes[i];
instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), index));
}
static int getArgIndex(boolean staticAccess, final Type[] argumentTypes, final int arg) {
int index = staticAccess ? 0 : 1;
for (int i = 0; i < arg; i++) {
index += argumentTypes[i].getSize();
}
return index;
}
public static void loadVar(final InsnList instructions, Type type, final int index) {
instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), index));
}
public static void storeVar(final InsnList instructions, Type type, final int index) {
instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ISTORE), index));
}
/**
* Generates a type dependent instruction.
*
* @param opcode
* the instruction's opcode.
* @param type
* the instruction's operand.
*/
private static void typeInsn(final InsnList instructions, final int opcode, final Type type) {
instructions.add(new TypeInsnNode(opcode, type.getInternalName()));
}
/**
* Generates the instruction to check that the top stack value is of the
* given type.
*
* @param type
* a class or interface type.
*/
public static void checkCast(final InsnList instructions, final Type type) {
if (!type.equals(OBJECT_TYPE)) {
typeInsn(instructions, Opcodes.CHECKCAST, type);
}
}
public static void throwException(final InsnList instructions) {
instructions.add(new InsnNode(Opcodes.ATHROW));
}
public static boolean isReturnCode(final int opcode) {
return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN;
}
public static List<LocalVariableNode> validVariables(List<LocalVariableNode> localVariables,
AbstractInsnNode currentInsnNode) {
List<LocalVariableNode> results = new ArrayList<LocalVariableNode>();
// find out current valid local variables
for (LocalVariableNode localVariableNode : localVariables) {
for (AbstractInsnNode iter = localVariableNode.start; iter != null
&& (!iter.equals(localVariableNode.end)); iter = iter.getNext()) {
if (iter.equals(currentInsnNode)) {
results.add(localVariableNode);
break;
}
}
}
return results;
}
}

View File

@ -0,0 +1,499 @@
package com.taobao.arthas.bytekit.utils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.alibaba.arthas.deps.org.objectweb.asm.ClassReader;
import com.alibaba.arthas.deps.org.objectweb.asm.ClassVisitor;
import com.alibaba.arthas.deps.org.objectweb.asm.ClassWriter;
import com.alibaba.arthas.deps.org.objectweb.asm.Label;
import com.alibaba.arthas.deps.org.objectweb.asm.MethodVisitor;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.commons.ClassRemapper;
import com.alibaba.arthas.deps.org.objectweb.asm.commons.JSRInlinerAdapter;
import com.alibaba.arthas.deps.org.objectweb.asm.commons.Remapper;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.TypeInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.util.ASMifier;
import com.alibaba.arthas.deps.org.objectweb.asm.util.TraceClassVisitor;
public class AsmUtils {
public static ClassNode loadClass(Class<?> clazz) throws IOException {
String resource = clazz.getName().replace('.', '/') + ".class";
InputStream is = clazz.getClassLoader().getResourceAsStream(resource);
ClassReader cr = new ClassReader(is);
ClassNode classNode = new ClassNode();
cr.accept(classNode, ClassReader.SKIP_FRAMES);
return classNode;
}
public static ClassNode toClassNode(byte[] classBytes) {
ClassReader reader = new ClassReader(classBytes);
ClassNode result = new ClassNode(Opcodes.ASM7);
reader.accept(result, ClassReader.SKIP_FRAMES);
return result;
}
public static byte[] toBytes(ClassNode classNode) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
classNode.accept(writer);
return writer.toByteArray();
}
public static byte[] renameClass(byte[] classBytes, final String newClassName) {
final String internalName = newClassName.replace('.', '/');
ClassReader reader = new ClassReader(classBytes);
ClassWriter writer = new ClassWriter(0);
class RenameRemapper extends Remapper {
private String className;
@Override
public String map(String typeName) {
if (typeName.equals(className)) {
return internalName;
}
return super.map(typeName);
}
public void setClassName(String className) {
this.className = className;
}
}
final RenameRemapper renameRemapper = new RenameRemapper();
ClassRemapper adapter = new ClassRemapper(writer, renameRemapper) {
@Override
public void visit(final int version, final int access, final String name, final String signature,
final String superName, final String[] interfaces) {
renameRemapper.setClassName(name);
super.visit(version, access, name, signature, superName, interfaces);
}
};
reader.accept(adapter, ClassReader.EXPAND_FRAMES);
writer.visitEnd();
return writer.toByteArray();
}
public static void replaceMethod(ClassNode classNode, MethodNode methodNode) {
for (int index = 0; index < classNode.methods.size(); ++index) {
MethodNode tmp = classNode.methods.get(index);
if (tmp.name.equals(methodNode.name) && tmp.desc.equals(methodNode.desc)) {
classNode.methods.set(index, methodNode);
}
}
}
public static String toASMCode(byte[] bytecode) throws IOException {
return toASMCode(bytecode, true);
}
public static String toASMCode(byte[] bytecode, boolean debug) throws IOException {
int flags = ClassReader.SKIP_DEBUG;
if (debug) {
flags = 0;
}
ClassReader cr = new ClassReader(new ByteArrayInputStream(bytecode));
StringWriter sw = new StringWriter();
cr.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(sw)), flags);
return sw.toString();
}
public static String toASMCode(ClassNode classNode) {
StringWriter sw = new StringWriter();
classNode.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(sw)));
return sw.toString();
}
public static String toASMCode(MethodNode methodNode) {
ClassNode classNode = new ClassNode();
classNode.methods.add(methodNode);
return toASMCode(classNode);
}
public static MethodNode newMethodNode(MethodNode source) {
return new MethodNode(Opcodes.ASM7, source.access, source.name, source.desc, source.signature,
source.exceptions.toArray(new String[source.exceptions.size()]));
}
public static MethodNode removeJSRInstructions(MethodNode subjectMethod) {
MethodNode result = newMethodNode(subjectMethod);
subjectMethod.accept(new JSRInlinerAdapter(result, subjectMethod.access, subjectMethod.name, subjectMethod.desc,
subjectMethod.signature,
subjectMethod.exceptions.toArray(new String[subjectMethod.exceptions.size()])));
return result;
}
public static MethodNode removeLineNumbers(MethodNode methodNode) {
MethodNode result = newMethodNode(methodNode);
methodNode.accept(new MethodVisitor(Opcodes.ASM7, result) {
public void visitLineNumber(int line, Label start) {
}
});
return result;
}
public static MethodNode findFirstMethod(Collection<MethodNode> methodNodes, String name) {
for (MethodNode methodNode : methodNodes) {
if (methodNode.name.equals(name)) {
return methodNode;
}
}
return null;
}
public static List<MethodNode> findMethods(Collection<MethodNode> methodNodes, String name) {
List<MethodNode> result = new ArrayList<MethodNode>();
for (MethodNode methodNode : methodNodes) {
if (methodNode.name.equals(name)) {
result.add(methodNode);
}
}
return result;
}
public static MethodNode findMethod(Collection<MethodNode> methodNodes, MethodNode target) {
return findMethod(methodNodes, target.name, target.desc);
}
public static MethodNode findMethod(Collection<MethodNode> methodNodes, String name, String desc) {
for (MethodNode methodNode : methodNodes) {
if (methodNode.name.equals(name) && methodNode.desc.equals(desc)) {
return methodNode;
}
}
return null;
}
public static AbstractInsnNode findInitConstructorInstruction(MethodNode methodNode) {
int nested = 0;
for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode
.getNext()) {
if (insnNode instanceof TypeInsnNode) {
if (insnNode.getOpcode() == Opcodes.NEW) {
// new object().
nested++;
}
} else if (insnNode instanceof MethodInsnNode) {
final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
if (methodInsnNode.getOpcode() == Opcodes.INVOKESPECIAL && methodInsnNode.name.equals("<init>")) {
if (--nested < 0) {
// find this() or super().
return insnNode.getNext();
}
}
}
}
return null;
}
public static List<MethodInsnNode> findMethodInsnNodeWithPrefix(MethodNode methodNode, String prefix) {
List<MethodInsnNode> result = new ArrayList<MethodInsnNode>();
for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode
.getNext()) {
if (insnNode instanceof MethodInsnNode) {
final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
if(methodInsnNode.name.startsWith(prefix)) {
result.add(methodInsnNode);
}
}
}
return result;
}
public static List<MethodInsnNode> findMethodInsnNode(MethodNode methodNode, String owner, String name,
String desc) {
List<MethodInsnNode> result = new ArrayList<MethodInsnNode>();
for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode
.getNext()) {
if (insnNode instanceof MethodInsnNode) {
final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
if (methodInsnNode.owner.equals(owner) && methodInsnNode.name.equals(name)
&& methodInsnNode.desc.equals(desc)) {
result.add(methodInsnNode);
}
}
}
return result;
}
public static boolean isStatic(MethodNode methodNode) {
return (methodNode.access & Opcodes.ACC_STATIC) != 0;
}
public static boolean isStatic(MethodInsnNode methodInsnNode) {
return methodInsnNode.getOpcode() == Opcodes.INVOKESTATIC;
}
public static boolean isConstructor(MethodNode methodNode) {
return methodNode.name != null && methodNode.name.equals("<init>");
}
public String[] getParameterNames(MethodNode methodNode) {
Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc);
if (argumentTypes.length == 0) {
return new String[0];
}
final List<LocalVariableNode> localVariableNodes = methodNode.localVariables;
int localVariableStartIndex = 1;
if (isStatic(methodNode)) {
// static method is none this.
localVariableStartIndex = 0;
}
if (localVariableNodes == null || localVariableNodes.size() <= localVariableStartIndex ||
(argumentTypes.length + localVariableStartIndex) > localVariableNodes.size()) {
// make simple argument names.
final String[] names = new String[argumentTypes.length];
for (int i = 0; i < argumentTypes.length; i++) {
final String className = argumentTypes[i].getClassName();
if (className != null) {
final int findIndex = className.lastIndexOf('.');
if (findIndex == -1) {
names[i] = className;
} else {
names[i] = className.substring(findIndex + 1);
}
} else {
names[i] = argumentTypes[i].getDescriptor();
}
}
return names;
}
// sort by index.
Collections.sort(localVariableNodes, new Comparator<LocalVariableNode>() {
@Override
public int compare(LocalVariableNode o1, LocalVariableNode o2) {
return o1.index - o2.index;
}
});
String[] names = new String[argumentTypes.length];
for (int i = 0; i < argumentTypes.length; i++) {
final String name = localVariableNodes.get(localVariableStartIndex++).name;
if (name != null) {
names[i] = name;
} else {
names[i] = "";
}
}
return names;
}
public static MethodNode copy(MethodNode source) {
MethodNode result = newMethodNode(source);
source.accept(result);
return result;
}
public static ClassNode copy(ClassNode source) {
ClassNode result = new ClassNode(Opcodes.ASM7);
source.accept(new ClassVisitor(Opcodes.ASM7, result) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new JSRInlinerAdapter(mv, access, name, desc, signature, exceptions);
}
});
return result;
}
public static String methodDeclaration(MethodInsnNode methodInsnNode) {
StringBuilder sb = new StringBuilder(128);
int opcode = methodInsnNode.getOpcode();
if(opcode == Opcodes.INVOKESTATIC) {
sb.append("static ");
}
Type methodType = Type.getMethodType(methodInsnNode.desc);
Type ownerType = Type.getObjectType(methodInsnNode.owner);
//skip constructor return type
if(methodInsnNode.name.equals("<init>")) {
sb.append(ownerType.getClassName());
}else {
sb.append(methodType.getReturnType().getClassName()).append(' ');
sb.append(methodInsnNode.name);
}
sb.append('(');
Type[] argumentTypes = methodType.getArgumentTypes();
for(int i = 0 ; i < argumentTypes.length; ++i) {
sb.append(argumentTypes[i].getClassName());
if(i != argumentTypes.length - 1) {
sb.append(", ");
}
}
sb.append(')');
return sb.toString();
}
public static String methodDeclaration(Type owner, MethodNode methodNode) {
int access = methodNode.access;
StringBuilder sb = new StringBuilder(128);
// int ACC_PUBLIC = 0x0001; // class, field, method
// int ACC_PRIVATE = 0x0002; // class, field, method
// int ACC_PROTECTED = 0x0004; // class, field, method
// int ACC_STATIC = 0x0008; // field, method
// int ACC_FINAL = 0x0010; // class, field, method, parameter
// int ACC_SUPER = 0x0020; // class
// int ACC_SYNCHRONIZED = 0x0020; // method
// int ACC_OPEN = 0x0020; // module
// int ACC_TRANSITIVE = 0x0020; // module requires
// int ACC_VOLATILE = 0x0040; // field
// int ACC_BRIDGE = 0x0040; // method
// int ACC_STATIC_PHASE = 0x0040; // module requires
// int ACC_VARARGS = 0x0080; // method
// int ACC_TRANSIENT = 0x0080; // field
// int ACC_NATIVE = 0x0100; // method
// int ACC_INTERFACE = 0x0200; // class
// int ACC_ABSTRACT = 0x0400; // class, method
// int ACC_STRICT = 0x0800; // method
// int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module *
// int ACC_ANNOTATION = 0x2000; // class
// int ACC_ENUM = 0x4000; // class(?) field inner
// int ACC_MANDATED = 0x8000; // parameter, module, module *
// int ACC_MODULE = 0x8000; // class
if((access & Opcodes.ACC_PUBLIC) != 0) {
sb.append("public ");
}
if((access & Opcodes.ACC_PRIVATE) != 0) {
sb.append("private ");
}
if((access & Opcodes.ACC_PROTECTED) != 0) {
sb.append("protected ");
}
if((access & Opcodes.ACC_STATIC) != 0) {
sb.append("static ");
}
if((access & Opcodes.ACC_FINAL) != 0) {
sb.append("final ");
}
if((access & Opcodes.ACC_SYNCHRONIZED) != 0) {
sb.append("synchronized ");
}
if((access & Opcodes.ACC_NATIVE) != 0) {
sb.append("native ");
}
if((access & Opcodes.ACC_ABSTRACT) != 0) {
sb.append("abstract ");
}
Type methodType = Type.getMethodType(methodNode.desc);
//skip constructor return type
if(methodNode.name.equals("<init>")) {
sb.append(owner.getClassName());
}else {
sb.append(methodType.getReturnType().getClassName()).append(' ');
sb.append(methodNode.name);
}
sb.append('(');
Type[] argumentTypes = methodType.getArgumentTypes();
for(int i = 0 ; i < argumentTypes.length; ++i) {
sb.append(argumentTypes[i].getClassName());
if(i != argumentTypes.length - 1) {
sb.append(", ");
}
}
sb.append(')');
if(methodNode.exceptions != null) {
int exceptionSize = methodNode.exceptions.size();
if( exceptionSize > 0) {
sb.append(" throws");
for(int i = 0; i < exceptionSize; ++i) {
sb.append(' ');
sb.append(Type.getObjectType(methodNode.exceptions.get(i)).getClassName());
if(i != exceptionSize -1) {
sb.append(',');
}
}
}
}
return sb.toString();
}
public static FieldNode findField(List<FieldNode> fields, String name) {
for(FieldNode field : fields) {
if(field.name.equals(name)) {
return field;
}
}
return null;
}
public static void addField(ClassNode classNode, FieldNode fieldNode) {
// TODO 检查是否有重复
classNode.fields.add(fieldNode);
}
public static void addMethod(ClassNode classNode, MethodNode methodNode) {
classNode.methods.add(methodNode);
}
// TODO 是否真的 unique
public static String uniqueNameForMethod(String className, String methodName, String desc) {
StringBuilder result = new StringBuilder(128);
result.append(cleanClassName(className)).append('_').append(methodName);
for(Type arg : Type.getMethodType(desc).getArgumentTypes()) {
result.append('_').append(cleanClassName(arg.getClassName()));
}
return result.toString();
}
private static String cleanClassName(String className) {
char[] charArray = className.toCharArray();
int length = charArray.length;
for(int i = 0 ; i < length; ++i) {
switch( charArray[i]) {
case '[' :
case ']' :
case '<' :
case '>' :
case ';' :
case '/' :
case '.' :
charArray[i] = '_';
break;
}
}
return new String(charArray);
}
}

View File

@ -0,0 +1,271 @@
package com.taobao.arthas.bytekit.utils;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.benf.cfr.reader.api.ClassFileSource;
import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair;
import org.benf.cfr.reader.entities.ClassFile;
import org.benf.cfr.reader.entities.Method;
import org.benf.cfr.reader.relationship.MemberNameResolver;
import org.benf.cfr.reader.state.ClassFileSourceImpl;
import org.benf.cfr.reader.state.DCCommonState;
import org.benf.cfr.reader.state.TypeUsageCollector;
import org.benf.cfr.reader.state.TypeUsageInformation;
import org.benf.cfr.reader.util.AnalysisType;
import org.benf.cfr.reader.util.CannotLoadClassException;
import org.benf.cfr.reader.util.ConfusedCFRException;
import org.benf.cfr.reader.util.ListFactory;
import org.benf.cfr.reader.util.getopt.GetOptParser;
import org.benf.cfr.reader.util.getopt.Options;
import org.benf.cfr.reader.util.getopt.OptionsImpl;
import org.benf.cfr.reader.util.output.Dumper;
import org.benf.cfr.reader.util.output.DumperFactory;
import org.benf.cfr.reader.util.output.DumperFactoryImpl;
import org.benf.cfr.reader.util.output.IllegalIdentifierDump;
import org.benf.cfr.reader.util.output.StreamDumper;
import org.benf.cfr.reader.util.output.ToStringDumper;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.arthas.deps.org.objectweb.asm.util.Printer;
import com.alibaba.arthas.deps.org.objectweb.asm.util.Textifier;
import com.alibaba.arthas.deps.org.objectweb.asm.util.TraceClassVisitor;
import com.alibaba.arthas.deps.org.objectweb.asm.util.TraceMethodVisitor;
public class Decompiler {
public static String decompile(byte[] bytecode) throws IOException {
String result = "";
File tempDirectory = FileUtils.getTempDirectory();
File file = new File(tempDirectory, RandomStringUtils.randomAlphabetic(8));
FileUtils.writeByteArrayToFile(file, bytecode);
result = decompile(file.getAbsolutePath(), null);
return result;
}
public static String decompile(String path) throws IOException {
byte[] byteArray = FileUtils.readFileToByteArray(new File(path));
return decompile(byteArray);
}
public static String toString(MethodNode methodNode) {
Printer printer = new Textifier();
TraceMethodVisitor methodPrinter = new TraceMethodVisitor(printer);
methodNode.accept(methodPrinter);
StringWriter sw = new StringWriter();
printer.print(new PrintWriter(sw));
printer.getText().clear();
return sw.toString();
}
public static String toString(ClassNode classNode) {
Printer printer = new Textifier();
StringWriter sw = new StringWriter();
PrintWriter printWriter = new PrintWriter(sw);
TraceClassVisitor traceClassVisitor = new TraceClassVisitor(printWriter);
classNode.accept(traceClassVisitor);
printer.print(printWriter);
printer.getText().clear();
return sw.toString();
}
public static String toString(InsnList insnList) {
Printer printer = new Textifier();
TraceMethodVisitor mp = new TraceMethodVisitor(printer);
insnList.accept(mp);
StringWriter sw = new StringWriter();
printer.print(new PrintWriter(sw));
printer.getText().clear();
return sw.toString();
}
public static String toString(AbstractInsnNode insn) {
Printer printer = new Textifier();
TraceMethodVisitor mp = new TraceMethodVisitor(printer);
insn.accept(mp);
StringWriter sw = new StringWriter();
printer.print(new PrintWriter(sw));
printer.getText().clear();
return sw.toString();
}
/**
* @see org.benf.cfr.reader.Main#main(String[])
* @param classFilePath
* @param methodName
* @return
*/
public static String decompile(String classFilePath, String methodName) {
StringBuilder result = new StringBuilder(8192);
List<String> argList = new ArrayList<String>();
argList.add(classFilePath);
if (methodName != null) {
argList.add("--methodname");
argList.add(methodName);
}
String args[] = argList.toArray(new String[0]);
GetOptParser getOptParser = new GetOptParser();
Options options = null;
List<String> files = null;
try {
Pair processedArgs = getOptParser.parse(args, OptionsImpl.getFactory());
files = (List) processedArgs.getFirst();
options = (Options) processedArgs.getSecond();
} catch (Exception e) {
getOptParser.showHelp(OptionsImpl.getFactory(), e);
System.exit(1);
}
if ((options.optionIsSet(OptionsImpl.HELP)) || (files.isEmpty())) {
getOptParser.showOptionHelp(OptionsImpl.getFactory(), options, OptionsImpl.HELP);
return "";
}
ClassFileSourceImpl classFileSource = new ClassFileSourceImpl(options);
boolean skipInnerClass = (files.size() > 1)
&& (((Boolean) options.getOption(OptionsImpl.SKIP_BATCH_INNER_CLASSES)).booleanValue());
Collections.sort(files);
for (String path : files) {
classFileSource.clearConfiguration();
DCCommonState dcCommonState = new DCCommonState(options, classFileSource);
DumperFactory dumperFactory = new DumperFactoryImpl(options);
path = classFileSource.adjustInputPath(path);
AnalysisType type = (AnalysisType) options.getOption(OptionsImpl.ANALYSE_AS);
if (type == null)
type = dcCommonState.detectClsJar(path);
if (type == AnalysisType.JAR) {
// doJar(dcCommonState, path, dumperFactory);
}
if (type == AnalysisType.CLASS)
result.append(doClass(dcCommonState, path, skipInnerClass, dumperFactory));
}
return result.toString();
}
public static String doClass(DCCommonState dcCommonState, String path, boolean skipInnerClass,
DumperFactory dumperFactory) {
StringBuilder result = new StringBuilder(8192);
Options options = dcCommonState.getOptions();
IllegalIdentifierDump illegalIdentifierDump = IllegalIdentifierDump.Factory.get(options);
Dumper d = new ToStringDumper();
try {
ClassFile c = dcCommonState.getClassFileMaybePath(path);
if ((skipInnerClass) && (c.isInnerClass()))
return "";
dcCommonState.configureWith(c);
dumperFactory.getProgressDumper().analysingType(c.getClassType());
try {
c = dcCommonState.getClassFile(c.getClassType());
} catch (CannotLoadClassException e) {
}
if (((Boolean) options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)).booleanValue()) {
c.loadInnerClasses(dcCommonState);
}
if (((Boolean) options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)).booleanValue()) {
MemberNameResolver.resolveNames(dcCommonState,
ListFactory.newList(dcCommonState.getClassCache().getLoadedTypes()));
}
c.analyseTop(dcCommonState);
TypeUsageCollector collectingDumper = new TypeUsageCollector(c);
c.collectTypeUsages(collectingDumper);
d = new StringDumper(collectingDumper.getTypeUsageInformation(), options, illegalIdentifierDump);
// d = dumperFactory.getNewTopLevelDumper(c.getClassType(), summaryDumper,
// collectingDumper.getTypeUsageInformation(), illegalIdentifierDump);
String methname = (String) options.getOption(OptionsImpl.METHODNAME);
if (methname == null)
c.dump(d);
else {
try {
for (Method method : c.getMethodByName(methname))
method.dump(d, true);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("No such method '" + methname + "'.");
}
}
d.print("");
result.append(d.toString());
} catch (ConfusedCFRException e) {
result.append(e.toString()).append("\n");
for (Object x : e.getStackTrace())
result.append(x).append("\n");
} catch (CannotLoadClassException e) {
result.append("Can't load the class specified:").append("\n");
result.append(e.toString()).append("\n");
} catch (RuntimeException e) {
result.append(e.toString()).append("\n");
for (Object x : e.getStackTrace())
result.append(x).append("\n");
} finally {
if (d != null)
d.close();
}
return result.toString();
}
public static class StringDumper extends StreamDumper {
private StringWriter sw = new StringWriter();
public StringDumper(TypeUsageInformation typeUsageInformation, Options options,
IllegalIdentifierDump illegalIdentifierDump) {
super(typeUsageInformation, options, illegalIdentifierDump);
}
public void addSummaryError(Method paramMethod, String paramString) {
}
public void close() {
try {
sw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void write(String source) {
sw.write(source);
}
public String toString() {
return sw.toString();
}
}
}

View File

@ -0,0 +1,13 @@
package com.taobao.arthas.bytekit.utils;
public class InstanceUtils {
public static <T> T newInstance(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}

View File

@ -0,0 +1,169 @@
package com.taobao.arthas.bytekit.utils;
import java.util.ArrayList;
import java.util.Stack;
/**
* from org.apache.commons.io.FilenameUtils
*
* @author hengyunabc
*
*/
public class MatchUtils {
/**
* The wildcard matcher uses the characters '?' and '*' to represent a
* single or multiple wildcard characters.
*
* @param str
* @param wildcardMatcher
* @return
*/
public static boolean wildcardMatch(String str, String wildcardMatcher) {
return wildcardMatch(str, wildcardMatcher, false);
}
/**
* The wildcard matcher uses the characters '?' and '*' to represent a
* single or multiple wildcard characters.
*
* @param str
* @param wildcardMatcher
* @param sensitive
* if sensitive is true, str and wildcardMatcher will
* toLowerCase.
* @return
*/
public static boolean wildcardMatch(String str, String wildcardMatcher, boolean sensitive) {
if (str == null && wildcardMatcher == null) {
return true;
}
if (str == null || wildcardMatcher == null) {
return false;
}
str = convertCase(str, sensitive);
wildcardMatcher = convertCase(wildcardMatcher, sensitive);
String[] wcs = splitOnTokens(wildcardMatcher);
boolean anyChars = false;
int textIdx = 0;
int wcsIdx = 0;
Stack backtrack = new Stack();
// loop around a backtrack stack, to handle complex * matching
do {
if (backtrack.size() > 0) {
int[] array = (int[]) backtrack.pop();
wcsIdx = array[0];
textIdx = array[1];
anyChars = true;
}
// loop whilst tokens and text left to process
while (wcsIdx < wcs.length) {
if (wcs[wcsIdx].equals("?")) {
// ? so move to next text char
textIdx++;
anyChars = false;
} else if (wcs[wcsIdx].equals("*")) {
// set any chars status
anyChars = true;
if (wcsIdx == wcs.length - 1) {
textIdx = str.length();
}
} else {
// matching text token
if (anyChars) {
// any chars then try to locate text token
textIdx = str.indexOf(wcs[wcsIdx], textIdx);
if (textIdx == -1) {
// token not found
break;
}
int repeat = str.indexOf(wcs[wcsIdx], textIdx + 1);
if (repeat >= 0) {
backtrack.push(new int[] { wcsIdx, repeat });
}
} else {
// matching from current position
if (!str.startsWith(wcs[wcsIdx], textIdx)) {
// couldnt match token
break;
}
}
// matched text token, move text index to end of matched
// token
textIdx += wcs[wcsIdx].length();
anyChars = false;
}
wcsIdx++;
}
// full match
if (wcsIdx == wcs.length && textIdx == str.length()) {
return true;
}
} while (backtrack.size() > 0);
return false;
}
/**
* Splits a string into a number of tokens.
*
* @param text
* the text to split
* @return the tokens, never null
*/
static String[] splitOnTokens(String text) {
// used by wildcardMatch
// package level so a unit test may run on this
if (text.indexOf("?") == -1 && text.indexOf("*") == -1) {
return new String[] { text };
}
char[] array = text.toCharArray();
ArrayList list = new ArrayList();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < array.length; i++) {
if (array[i] == '?' || array[i] == '*') {
if (buffer.length() != 0) {
list.add(buffer.toString());
buffer.setLength(0);
}
if (array[i] == '?') {
list.add("?");
} else if (list.size() == 0 || (i > 0 && list.get(list.size() - 1).equals("*") == false)) {
list.add("*");
}
} else {
buffer.append(array[i]);
}
}
if (buffer.length() != 0) {
list.add(buffer.toString());
}
return (String[]) list.toArray(new String[list.size()]);
}
/**
* Converts the case of the input String to a standard format. Subsequent
* operations can then use standard String methods.
*
* @param str
* the string to convert, null returns null
* @return the lower-case version if case-insensitive
*/
static String convertCase(String str, boolean sensitive) {
if (str == null) {
return null;
}
return sensitive ? str : str.toLowerCase();
}
}

View File

@ -0,0 +1,5 @@
package com.taobao.arthas.bytekit.utils;
public class MethodUtils {
}

View File

@ -0,0 +1,807 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.arthas.bytekit.utils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* Simple utility class for working with the reflection API and handling
* reflection exceptions.
*
* <p>Only intended for internal use.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @author Rod Johnson
* @author Costin Leau
* @author Sam Brannen
* @author Chris Beams
* @since 1.2.2
*/
public abstract class ReflectionUtils {
/**
* Naming prefix for CGLIB-renamed methods.
* @see #isCglibRenamedMethod
*/
private static final String CGLIB_RENAMED_METHOD_PREFIX = "CGLIB$";
/**
* Attempt to find a {@link Field field} on the supplied {@link Class} with the
* supplied {@code name}. Searches all superclasses up to {@link Object}.
* @param clazz the class to introspect
* @param name the name of the field
* @return the corresponding Field object, or {@code null} if not found
*/
public static Field findField(Class<?> clazz, String name) {
return findField(clazz, name, null);
}
/**
* Attempt to find a {@link Field field} on the supplied {@link Class} with the
* supplied {@code name} and/or {@link Class type}. Searches all superclasses
* up to {@link Object}.
* @param clazz the class to introspect
* @param name the name of the field (may be {@code null} if type is specified)
* @param type the type of the field (may be {@code null} if name is specified)
* @return the corresponding Field object, or {@code null} if not found
*/
public static Field findField(Class<?> clazz, String name, Class<?> type) {
Class<?> searchType = clazz;
while (Object.class != searchType && searchType != null) {
Field[] fields = getDeclaredFields(searchType);
for (Field field : fields) {
if ((name == null || name.equals(field.getName())) &&
(type == null || type.equals(field.getType()))) {
return field;
}
}
searchType = searchType.getSuperclass();
}
return null;
}
/**
* Set the field represented by the supplied {@link Field field object} on the
* specified {@link Object target object} to the specified {@code value}.
* In accordance with {@link Field#set(Object, Object)} semantics, the new value
* is automatically unwrapped if the underlying field has a primitive type.
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}.
* @param field the field to set
* @param target the target object on which to set the field
* @param value the value to set (may be {@code null})
*/
public static void setField(Field field, Object target, Object value) {
try {
field.set(target, value);
}
catch (IllegalAccessException ex) {
handleReflectionException(ex);
throw new IllegalStateException(
"Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
}
}
/**
* Get the field represented by the supplied {@link Field field object} on the
* specified {@link Object target object}. In accordance with {@link Field#get(Object)}
* semantics, the returned value is automatically wrapped if the underlying field
* has a primitive type.
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}.
* @param field the field to get
* @param target the target object from which to get the field
* @return the field's current value
*/
public static Object getField(Field field, Object target) {
try {
return field.get(target);
}
catch (IllegalAccessException ex) {
handleReflectionException(ex);
throw new IllegalStateException(
"Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
}
}
/**
* Attempt to find a {@link Method} on the supplied class with the supplied name
* and no parameters. Searches all superclasses up to {@code Object}.
* <p>Returns {@code null} if no {@link Method} can be found.
* @param clazz the class to introspect
* @param name the name of the method
* @return the Method object, or {@code null} if none found
*/
public static Method findMethod(Class<?> clazz, String name) {
return findMethod(clazz, name, new Class<?>[0]);
}
/**
* Attempt to find a {@link Method} on the supplied class with the supplied name
* and parameter types. Searches all superclasses up to {@code Object}.
* <p>Returns {@code null} if no {@link Method} can be found.
* @param clazz the class to introspect
* @param name the name of the method
* @param paramTypes the parameter types of the method
* (may be {@code null} to indicate any signature)
* @return the Method object, or {@code null} if none found
*/
public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
Class<?> searchType = clazz;
while (searchType != null) {
Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType));
for (Method method : methods) {
if (name.equals(method.getName()) &&
(paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
return method;
}
}
searchType = searchType.getSuperclass();
}
return null;
}
/**
* Invoke the specified {@link Method} against the supplied target object with no arguments.
* The target object can be {@code null} when invoking a static {@link Method}.
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @return the invocation result, if any
* @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeMethod(Method method, Object target) {
return invokeMethod(method, target, new Object[0]);
}
/**
* Invoke the specified {@link Method} against the supplied target object with the
* supplied arguments. The target object can be {@code null} when invoking a
* static {@link Method}.
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @param args the invocation arguments (may be {@code null})
* @return the invocation result, if any
*/
public static Object invokeMethod(Method method, Object target, Object... args) {
try {
return method.invoke(target, args);
}
catch (Exception ex) {
handleReflectionException(ex);
}
throw new IllegalStateException("Should never get here");
}
/**
* Invoke the specified JDBC API {@link Method} against the supplied target
* object with no arguments.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @return the invocation result, if any
* @throws SQLException the JDBC API SQLException to rethrow (if any)
* @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeJdbcMethod(Method method, Object target) throws SQLException {
return invokeJdbcMethod(method, target, new Object[0]);
}
/**
* Invoke the specified JDBC API {@link Method} against the supplied target
* object with the supplied arguments.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @param args the invocation arguments (may be {@code null})
* @return the invocation result, if any
* @throws SQLException the JDBC API SQLException to rethrow (if any)
* @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeJdbcMethod(Method method, Object target, Object... args) throws SQLException {
try {
return method.invoke(target, args);
}
catch (IllegalAccessException ex) {
handleReflectionException(ex);
}
catch (InvocationTargetException ex) {
if (ex.getTargetException() instanceof SQLException) {
throw (SQLException) ex.getTargetException();
}
handleInvocationTargetException(ex);
}
throw new IllegalStateException("Should never get here");
}
/**
* Handle the given reflection exception. Should only be called if no
* checked exception is expected to be thrown by the target method.
* <p>Throws the underlying RuntimeException or Error in case of an
* InvocationTargetException with such a root cause. Throws an
* IllegalStateException with an appropriate message or
* UndeclaredThrowableException otherwise.
* @param ex the reflection exception to handle
*/
public static void handleReflectionException(Exception ex) {
if (ex instanceof NoSuchMethodException) {
throw new IllegalStateException("Method not found: " + ex.getMessage());
}
if (ex instanceof IllegalAccessException) {
throw new IllegalStateException("Could not access method: " + ex.getMessage());
}
if (ex instanceof InvocationTargetException) {
handleInvocationTargetException((InvocationTargetException) ex);
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new UndeclaredThrowableException(ex);
}
/**
* Handle the given invocation target exception. Should only be called if no
* checked exception is expected to be thrown by the target method.
* <p>Throws the underlying RuntimeException or Error in case of such a root
* cause. Throws an UndeclaredThrowableException otherwise.
* @param ex the invocation target exception to handle
*/
public static void handleInvocationTargetException(InvocationTargetException ex) {
rethrowRuntimeException(ex.getTargetException());
}
/**
* Rethrow the given {@link Throwable exception}, which is presumably the
* <em>target exception</em> of an {@link InvocationTargetException}.
* Should only be called if no checked exception is expected to be thrown
* by the target method.
* <p>Rethrows the underlying exception cast to a {@link RuntimeException} or
* {@link Error} if appropriate; otherwise, throws an
* {@link UndeclaredThrowableException}.
* @param ex the exception to rethrow
* @throws RuntimeException the rethrown exception
*/
public static void rethrowRuntimeException(Throwable ex) {
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
if (ex instanceof Error) {
throw (Error) ex;
}
throw new UndeclaredThrowableException(ex);
}
/**
* Rethrow the given {@link Throwable exception}, which is presumably the
* <em>target exception</em> of an {@link InvocationTargetException}.
* Should only be called if no checked exception is expected to be thrown
* by the target method.
* <p>Rethrows the underlying exception cast to an {@link Exception} or
* {@link Error} if appropriate; otherwise, throws an
* {@link UndeclaredThrowableException}.
* @param ex the exception to rethrow
* @throws Exception the rethrown exception (in case of a checked exception)
*/
public static void rethrowException(Throwable ex) throws Exception {
if (ex instanceof Exception) {
throw (Exception) ex;
}
if (ex instanceof Error) {
throw (Error) ex;
}
throw new UndeclaredThrowableException(ex);
}
/**
* Determine whether the given method explicitly declares the given
* exception or one of its superclasses, which means that an exception
* of that type can be propagated as-is within a reflective invocation.
* @param method the declaring method
* @param exceptionType the exception to throw
* @return {@code true} if the exception can be thrown as-is;
* {@code false} if it needs to be wrapped
*/
public static boolean declaresException(Method method, Class<?> exceptionType) {
Class<?>[] declaredExceptions = method.getExceptionTypes();
for (Class<?> declaredException : declaredExceptions) {
if (declaredException.isAssignableFrom(exceptionType)) {
return true;
}
}
return false;
}
/**
* Determine whether the given field is a "public static final" constant.
* @param field the field to check
*/
public static boolean isPublicStaticFinal(Field field) {
int modifiers = field.getModifiers();
return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers));
}
/**
* Determine whether the given method is an "equals" method.
* @see java.lang.Object#equals(Object)
*/
public static boolean isEqualsMethod(Method method) {
if (method == null || !method.getName().equals("equals")) {
return false;
}
Class<?>[] paramTypes = method.getParameterTypes();
return (paramTypes.length == 1 && paramTypes[0] == Object.class);
}
/**
* Determine whether the given method is a "hashCode" method.
* @see java.lang.Object#hashCode()
*/
public static boolean isHashCodeMethod(Method method) {
return (method != null && method.getName().equals("hashCode") && method.getParameterTypes().length == 0);
}
/**
* Determine whether the given method is a "toString" method.
* @see java.lang.Object#toString()
*/
public static boolean isToStringMethod(Method method) {
return (method != null && method.getName().equals("toString") && method.getParameterTypes().length == 0);
}
/**
* Determine whether the given method is originally declared by {@link java.lang.Object}.
*/
public static boolean isObjectMethod(Method method) {
if (method == null) {
return false;
}
try {
Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes());
return true;
}
catch (Exception ex) {
return false;
}
}
/**
* Determine whether the given method is a CGLIB 'renamed' method,
* following the pattern "CGLIB$methodName$0".
* @param renamedMethod the method to check
* @see org.springframework.cglib.proxy.Enhancer#rename
*/
public static boolean isCglibRenamedMethod(Method renamedMethod) {
String name = renamedMethod.getName();
if (name.startsWith(CGLIB_RENAMED_METHOD_PREFIX)) {
int i = name.length() - 1;
while (i >= 0 && Character.isDigit(name.charAt(i))) {
i--;
}
return ((i > CGLIB_RENAMED_METHOD_PREFIX.length()) &&
(i < name.length() - 1) && name.charAt(i) == '$');
}
return false;
}
/**
* Make the given field accessible, explicitly setting it accessible if
* necessary. The {@code setAccessible(true)} method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
* @param field the field to make accessible
* @see java.lang.reflect.Field#setAccessible
*/
public static void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) ||
!Modifier.isPublic(field.getDeclaringClass().getModifiers()) ||
Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
field.setAccessible(true);
}
}
/**
* Make the given method accessible, explicitly setting it accessible if
* necessary. The {@code setAccessible(true)} method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
* @param method the method to make accessible
* @see java.lang.reflect.Method#setAccessible
*/
public static void makeAccessible(Method method) {
if ((!Modifier.isPublic(method.getModifiers()) ||
!Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) {
method.setAccessible(true);
}
}
/**
* Make the given constructor accessible, explicitly setting it accessible
* if necessary. The {@code setAccessible(true)} method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
* @param ctor the constructor to make accessible
* @see java.lang.reflect.Constructor#setAccessible
*/
public static void makeAccessible(Constructor<?> ctor) {
if ((!Modifier.isPublic(ctor.getModifiers()) ||
!Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) {
ctor.setAccessible(true);
}
}
/**
* Perform the given callback operation on all matching methods of the given
* class, as locally declared or equivalent thereof (such as default methods
* on Java 8 based interfaces that the given class implements).
* @param clazz the class to introspect
* @param mc the callback to invoke for each method
* @since 4.2
* @see #doWithMethods
*/
public static void doWithLocalMethods(Class<?> clazz, MethodCallback mc) {
Method[] methods = getDeclaredMethods(clazz);
for (Method method : methods) {
try {
mc.doWith(method);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
}
}
}
/**
* Perform the given callback operation on all matching methods of the given
* class and superclasses.
* <p>The same named method occurring on subclass and superclass will appear
* twice, unless excluded by a {@link MethodFilter}.
* @param clazz the class to introspect
* @param mc the callback to invoke for each method
* @see #doWithMethods(Class, MethodCallback, MethodFilter)
*/
public static void doWithMethods(Class<?> clazz, MethodCallback mc) {
doWithMethods(clazz, mc, null);
}
/**
* Perform the given callback operation on all matching methods of the given
* class and superclasses (or given interface and super-interfaces).
* <p>The same named method occurring on subclass and superclass will appear
* twice, unless excluded by the specified {@link MethodFilter}.
* @param clazz the class to introspect
* @param mc the callback to invoke for each method
* @param mf the filter that determines the methods to apply the callback to
*/
public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
// Keep backing up the inheritance hierarchy.
Method[] methods = getDeclaredMethods(clazz);
for (Method method : methods) {
if (mf != null && !mf.matches(method)) {
continue;
}
try {
mc.doWith(method);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
}
}
if (clazz.getSuperclass() != null) {
doWithMethods(clazz.getSuperclass(), mc, mf);
}
else if (clazz.isInterface()) {
for (Class<?> superIfc : clazz.getInterfaces()) {
doWithMethods(superIfc, mc, mf);
}
}
}
/**
* Get all declared methods on the leaf class and all superclasses.
* Leaf class methods are included first.
* @param leafClass the class to introspect
*/
public static Method[] getAllDeclaredMethods(Class<?> leafClass) {
final List<Method> methods = new ArrayList<Method>(32);
doWithMethods(leafClass, new MethodCallback() {
@Override
public void doWith(Method method) {
methods.add(method);
}
});
return methods.toArray(new Method[methods.size()]);
}
/**
* Get the unique set of declared methods on the leaf class and all superclasses.
* Leaf class methods are included first and while traversing the superclass hierarchy
* any methods found with signatures matching a method already included are filtered out.
* @param leafClass the class to introspect
*/
public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) {
final List<Method> methods = new ArrayList<Method>(32);
doWithMethods(leafClass, new MethodCallback() {
@Override
public void doWith(Method method) {
boolean knownSignature = false;
Method methodBeingOverriddenWithCovariantReturnType = null;
for (Method existingMethod : methods) {
if (method.getName().equals(existingMethod.getName()) &&
Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) {
// Is this a covariant return type situation?
if (existingMethod.getReturnType() != method.getReturnType() &&
existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
methodBeingOverriddenWithCovariantReturnType = existingMethod;
}
else {
knownSignature = true;
}
break;
}
}
if (methodBeingOverriddenWithCovariantReturnType != null) {
methods.remove(methodBeingOverriddenWithCovariantReturnType);
}
if (!knownSignature && !isCglibRenamedMethod(method)) {
methods.add(method);
}
}
});
return methods.toArray(new Method[methods.size()]);
}
/**
* This variant retrieves {@link Class#getDeclaredMethods()} from a local cache
* in order to avoid the JVM's SecurityManager check and defensive array copying.
* In addition, it also includes Java 8 default methods from locally implemented
* interfaces, since those are effectively to be treated just like declared methods.
* @param clazz the class to introspect
* @return the cached array of methods
* @see Class#getDeclaredMethods()
*/
private static Method[] getDeclaredMethods(Class<?> clazz) {
Method[] result = null;
if (result == null) {
Method[] declaredMethods = clazz.getDeclaredMethods();
List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz);
if (defaultMethods != null) {
result = new Method[declaredMethods.length + defaultMethods.size()];
System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
int index = declaredMethods.length;
for (Method defaultMethod : defaultMethods) {
result[index] = defaultMethod;
index++;
}
}
else {
result = declaredMethods;
}
}
return result;
}
private static List<Method> findConcreteMethodsOnInterfaces(Class<?> clazz) {
List<Method> result = null;
for (Class<?> ifc : clazz.getInterfaces()) {
for (Method ifcMethod : ifc.getMethods()) {
if (!Modifier.isAbstract(ifcMethod.getModifiers())) {
if (result == null) {
result = new LinkedList<Method>();
}
result.add(ifcMethod);
}
}
}
return result;
}
/**
* Invoke the given callback on all fields in the target class, going up the
* class hierarchy to get all declared fields.
* @param clazz the target class to analyze
* @param fc the callback to invoke for each field
* @since 4.2
* @see #doWithFields
*/
public static void doWithLocalFields(Class<?> clazz, FieldCallback fc) {
for (Field field : getDeclaredFields(clazz)) {
try {
fc.doWith(field);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex);
}
}
}
/**
* Invoke the given callback on all fields in the target class, going up the
* class hierarchy to get all declared fields.
* @param clazz the target class to analyze
* @param fc the callback to invoke for each field
*/
public static void doWithFields(Class<?> clazz, FieldCallback fc) {
doWithFields(clazz, fc, null);
}
/**
* Invoke the given callback on all fields in the target class, going up the
* class hierarchy to get all declared fields.
* @param clazz the target class to analyze
* @param fc the callback to invoke for each field
* @param ff the filter that determines the fields to apply the callback to
*/
public static void doWithFields(Class<?> clazz, FieldCallback fc, FieldFilter ff) {
// Keep backing up the inheritance hierarchy.
Class<?> targetClass = clazz;
do {
Field[] fields = getDeclaredFields(targetClass);
for (Field field : fields) {
if (ff != null && !ff.matches(field)) {
continue;
}
try {
fc.doWith(field);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex);
}
}
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
}
/**
* This variant retrieves {@link Class#getDeclaredFields()} from a local cache
* in order to avoid the JVM's SecurityManager check and defensive array copying.
* @param clazz the class to introspect
* @return the cached array of fields
* @see Class#getDeclaredFields()
*/
private static Field[] getDeclaredFields(Class<?> clazz) {
Field[] result = null;
if (result == null) {
result = clazz.getDeclaredFields();
}
return result;
}
/**
* Given the source object and the destination, which must be the same class
* or a subclass, copy all fields, including inherited fields. Designed to
* work on objects with public no-arg constructors.
*/
public static void shallowCopyFieldState(final Object src, final Object dest) {
if (src == null) {
throw new IllegalArgumentException("Source for field copy cannot be null");
}
if (dest == null) {
throw new IllegalArgumentException("Destination for field copy cannot be null");
}
if (!src.getClass().isAssignableFrom(dest.getClass())) {
throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() +
"] must be same or subclass as source class [" + src.getClass().getName() + "]");
}
doWithFields(src.getClass(), new FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
makeAccessible(field);
Object srcValue = field.get(src);
field.set(dest, srcValue);
}
}, COPYABLE_FIELDS);
}
/**
* Action to take on each method.
*/
public interface MethodCallback {
/**
* Perform an operation using the given method.
* @param method the method to operate on
*/
void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
}
/**
* Callback optionally used to filter methods to be operated on by a method callback.
*/
public interface MethodFilter {
/**
* Determine whether the given method matches.
* @param method the method to check
*/
boolean matches(Method method);
}
/**
* Callback interface invoked on each field in the hierarchy.
*/
public interface FieldCallback {
/**
* Perform an operation using the given field.
* @param field the field to operate on
*/
void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
}
/**
* Callback optionally used to filter fields to be operated on by a field callback.
*/
public interface FieldFilter {
/**
* Determine whether the given field matches.
* @param field the field to check
*/
boolean matches(Field field);
}
/**
* Pre-built FieldFilter that matches all non-static, non-final fields.
*/
public static final FieldFilter COPYABLE_FIELDS = new FieldFilter() {
@Override
public boolean matches(Field field) {
return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()));
}
};
/**
* Pre-built MethodFilter that matches all non-bridge methods.
*/
public static final MethodFilter NON_BRIDGED_METHODS = new MethodFilter() {
@Override
public boolean matches(Method method) {
return !method.isBridge();
}
};
/**
* Pre-built MethodFilter that matches all non-bridge methods
* which are not declared on {@code java.lang.Object}.
*/
public static final MethodFilter USER_DECLARED_METHODS = new MethodFilter() {
@Override
public boolean matches(Method method) {
return (!method.isBridge() && method.getDeclaringClass() != Object.class);
}
};
}

View File

@ -0,0 +1,69 @@
package com.taobao.arthas.bytekit.utils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import com.alibaba.arthas.deps.org.objectweb.asm.ClassReader;
import com.alibaba.arthas.deps.org.objectweb.asm.ClassVisitor;
import com.alibaba.arthas.deps.org.objectweb.asm.ClassWriter;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.util.CheckClassAdapter;
/**
*
* @author hengyunabc
*
*/
public class VerifyUtils {
public static void asmVerify(byte[] bytes) throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
ClassReader cr = new ClassReader(inputStream);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new CheckClassAdapter(cw);
cr.accept(cv, 0);
}
public static Object instanceVerity(byte[] bytes) throws Exception {
String name = Type.getObjectType(AsmUtils.toClassNode(bytes).name).getClassName();
URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
@SuppressWarnings("resource")
ClassbyteClassLoader cl = new ClassbyteClassLoader(systemClassLoader.getURLs(),
ClassLoader.getSystemClassLoader().getParent());
cl.addClass(name, bytes);
Class<?> loadClass = cl.loadClass(name);
return loadClass.newInstance();
}
public static Object invoke(Object instance, String name, Object... args) throws Exception {
Method[] methods = instance.getClass().getMethods();
for (Method method : methods) {
if (name.contentEquals(method.getName())) {
return method.invoke(instance, args);
}
}
throw new NoSuchMethodError("name: " + name);
}
public static class ClassbyteClassLoader extends URLClassLoader {
public ClassbyteClassLoader(URL[] urls, ClassLoader cl) {
super(urls, cl);
}
public Class<?> addClass(String name, byte[] bytes) throws ClassFormatError {
Class<?> cl = defineClass(name, bytes, 0, bytes.length);
resolveClass(cl);
return cl;
}
}
}

View File

@ -0,0 +1,18 @@
package com.taobao.arthas.bytekit.asm.inst;
public class InstDemo {
public int returnInt(int i) {
System.out.println(new Object[] { i });
return 9998;
}
public static void onEnter(Object[] args) {
System.out.println(args);
}
public static int returnIntStatic(int i) {
return 9998;
}
}

View File

@ -0,0 +1,102 @@
package com.taobao.arthas.bytekit.asm.inst;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.AnnotationNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.taobao.arthas.bytekit.asm.MethodProcessor;
import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
import com.taobao.arthas.bytekit.utils.Decompiler;
import com.taobao.arthas.bytekit.utils.VerifyUtils;
public class InstDemoTest {
@Test
public void test() throws Exception {
ClassNode apmClassNode = AsmUtils.loadClass(InstDemo_APM.class);
ClassNode originClassNode = AsmUtils.loadClass(InstDemo.class);
ClassNode targetClassNode = AsmUtils.copy(originClassNode);
byte[] renameClass = AsmUtils.renameClass(AsmUtils.toBytes(apmClassNode), Type.getObjectType(originClassNode.name).getClassName());
apmClassNode = AsmUtils.toClassNode(renameClass);
for(FieldNode fieldNode : apmClassNode.fields) {
if( fieldNode.visibleAnnotations != null) {
for( AnnotationNode annotationNode : fieldNode.visibleAnnotations) {
System.err.println(annotationNode.desc);
System.err.println(annotationNode.values);
if(Type.getType(NewField.class).equals(Type.getType(annotationNode.desc))) {
AsmUtils.addField(targetClassNode, fieldNode);
}
}
}
}
for (MethodNode methodNode : apmClassNode.methods) {
methodNode = AsmUtils.removeLineNumbers(methodNode);
if (methodNode.name.startsWith("__origin_")) {
continue;
} else {
MethodNode findMethod = AsmUtils.findMethod(originClassNode.methods, methodNode);
if (findMethod != null) {
// 先要替换 invokeOrigin 要判断
// apm 里查找 __origin_ 开头的函数忽略
// 查找 __origin_ 开头的函数在原来的类里查找如果有同样签名的函数
// 则从函数里查找 是否有 __origin_ 的函数调用如果有的话则从原有的类里查找到 method再inline掉
List<MethodInsnNode> originMethodInsnNodes = AsmUtils.findMethodInsnNodeWithPrefix(methodNode,
"__origin_");
for (MethodInsnNode methodInsnNode : originMethodInsnNodes) {
String toInlineMethodName = methodInsnNode.name.substring("__origin_".length());
MethodNode originMethodNode = AsmUtils.findMethod(originClassNode.methods, toInlineMethodName,
findMethod.desc);
MethodNode tmpMethodNode = AsmUtils.copy(originMethodNode);
tmpMethodNode.name = methodInsnNode.name;
MethodProcessor methodProcessor = new MethodProcessor(apmClassNode.name, methodNode);
methodProcessor.inline(originClassNode.name, tmpMethodNode);
AsmUtils.replaceMethod(targetClassNode, methodProcessor.getMethodNode());
}
} else {
// 没找到的函数则加进去
AsmUtils.addMethod(targetClassNode, methodNode);
}
}
}
byte[] resutlBytes = AsmUtils.toBytes(targetClassNode);
System.err.println(Decompiler.decompile(resutlBytes));
System.err.println(AsmUtils.toASMCode(resutlBytes));
FileUtils.writeByteArrayToFile(new File("/tmp/ttt/InstDemo.class"), resutlBytes);
VerifyUtils.asmVerify(resutlBytes);
VerifyUtils.instanceVerity(resutlBytes);
}
}

View File

@ -0,0 +1,29 @@
package com.taobao.arthas.bytekit.asm.inst;
@Instrument
public class InstDemo_APM {
@NewField
private String newField;
public int newMethod(String s) {
return s.length() + 998;
}
// 这种方式来写怎么样有点丑但是不需要写那些转换的代码 在插件的编绎出结果后可以检查下 名字static参数等是否匹配的
// 这种处理有点丑但inline应该没问题
public int __origin_returnInt(int i) {
return 0;
}
public int returnInt(int i) {
int re = __origin_returnInt(i);
return 9998 + re;
}
public static int returnIntStatic(int i) {
return 9998;
}
}

View File

@ -0,0 +1,77 @@
package com.taobao.arthas.bytekit.asm.inst;
import java.util.Date;
/**
* @author hengyunabc 2019-03-13
*
*/
public class InvokeOriginDemo {
public void returnVoid() {
}
public Void returnVoidObject() {
int i = 0;
try {
int parseInt = Integer.parseInt("1000");
i += parseInt;
} catch (Exception e) {
System.err.println(i + " " + e);
}
return null;
}
public int returnInt(int i) {
return 9998;
}
public int returnIntToObject(int i) {
return 9998;
}
public int returnIntToInteger(int i) {
return 9998;
}
public static int returnIntStatic(int i) {
return 9998;
}
public long returnLong() {
return 9998L;
}
public long returnLongToObject() {
return 9998L;
}
public String[] returnStrArray() {
String[] result = new String[] {"abc", "xyz" , "ufo"};
return result;
}
public String[] returnStrArrayWithArgs(int i, String s, long l) {
String[] result = new String[] {"abc" + i, "xyz" + s , "ufo" + l};
return result;
}
public String returnStr() {
return new Date().toString();
}
public Object returnObject() {
return InvokeOriginDemo.class;
}
public int recursive(int i) {
if (i == 1) {
return 1;
}
return i + recursive(i - 1);
}
}

View File

@ -0,0 +1,85 @@
package com.taobao.arthas.bytekit.asm.inst;
/**
*
* @author hengyunabc 2019-03-18
*
*/
public class InvokeOriginDemo_APM {
public void returnVoid() {
Object o = InstrumentApi.invokeOrigin();
System.out.println(o);
}
public Void returnVoidObject() {
Void v = InstrumentApi.invokeOrigin();
System.out.println(v);
return v;
}
public int returnInt(int i) {
System.out.println("before");
int value = InstrumentApi.invokeOrigin();
System.out.println("after");
return value + 123;
}
public int returnIntToObject(int i) {
Object value = InstrumentApi.invokeOrigin();
return 9998 + (Integer) value;
}
public int returnIntToInteger(int i) {
Integer ixx = InstrumentApi.invokeOrigin();
return ixx + 9998;
}
public static int returnIntStatic(int i) {
int result = InstrumentApi.invokeOrigin();
return 9998 + result;
}
public long returnLong() {
long result = InstrumentApi.invokeOrigin();
return 9998L + result;
}
public long returnLongToObject() {
Long lll = InstrumentApi.invokeOrigin();
return 9998L + lll;
}
public String[] returnStrArray() {
String[] result = InstrumentApi.invokeOrigin();
System.err.println(result);
return result;
}
public String[] returnStrArrayWithArgs(int i, String s, long l) {
System.out.println(i);
String[] result = InstrumentApi.invokeOrigin();
result[0] = "fff";
return result;
}
public String returnStr() {
System.err.println("ssss");
Object result = InstrumentApi.invokeOrigin();
return "hello" + result;
}
public Object returnObject() {
InstrumentApi.invokeOrigin();
return InvokeOriginDemo.class;
}
public int recursive(int i) {
int result = InstrumentApi.invokeOrigin();
System.err.println(result);
return result;
}
}

View File

@ -0,0 +1,178 @@
package com.taobao.arthas.bytekit.asm.inst;
import java.io.IOException;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
import com.taobao.arthas.bytekit.asm.inst.impl.InstrumentImpl;
import com.taobao.arthas.bytekit.utils.AsmUtils;
import com.taobao.arthas.bytekit.utils.Decompiler;
import com.taobao.arthas.bytekit.utils.VerifyUtils;
/**
*
* @author hengyunabc 2019-03-18
*
*/
public class InvokeOriginTest {
ClassNode apmClassNode;
ClassNode originClassNode;
ClassNode targetClassNode;
@Rule
public TestName testName = new TestName();
@BeforeClass
public static void beforeClass() throws IOException {
}
@Before
public void before() throws IOException {
apmClassNode = AsmUtils.loadClass(InvokeOriginDemo_APM.class);
originClassNode = AsmUtils.loadClass(InvokeOriginDemo.class);
byte[] renameClass = AsmUtils.renameClass(AsmUtils.toBytes(apmClassNode),
Type.getObjectType(originClassNode.name).getClassName());
apmClassNode = AsmUtils.toClassNode(renameClass);
targetClassNode = AsmUtils.copy(originClassNode);
}
private Object replace(String methodName) throws Exception {
System.err.println(methodName);
for (MethodNode methodNode : apmClassNode.methods) {
if (methodNode.name.equals(methodName)) {
methodNode = AsmUtils.removeLineNumbers(methodNode);
// 从原来的类里查找对应的函数
MethodNode findMethod = AsmUtils.findMethod(originClassNode.methods, methodNode);
if (findMethod != null) {
MethodNode methodNode2 = InstrumentImpl.replaceInvokeOrigin(originClassNode.name, findMethod,
methodNode);
System.err.println(Decompiler.toString(methodNode2));
AsmUtils.replaceMethod(targetClassNode, methodNode2);
} else {
}
}
}
byte[] resutlBytes = AsmUtils.toBytes(targetClassNode);
System.err.println("=================");
System.err.println(Decompiler.decompile(resutlBytes));
// System.err.println(AsmUtils.toASMCode(resutlBytes));
VerifyUtils.asmVerify(resutlBytes);
return VerifyUtils.instanceVerity(resutlBytes);
}
@Test
public void test_returnVoid() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(null);
}
@Test
public void test_returnVoidObject() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(null);
}
@Test
public void test_returnInt() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 123);
}
@Test
public void test_returnIntToObject() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998);
}
@Test
public void test_returnIntToInteger() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998);
}
@Test
public void test_returnIntStatic() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998);
}
@Test
public void test_returnLong() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(9998L + 9998);
}
@Test
public void test_returnLongToObject() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(9998L + 9998);
}
@Test
public void test_returnStrArray() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(new String[] { "abc", "xyz", "ufo" });
}
@Test
public void test_returnStrArrayWithArgs() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123, "sss", 777L))
.isEqualTo(new Object[] { "fff", "xyz" + "sss", "ufo" + 777 });
}
@Test
public void test_returnStr() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName)).asString().startsWith("hello");
}
@Test
public void test_returnObject() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(object.getClass());
}
@Test
public void test_recursive() throws Exception {
String methodName = testName.getMethodName().substring("test_".length());
Object object = replace(methodName);
Assertions.assertThat(VerifyUtils.invoke(object, methodName, 100)).isEqualTo((100 + 1) * 100 / 2);
}
}

View File

@ -0,0 +1,89 @@
package com.taobao.arthas.bytekit.asm.interceptor;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.test.rule.OutputCapture;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
import com.taobao.arthas.bytekit.utils.Decompiler;
public class AtEnterTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Rule
public OutputCapture capture = new OutputCapture();
public static class Sample {
long longField;
String strField;
static int intField;
public int hello(String str, boolean exception) {
if (exception) {
throw new RuntimeException("test exception");
}
return str.length();
}
public long toBeInvoke(int i , long l, String s, long ll) {
return l + ll;
}
public void testInvokeArgs() {
toBeInvoke(1, 123L, "abc", 100L);
}
}
public static class TestPrintSuppressHandler {
@ExceptionHandler(inline = true)
public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
System.err.println("exception handler: " + clazz);
e.printStackTrace();
}
}
public static class EnterInterceptor {
@AtEnter(inline = true
, suppress = RuntimeException.class, suppressHandler = TestPrintSuppressHandler.class
)
public static long onEnter(
@Binding.This Object object, @Binding.Class Object clazz,
@Binding.Field(name = "longField") long longField,
@Binding.Field(name = "longField") Object longFieldObject,
@Binding.Field(name = "intField") int intField,
@Binding.Field(name = "strField") String strField,
@Binding.Field(name = "intField") Object intFielObject
) {
System.err.println("onEnter, object:" + object);
return 123L;
}
}
@Test
public void testEnter() throws Exception {
TestHelper helper = TestHelper.builder().interceptorClass(EnterInterceptor.class).methodMatcher("hello")
.redefine(true);
byte[] bytes = helper.process(Sample.class);
new Sample().hello("abc", false);
System.err.println(Decompiler.decompile(bytes));
assertThat(capture.toString()).contains("onEnter, object:");
}
}

View File

@ -0,0 +1,84 @@
package com.taobao.arthas.bytekit.asm.interceptor;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.test.rule.OutputCapture;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
import com.taobao.arthas.bytekit.utils.Decompiler;
public class AtExceptionExitTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Rule
public OutputCapture capture = new OutputCapture();
public static class Sample {
long longField;
String strField;
static int intField;
public int hello(String str, boolean exception) {
if (exception) {
throw new RuntimeException("test exception");
}
return str.length();
}
public long toBeInvoke(int i , long l, String s, long ll) {
return l + ll;
}
public void testInvokeArgs() {
toBeInvoke(1, 123L, "abc", 100L);
}
}
public static class TestPrintSuppressHandler {
@ExceptionHandler(inline = true)
public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
System.err.println("exception handler: " + clazz);
System.err.println(e.getMessage());
assertThat(e).hasMessage("exception for ExceptionHandler");
}
}
public static class ExceptionExitInterceptor {
@AtExceptionExit(inline = false, onException = RuntimeException.class ,suppress = Throwable.class, suppressHandler = TestPrintSuppressHandler.class)
public static void onExceptionExit(@Binding.Throwable RuntimeException ex, @Binding.This Object object,
@Binding.Class Object clazz) {
System.err.println("AtExceptionExit, ex:" + ex);
throw new RuntimeException("exception for ExceptionHandler");
}
}
@Test
public void testExecptionExitException() throws Exception {
TestHelper helper = TestHelper.builder().interceptorClass(ExceptionExitInterceptor.class).methodMatcher("hello")
.redefine(true);
byte[] bytes = helper.process(Sample.class);
System.err.println(Decompiler.decompile(bytes));
try {
new Sample().hello("abc", true);
} catch (Exception e) {
assertThat(e).isInstanceOf(RuntimeException.class).hasMessageContaining("test exception");
}
assertThat(capture.toString()).contains("AtExceptionExit, ex:");
}
}

View File

@ -0,0 +1,93 @@
package com.taobao.arthas.bytekit.asm.interceptor;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.rule.OutputCapture;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
import com.taobao.arthas.bytekit.utils.Decompiler;
public class AtExitTest {
@Rule
public OutputCapture capture = new OutputCapture();
static class Sample {
long longField;
int intField;
String strField;
public void voidExit() {
}
public long longExit() {
return 100L;
}
public static long staticExit() {
return 999L;
}
}
public static class TestPrintSuppressHandler {
@ExceptionHandler(inline = false)
public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
System.err.println("exception handler: " + clazz);
e.printStackTrace();
}
}
public static class TestAccessInterceptor {
@AtExit(inline = false)
public static void atExit(@Binding.This Object object,
@Binding.Class Object clazz
,
@Binding.Return Object re
) {
System.err.println("AtFieldAccess: this" + object);
}
}
public static class ChangeReturnInterceptor {
@AtExit(inline = false, suppress = RuntimeException.class, suppressHandler = TestPrintSuppressHandler.class)
public static Object onExit(@Binding.This Object object, @Binding.Class Object clazz) {
System.err.println("onExit, object:" + object);
return 123L;
}
}
@Test
public void testExit() throws Exception {
TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("voidExit")
.redefine(true);
byte[] bytes = helper.process(Sample.class);
new Sample().voidExit();
System.err.println(Decompiler.decompile(bytes));
assertThat(capture.toString()).contains("AtFieldAccess: this");
}
@Test
public void testExitAndChangeReturn() throws Exception {
TestHelper helper = TestHelper.builder().interceptorClass(ChangeReturnInterceptor.class).methodMatcher("longExit")
.redefine(true);
byte[] bytes = helper.process(Sample.class);
System.err.println(Decompiler.decompile(bytes));
long re = new Sample().longExit();
assertThat(re).isEqualTo(123);
assertThat(capture.toString()).contains("onExit, object:");
}
}

View File

@ -0,0 +1,60 @@
package com.taobao.arthas.bytekit.asm.interceptor;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.rule.OutputCapture;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtFieldAccess;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
import com.taobao.arthas.bytekit.utils.Decompiler;
public class AtFieldAccessTest {
@Rule
public OutputCapture capture = new OutputCapture();
class Sample {
long longField;
int intField;
String strField;
public int testReadField(int ii) {
longField = 999;
return 123;
}
}
public static class TestPrintSuppressHandler {
@ExceptionHandler(inline = false)
public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
System.err.println("exception handler: " + clazz);
e.printStackTrace();
}
}
public static class FieldAccessInterceptor {
@AtFieldAccess(name = "longField" , inline =false)
public static void onFieldAccess(@Binding.This Object object,
@Binding.Class Object clazz) {
System.err.println("AtFieldAccess: this" + object);
}
}
@Test
public void testEnter() throws Exception {
TestHelper helper = TestHelper.builder().interceptorClass(FieldAccessInterceptor.class).methodMatcher("testReadField")
.redefine(true);
byte[] bytes = helper.process(Sample.class);
new Sample().testReadField(100);
System.err.println(Decompiler.decompile(bytes));
assertThat(capture.toString()).contains("AtFieldAccess: this");
}
}

View File

@ -0,0 +1,101 @@
package com.taobao.arthas.bytekit.asm.interceptor;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.rule.OutputCapture;
import com.taobao.arthas.bytekit.asm.binding.Binding;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke;
import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
import com.taobao.arthas.bytekit.utils.Decompiler;
public class AtInvokeTest {
@Rule
public OutputCapture capture = new OutputCapture();
static class Sample {
long longField;
int intField;
String strField;
public Sample(int i, long l, String s) {
staticToBeCall(i, l, s);
aaa("aaa");
}
public int testCall(int ii) {
toBeCall(ii, 123L, "");
System.err.println("abc");
aaa("abc");
return 123;
}
public void aaa(String aaa) {
return ;
}
public long toBeCall(int i , long l, String s) {
return l + i;
}
public static long staticToBeCall(int i , long l, String s) {
return l + i;
}
}
public static class TestPrintSuppressHandler {
@ExceptionHandler(inline = false)
public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
System.err.println("exception handler: " + clazz);
e.printStackTrace();
}
}
public static class TestAccessInterceptor {
@AtInvoke(name = "", inline = false, whenComplete=false, excludes = {"System."})
public static void onInvoke(
@Binding.This Object object,
@Binding.Class Object clazz
,
@Binding.InvokeArgs Object[] args
) {
System.err.println("onInvoke: this" + object);
}
@AtInvoke(name = "toBeCall", inline = false, whenComplete = true)
public static void onInvokeAfter(
@Binding.This Object object,
@Binding.Class Object clazz
,
@Binding.InvokeReturn Object invokeReturn
,
@Binding.InvokeMethodDeclaration String declaration
) {
System.err.println("onInvokeAfter: this" + object);
System.err.println("declaration: " + declaration);
assertThat(declaration).isEqualTo("long toBeCall(int, long, java.lang.String)");
System.err.println("invokeReturn: " + invokeReturn);
assertThat(invokeReturn).isEqualTo(100 + 123L);
}
}
@Test
public void testInvokeBefore() throws Exception {
TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("testCall")
.redefine(true);
byte[] bytes = helper.process(Sample.class);
new Sample(100, 100L, "").testCall(100);
System.err.println(Decompiler.decompile(bytes));
assertThat(capture.toString()).contains("onInvoke: this");
}
}

Some files were not shown because too many files have changed in this diff Show More