mirror of
https://gitee.com/arthas/arthas.git
synced 2024-12-02 12:17:45 +08:00
add bytekit
This commit is contained in:
parent
27d4dec87f
commit
2a0dfdeb1b
29
apm-demo/pom.xml
Normal file
29
apm-demo/pom.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?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.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>arthas-apm-demo</artifactId>
|
||||
<name>arthas-apm-demo</name>
|
||||
|
||||
<build>
|
||||
<finalName>arthas-apm-demo</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>
|
127
bytekit/pom.xml
Normal file
127
bytekit/pom.xml
Normal file
@ -0,0 +1,127 @@
|
||||
<?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.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>arthas-bytekit</artifactId>
|
||||
<name>arthas-bytekit</name>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>in-project</id>
|
||||
<name>In Project Repo</name>
|
||||
<url>file://${project.basedir}/../lib</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-util</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-analysis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.benf</groupId>
|
||||
<artifactId>cfr</artifactId>
|
||||
<version>0.132</version>
|
||||
</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>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>4.3.9.RELEASE</version>
|
||||
</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>
|
22
bytekit/src/main/java/com/taobao/arthas/bytekit/ByteKit.java
Normal file
22
bytekit/src/main/java/com/taobao/arthas/bytekit/ByteKit.java
Normal 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;
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
package com.taobao.arthas.bytekit.asm;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import 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.ASM6, 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package com.taobao.arthas.bytekit.asm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.commons.GeneratorAdapter;
|
||||
import 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.ASM6, 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
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.taobao.arthas.bytekit.asm;
|
||||
|
||||
public class MethodInfo {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.commons.Method;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FrameNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import org.objectweb.asm.tree.InsnNode;
|
||||
import org.objectweb.asm.tree.IntInsnNode;
|
||||
import org.objectweb.asm.tree.JumpInsnNode;
|
||||
import org.objectweb.asm.tree.LabelNode;
|
||||
import org.objectweb.asm.tree.LdcInsnNode;
|
||||
import org.objectweb.asm.tree.LocalVariableNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.objectweb.asm.tree.TryCatchBlockNode;
|
||||
import org.objectweb.asm.tree.TypeInsnNode;
|
||||
import 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.taobao.arthas.bytekit.asm;
|
||||
import org.objectweb.asm.tree.LabelNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.taobao.arthas.bytekit.asm;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.LabelNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import 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;
|
||||
}
|
||||
|
||||
}
|
@ -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 org.objectweb.asm.Type;
|
||||
import 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import org.objectweb.asm.tree.LocalVariableNode;
|
||||
import 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import 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();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import 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);
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.taobao.arthas.bytekit.asm.binding;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import 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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
public class EnterInteceptor {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
public class ExceptionInterceptor {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
public class ExitInterceptor {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
public interface Inteceptor {
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,240 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import org.objectweb.asm.tree.JumpInsnNode;
|
||||
import org.objectweb.asm.tree.LabelNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import 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;
|
||||
|
||||
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());
|
||||
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());
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
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 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();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 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();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 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();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 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();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 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();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 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();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 org.objectweb.asm.Opcodes;
|
||||
import 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();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 org.objectweb.asm.Opcodes;
|
||||
import 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();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 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();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor.annotation;
|
||||
|
||||
public class EmptySuppressHandler {
|
||||
|
||||
@ExceptionHandler
|
||||
public static void onSuppress() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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 org.objectweb.asm.Type;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
|
||||
import com.taobao.arthas.bytekit.asm.binding.Binding;
|
||||
import com.taobao.arthas.bytekit.asm.binding.ThrowableBinding;
|
||||
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
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 org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
|
||||
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
|
||||
import com.taobao.arthas.bytekit.asm.interceptor.annotation.InterceptorParserHander;
|
||||
import com.taobao.arthas.bytekit.utils.InstanceUtils;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,646 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.FieldInsnNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import org.objectweb.asm.tree.LocalVariableNode;
|
||||
import 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MatchResult {
|
||||
|
||||
List<Location> locations;
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.taobao.arthas.bytekit.asm.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.taobao.arthas.bytekit.asm.matcher;
|
||||
|
||||
public interface ClassMatcher {
|
||||
|
||||
boolean match(String name, ClassLoader classLoader);
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.taobao.arthas.bytekit.asm.matcher;
|
||||
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
public interface MethodMatcher {
|
||||
|
||||
boolean match(String className, MethodNode methodNode);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,408 @@
|
||||
package com.taobao.arthas.bytekit.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.commons.Method;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.FieldInsnNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import org.objectweb.asm.tree.InsnNode;
|
||||
import org.objectweb.asm.tree.IntInsnNode;
|
||||
import org.objectweb.asm.tree.LdcInsnNode;
|
||||
import org.objectweb.asm.tree.LocalVariableNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.objectweb.asm.tree.TypeInsnNode;
|
||||
import 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 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()");
|
||||
|
||||
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 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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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));
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,403 @@
|
||||
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 org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.commons.JSRInlinerAdapter;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.LocalVariableNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.objectweb.asm.tree.TypeInsnNode;
|
||||
import org.objectweb.asm.util.ASMifier;
|
||||
import 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.ASM6);
|
||||
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 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.ASM6, 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.ASM6, 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, 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 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 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,484 @@
|
||||
package com.taobao.arthas.bytekit.utils;
|
||||
///*
|
||||
// * Copyright 2002-2017 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.alibaba.bytekit.utils;
|
||||
//
|
||||
//import java.util.Collection;
|
||||
//import java.util.Map;
|
||||
//
|
||||
///**
|
||||
// * Assertion utility class that assists in validating arguments.
|
||||
// *
|
||||
// * <p>Useful for identifying programmer errors early and clearly at runtime.
|
||||
// *
|
||||
// * <p>For example, if the contract of a public method states it does not
|
||||
// * allow {@code null} arguments, {@code Assert} can be used to validate that
|
||||
// * contract. Doing this clearly indicates a contract violation when it
|
||||
// * occurs and protects the class's invariants.
|
||||
// *
|
||||
// * <p>Typically used to validate method arguments rather than configuration
|
||||
// * properties, to check for cases that are usually programmer errors rather
|
||||
// * than configuration errors. In contrast to configuration initialization
|
||||
// * code, there is usually no point in falling back to defaults in such methods.
|
||||
// *
|
||||
// * <p>This class is similar to JUnit's assertion library. If an argument value is
|
||||
// * deemed invalid, an {@link IllegalArgumentException} is thrown (typically).
|
||||
// * For example:
|
||||
// *
|
||||
// * <pre class="code">
|
||||
// * Assert.notNull(clazz, "The class must not be null");
|
||||
// * Assert.isTrue(i > 0, "The value must be greater than zero");</pre>
|
||||
// *
|
||||
// * <p>Mainly for internal use within the framework; consider
|
||||
// * <a href="http://commons.apache.org/proper/commons-lang/">Apache's Commons Lang</a>
|
||||
// * for a more comprehensive suite of {@code String} utilities.
|
||||
// *
|
||||
// * @author Keith Donald
|
||||
// * @author Juergen Hoeller
|
||||
// * @author Sam Brannen
|
||||
// * @author Colin Sampaleanu
|
||||
// * @author Rob Harrop
|
||||
// * @since 1.1.2
|
||||
// */
|
||||
//public abstract class Assert {
|
||||
//
|
||||
// /**
|
||||
// * Assert a boolean expression, throwing an {@code IllegalStateException}
|
||||
// * if the expression evaluates to {@code false}.
|
||||
// * <p>Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException}
|
||||
// * on an assertion failure.
|
||||
// * <pre class="code">Assert.state(id == null, "The id property must not already be initialized");</pre>
|
||||
// * @param expression a boolean expression
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @throws IllegalStateException if {@code expression} is {@code false}
|
||||
// */
|
||||
// public static void state(boolean expression, String message) {
|
||||
// if (!expression) {
|
||||
// throw new IllegalStateException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #state(boolean, String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void state(boolean expression) {
|
||||
// state(expression, "[Assertion failed] - this state invariant must be true");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert a boolean expression, throwing an {@code IllegalArgumentException}
|
||||
// * if the expression evaluates to {@code false}.
|
||||
// * <pre class="code">Assert.isTrue(i > 0, "The value must be greater than zero");</pre>
|
||||
// * @param expression a boolean expression
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @throws IllegalArgumentException if {@code expression} is {@code false}
|
||||
// */
|
||||
// public static void isTrue(boolean expression, String message) {
|
||||
// if (!expression) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #isTrue(boolean, String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void isTrue(boolean expression) {
|
||||
// isTrue(expression, "[Assertion failed] - this expression must be true");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that an object is {@code null}.
|
||||
// * <pre class="code">Assert.isNull(value, "The value must be null");</pre>
|
||||
// * @param object the object to check
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @throws IllegalArgumentException if the object is not {@code null}
|
||||
// */
|
||||
// public static void isNull(Object object, String message) {
|
||||
// if (object != null) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #isNull(Object, String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void isNull(Object object) {
|
||||
// isNull(object, "[Assertion failed] - the object argument must be null");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that an object is not {@code null}.
|
||||
// * <pre class="code">Assert.notNull(clazz, "The class must not be null");</pre>
|
||||
// * @param object the object to check
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @throws IllegalArgumentException if the object is {@code null}
|
||||
// */
|
||||
// public static void notNull(Object object, String message) {
|
||||
// if (object == null) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #notNull(Object, String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void notNull(Object object) {
|
||||
// notNull(object, "[Assertion failed] - this argument is required; it must not be null");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that the given String is not empty; that is,
|
||||
// * it must not be {@code null} and not the empty String.
|
||||
// * <pre class="code">Assert.hasLength(name, "Name must not be empty");</pre>
|
||||
// * @param text the String to check
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @see StringUtils#hasLength
|
||||
// * @throws IllegalArgumentException if the text is empty
|
||||
// */
|
||||
// public static void hasLength(String text, String message) {
|
||||
// if (!StringUtils_hasLength(text)) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #hasLength(String, String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void hasLength(String text) {
|
||||
// hasLength(text,
|
||||
// "[Assertion failed] - this String argument must have length; it must not be null or empty");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that the given String contains valid text content; that is, it must not
|
||||
// * be {@code null} and must contain at least one non-whitespace character.
|
||||
// * <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
|
||||
// * @param text the String to check
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @see StringUtils#hasText
|
||||
// * @throws IllegalArgumentException if the text does not contain valid text content
|
||||
// */
|
||||
// public static void hasText(String text, String message) {
|
||||
// if (!StringUtils_hasText(text)) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #hasText(String, String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void hasText(String text) {
|
||||
// hasText(text,
|
||||
// "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that the given text does not contain the given substring.
|
||||
// * <pre class="code">Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");</pre>
|
||||
// * @param textToSearch the text to search
|
||||
// * @param substring the substring to find within the text
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @throws IllegalArgumentException if the text contains the substring
|
||||
// */
|
||||
// public static void doesNotContain(String textToSearch, String substring, String message) {
|
||||
// if (StringUtils_hasLength(textToSearch) && StringUtils_hasLength(substring) &&
|
||||
// textToSearch.contains(substring)) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #doesNotContain(String, String, String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void doesNotContain(String textToSearch, String substring) {
|
||||
// doesNotContain(textToSearch, substring,
|
||||
// "[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that an array contains elements; that is, it must not be
|
||||
// * {@code null} and must contain at least one element.
|
||||
// * <pre class="code">Assert.notEmpty(array, "The array must contain elements");</pre>
|
||||
// * @param array the array to check
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @throws IllegalArgumentException if the object array is {@code null} or contains no elements
|
||||
// */
|
||||
// public static void notEmpty(Object[] array, String message) {
|
||||
// if (ObjectUtils.isEmpty(array)) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Object[], String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void notEmpty(Object[] array) {
|
||||
// notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that an array contains no {@code null} elements.
|
||||
// * <p>Note: Does not complain if the array is empty!
|
||||
// * <pre class="code">Assert.noNullElements(array, "The array must contain non-null elements");</pre>
|
||||
// * @param array the array to check
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @throws IllegalArgumentException if the object array contains a {@code null} element
|
||||
// */
|
||||
// public static void noNullElements(Object[] array, String message) {
|
||||
// if (array != null) {
|
||||
// for (Object element : array) {
|
||||
// if (element == null) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #noNullElements(Object[], String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void noNullElements(Object[] array) {
|
||||
// noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that a collection contains elements; that is, it must not be
|
||||
// * {@code null} and must contain at least one element.
|
||||
// * <pre class="code">Assert.notEmpty(collection, "Collection must contain elements");</pre>
|
||||
// * @param collection the collection to check
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @throws IllegalArgumentException if the collection is {@code null} or
|
||||
// * contains no elements
|
||||
// */
|
||||
// public static void notEmpty(Collection<?> collection, String message) {
|
||||
// if (collection == null || collection.isEmpty()) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Collection, String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void notEmpty(Collection<?> collection) {
|
||||
// notEmpty(collection,
|
||||
// "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that a Map contains entries; that is, it must not be {@code null}
|
||||
// * and must contain at least one entry.
|
||||
// * <pre class="code">Assert.notEmpty(map, "Map must contain entries");</pre>
|
||||
// * @param map the map to check
|
||||
// * @param message the exception message to use if the assertion fails
|
||||
// * @throws IllegalArgumentException if the map is {@code null} or contains no entries
|
||||
// */
|
||||
// public static void notEmpty(Map<?, ?> map, String message) {
|
||||
// if ((map == null || map.isEmpty())) {
|
||||
// throw new IllegalArgumentException(message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Map, String)}
|
||||
// */
|
||||
// @Deprecated
|
||||
// public static void notEmpty(Map<?, ?> map) {
|
||||
// notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that the provided object is an instance of the provided class.
|
||||
// * <pre class="code">Assert.instanceOf(Foo.class, foo, "Foo expected");</pre>
|
||||
// * @param type the type to check against
|
||||
// * @param obj the object to check
|
||||
// * @param message a message which will be prepended to provide further context.
|
||||
// * If it is empty or ends in ":" or ";" or "," or ".", a full exception message
|
||||
// * will be appended. If it ends in a space, the name of the offending object's
|
||||
// * type will be appended. In any other case, a ":" with a space and the name
|
||||
// * of the offending object's type will be appended.
|
||||
// * @throws IllegalArgumentException if the object is not an instance of type
|
||||
// */
|
||||
// public static void isInstanceOf(Class<?> type, Object obj, String message) {
|
||||
// notNull(type, "Type to check against must not be null");
|
||||
// if (!type.isInstance(obj)) {
|
||||
// instanceCheckFailed(type, obj, message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that the provided object is an instance of the provided class.
|
||||
// * <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
|
||||
// * @param type the type to check against
|
||||
// * @param obj the object to check
|
||||
// * @throws IllegalArgumentException if the object is not an instance of type
|
||||
// */
|
||||
// public static void isInstanceOf(Class<?> type, Object obj) {
|
||||
// isInstanceOf(type, obj, "");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.
|
||||
// * <pre class="code">Assert.isAssignable(Number.class, myClass, "Number expected");</pre>
|
||||
// * @param superType the super type to check against
|
||||
// * @param subType the sub type to check
|
||||
// * @param message a message which will be prepended to provide further context.
|
||||
// * If it is empty or ends in ":" or ";" or "," or ".", a full exception message
|
||||
// * will be appended. If it ends in a space, the name of the offending sub type
|
||||
// * will be appended. In any other case, a ":" with a space and the name of the
|
||||
// * offending sub type will be appended.
|
||||
// * @throws IllegalArgumentException if the classes are not assignable
|
||||
// */
|
||||
// public static void isAssignable(Class<?> superType, Class<?> subType, String message) {
|
||||
// notNull(superType, "Super type to check against must not be null");
|
||||
// if (subType == null || !superType.isAssignableFrom(subType)) {
|
||||
// assignableCheckFailed(superType, subType, message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.
|
||||
// * <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
|
||||
// * @param superType the super type to check
|
||||
// * @param subType the sub type to check
|
||||
// * @throws IllegalArgumentException if the classes are not assignable
|
||||
// */
|
||||
// public static void isAssignable(Class<?> superType, Class<?> subType) {
|
||||
// isAssignable(superType, subType, "");
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private static void instanceCheckFailed(Class<?> type, Object obj, String msg) {
|
||||
// String className = (obj != null ? obj.getClass().getName() : "null");
|
||||
// String result = "";
|
||||
// boolean defaultMessage = true;
|
||||
// if (StringUtils_hasLength(msg)) {
|
||||
// if (endsWithSeparator(msg)) {
|
||||
// result = msg + " ";
|
||||
// }
|
||||
// else {
|
||||
// result = messageWithTypeName(msg, className);
|
||||
// defaultMessage = false;
|
||||
// }
|
||||
// }
|
||||
// if (defaultMessage) {
|
||||
// result = result + ("Object of class [" + className + "] must be an instance of " + type);
|
||||
// }
|
||||
// throw new IllegalArgumentException(result);
|
||||
// }
|
||||
//
|
||||
// private static void assignableCheckFailed(Class<?> superType, Class<?> subType, String msg) {
|
||||
// String result = "";
|
||||
// boolean defaultMessage = true;
|
||||
// if (StringUtils_hasLength(msg)) {
|
||||
// if (endsWithSeparator(msg)) {
|
||||
// result = msg + " ";
|
||||
// }
|
||||
// else {
|
||||
// result = messageWithTypeName(msg, subType);
|
||||
// defaultMessage = false;
|
||||
// }
|
||||
// }
|
||||
// if (defaultMessage) {
|
||||
// result = result + (subType + " is not assignable to " + superType);
|
||||
// }
|
||||
// throw new IllegalArgumentException(result);
|
||||
// }
|
||||
//
|
||||
// private static boolean endsWithSeparator(String msg) {
|
||||
// return (msg.endsWith(":") || msg.endsWith(";") || msg.endsWith(",") || msg.endsWith("."));
|
||||
// }
|
||||
//
|
||||
// private static String messageWithTypeName(String msg, Object typeName) {
|
||||
// return msg + (msg.endsWith(" ") ? "" : ": ") + typeName;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Check that the given {@code CharSequence} is neither {@code null} nor
|
||||
// * of length 0.
|
||||
// * <p>Note: this method returns {@code true} for a {@code CharSequence}
|
||||
// * that purely consists of whitespace.
|
||||
// * <p><pre class="code">
|
||||
// * StringUtils.hasLength(null) = false
|
||||
// * StringUtils.hasLength("") = false
|
||||
// * StringUtils.hasLength(" ") = true
|
||||
// * StringUtils.hasLength("Hello") = true
|
||||
// * </pre>
|
||||
// * @param str the {@code CharSequence} to check (may be {@code null})
|
||||
// * @return {@code true} if the {@code CharSequence} is not {@code null} and has length
|
||||
// * @see #hasText(String)
|
||||
// */
|
||||
// private static boolean StringUtils_hasLength(CharSequence str) {
|
||||
// return (str != null && str.length() > 0);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Check whether the given {@code CharSequence} contains actual <em>text</em>.
|
||||
// * <p>More specifically, this method returns {@code true} if the
|
||||
// * {@code CharSequence} is not {@code null}, its length is greater than
|
||||
// * 0, and it contains at least one non-whitespace character.
|
||||
// * <p><pre class="code">
|
||||
// * StringUtils.hasText(null) = false
|
||||
// * StringUtils.hasText("") = false
|
||||
// * StringUtils.hasText(" ") = false
|
||||
// * StringUtils.hasText("12345") = true
|
||||
// * StringUtils.hasText(" 12345 ") = true
|
||||
// * </pre>
|
||||
// * @param str the {@code CharSequence} to check (may be {@code null})
|
||||
// * @return {@code true} if the {@code CharSequence} is not {@code null},
|
||||
// * its length is greater than 0, and it does not contain whitespace only
|
||||
// * @see Character#isWhitespace
|
||||
// */
|
||||
// private static boolean StringUtils_hasText(CharSequence str) {
|
||||
// return (StringUtils_hasLength(str) && StringUtils_containsText(str));
|
||||
// }
|
||||
//
|
||||
// private static boolean StringUtils_containsText(CharSequence str) {
|
||||
// int strLen = str.length();
|
||||
// for (int i = 0; i < strLen; i++) {
|
||||
// if (!Character.isWhitespace(str.charAt(i))) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Check whether the given {@code String} is empty.
|
||||
// * <p>This method accepts any Object as an argument, comparing it to
|
||||
// * {@code null} and the empty String. As a consequence, this method
|
||||
// * will never return {@code true} for a non-null non-String object.
|
||||
// * <p>The Object signature is useful for general attribute handling code
|
||||
// * that commonly deals with Strings but generally has to iterate over
|
||||
// * Objects since attributes may e.g. be primitive value objects as well.
|
||||
// * @param str the candidate String
|
||||
// * @since 3.2.1
|
||||
// */
|
||||
// private static boolean StringUtils_isEmpty(Object str) {
|
||||
// return (str == null || "".equals(str));
|
||||
// }
|
||||
//}
|
File diff suppressed because it is too large
Load Diff
@ -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 org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.objectweb.asm.util.Printer;
|
||||
import org.objectweb.asm.util.Textifier;
|
||||
import org.objectweb.asm.util.TraceClassVisitor;
|
||||
import 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.taobao.arthas.bytekit.utils;
|
||||
|
||||
public class MethodUtils {
|
||||
|
||||
}
|
@ -0,0 +1,947 @@
|
||||
package com.taobao.arthas.bytekit.utils;
|
||||
///*
|
||||
// * Copyright 2002-2017 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.alibaba.bytekit.utils;
|
||||
//
|
||||
//import java.lang.reflect.Array;
|
||||
//import java.util.Arrays;
|
||||
//import java.util.Collection;
|
||||
//import java.util.Map;
|
||||
//
|
||||
///**
|
||||
// * Miscellaneous object utility methods.
|
||||
// *
|
||||
// * <p>Mainly for internal use within the framework.
|
||||
// *
|
||||
// * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
|
||||
// *
|
||||
// * @author Juergen Hoeller
|
||||
// * @author Keith Donald
|
||||
// * @author Rod Johnson
|
||||
// * @author Rob Harrop
|
||||
// * @author Chris Beams
|
||||
// * @author Sam Brannen
|
||||
// * @since 19.03.2004
|
||||
// * @see ClassUtils
|
||||
// * @see CollectionUtils
|
||||
// * @see StringUtils
|
||||
// */
|
||||
//public abstract class ObjectUtils {
|
||||
//
|
||||
// private static final int INITIAL_HASH = 7;
|
||||
// private static final int MULTIPLIER = 31;
|
||||
//
|
||||
// private static final String EMPTY_STRING = "";
|
||||
// private static final String NULL_STRING = "null";
|
||||
// private static final String ARRAY_START = "{";
|
||||
// private static final String ARRAY_END = "}";
|
||||
// private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END;
|
||||
// private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Return whether the given throwable is a checked exception:
|
||||
// * that is, neither a RuntimeException nor an Error.
|
||||
// * @param ex the throwable to check
|
||||
// * @return whether the throwable is a checked exception
|
||||
// * @see java.lang.Exception
|
||||
// * @see java.lang.RuntimeException
|
||||
// * @see java.lang.Error
|
||||
// */
|
||||
// public static boolean isCheckedException(Throwable ex) {
|
||||
// return !(ex instanceof RuntimeException || ex instanceof Error);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Check whether the given exception is compatible with the specified
|
||||
// * exception types, as declared in a throws clause.
|
||||
// * @param ex the exception to check
|
||||
// * @param declaredExceptions the exception types declared in the throws clause
|
||||
// * @return whether the given exception is compatible
|
||||
// */
|
||||
// public static boolean isCompatibleWithThrowsClause(Throwable ex, Class<?>... declaredExceptions) {
|
||||
// if (!isCheckedException(ex)) {
|
||||
// return true;
|
||||
// }
|
||||
// if (declaredExceptions != null) {
|
||||
// for (Class<?> declaredException : declaredExceptions) {
|
||||
// if (declaredException.isInstance(ex)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Determine whether the given object is an array:
|
||||
// * either an Object array or a primitive array.
|
||||
// * @param obj the object to check
|
||||
// */
|
||||
// public static boolean isArray(Object obj) {
|
||||
// return (obj != null && obj.getClass().isArray());
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Determine whether the given array is empty:
|
||||
// * i.e. {@code null} or of zero length.
|
||||
// * @param array the array to check
|
||||
// * @see #isEmpty(Object)
|
||||
// */
|
||||
// public static boolean isEmpty(Object[] array) {
|
||||
// return (array == null || array.length == 0);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Determine whether the given object is empty.
|
||||
// * <p>This method supports the following object types.
|
||||
// * <ul>
|
||||
// * <li>{@code Array}: considered empty if its length is zero</li>
|
||||
// * <li>{@link CharSequence}: considered empty if its length is zero</li>
|
||||
// * <li>{@link Collection}: delegates to {@link Collection#isEmpty()}</li>
|
||||
// * <li>{@link Map}: delegates to {@link Map#isEmpty()}</li>
|
||||
// * </ul>
|
||||
// * <p>If the given object is non-null and not one of the aforementioned
|
||||
// * supported types, this method returns {@code false}.
|
||||
// * @param obj the object to check
|
||||
// * @return {@code true} if the object is {@code null} or <em>empty</em>
|
||||
// * @since 4.2
|
||||
// * @see ObjectUtils#isEmpty(Object[])
|
||||
// * @see StringUtils#hasLength(CharSequence)
|
||||
// * @see StringUtils#isEmpty(Object)
|
||||
// * @see CollectionUtils#isEmpty(java.util.Collection)
|
||||
// * @see CollectionUtils#isEmpty(java.util.Map)
|
||||
// */
|
||||
// @SuppressWarnings("rawtypes")
|
||||
// public static boolean isEmpty(Object obj) {
|
||||
// if (obj == null) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// if (obj instanceof CharSequence) {
|
||||
// return ((CharSequence) obj).length() == 0;
|
||||
// }
|
||||
// if (obj.getClass().isArray()) {
|
||||
// return Array.getLength(obj) == 0;
|
||||
// }
|
||||
// if (obj instanceof Collection) {
|
||||
// return ((Collection) obj).isEmpty();
|
||||
// }
|
||||
// if (obj instanceof Map) {
|
||||
// return ((Map) obj).isEmpty();
|
||||
// }
|
||||
//
|
||||
// // else
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Check whether the given array contains the given element.
|
||||
// * @param array the array to check (may be {@code null},
|
||||
// * in which case the return value will always be {@code false})
|
||||
// * @param element the element to check for
|
||||
// * @return whether the element has been found in the given array
|
||||
// */
|
||||
// public static boolean containsElement(Object[] array, Object element) {
|
||||
// if (array == null) {
|
||||
// return false;
|
||||
// }
|
||||
// for (Object arrayEle : array) {
|
||||
// if (nullSafeEquals(arrayEle, element)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Check whether the given array of enum constants contains a constant with the given name,
|
||||
// * ignoring case when determining a match.
|
||||
// * @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
|
||||
// * @param constant the constant name to find (must not be null or empty string)
|
||||
// * @return whether the constant has been found in the given array
|
||||
// */
|
||||
// public static boolean containsConstant(Enum<?>[] enumValues, String constant) {
|
||||
// return containsConstant(enumValues, constant, false);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Check whether the given array of enum constants contains a constant with the given name.
|
||||
// * @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
|
||||
// * @param constant the constant name to find (must not be null or empty string)
|
||||
// * @param caseSensitive whether case is significant in determining a match
|
||||
// * @return whether the constant has been found in the given array
|
||||
// */
|
||||
// public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) {
|
||||
// for (Enum<?> candidate : enumValues) {
|
||||
// if (caseSensitive ?
|
||||
// candidate.toString().equals(constant) :
|
||||
// candidate.toString().equalsIgnoreCase(constant)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Case insensitive alternative to {@link Enum#valueOf(Class, String)}.
|
||||
// * @param <E> the concrete Enum type
|
||||
// * @param enumValues the array of all Enum constants in question, usually per Enum.values()
|
||||
// * @param constant the constant to get the enum value of
|
||||
// * @throws IllegalArgumentException if the given constant is not found in the given array
|
||||
// * of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to avoid this exception.
|
||||
// */
|
||||
// public static <E extends Enum<?>> E caseInsensitiveValueOf(E[] enumValues, String constant) {
|
||||
// for (E candidate : enumValues) {
|
||||
// if (candidate.toString().equalsIgnoreCase(constant)) {
|
||||
// return candidate;
|
||||
// }
|
||||
// }
|
||||
// throw new IllegalArgumentException(
|
||||
// String.format("constant [%s] does not exist in enum type %s",
|
||||
// constant, enumValues.getClass().getComponentType().getName()));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Append the given object to the given array, returning a new array
|
||||
// * consisting of the input array contents plus the given object.
|
||||
// * @param array the array to append to (can be {@code null})
|
||||
// * @param obj the object to append
|
||||
// * @return the new array (of the same component type; never {@code null})
|
||||
// */
|
||||
// public static <A, O extends A> A[] addObjectToArray(A[] array, O obj) {
|
||||
// Class<?> compType = Object.class;
|
||||
// if (array != null) {
|
||||
// compType = array.getClass().getComponentType();
|
||||
// }
|
||||
// else if (obj != null) {
|
||||
// compType = obj.getClass();
|
||||
// }
|
||||
// int newArrLength = (array != null ? array.length + 1 : 1);
|
||||
// @SuppressWarnings("unchecked")
|
||||
// A[] newArr = (A[]) Array.newInstance(compType, newArrLength);
|
||||
// if (array != null) {
|
||||
// System.arraycopy(array, 0, newArr, 0, array.length);
|
||||
// }
|
||||
// newArr[newArr.length - 1] = obj;
|
||||
// return newArr;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Convert the given array (which may be a primitive array) to an
|
||||
// * object array (if necessary of primitive wrapper objects).
|
||||
// * <p>A {@code null} source value will be converted to an
|
||||
// * empty Object array.
|
||||
// * @param source the (potentially primitive) array
|
||||
// * @return the corresponding object array (never {@code null})
|
||||
// * @throws IllegalArgumentException if the parameter is not an array
|
||||
// */
|
||||
// public static Object[] toObjectArray(Object source) {
|
||||
// if (source instanceof Object[]) {
|
||||
// return (Object[]) source;
|
||||
// }
|
||||
// if (source == null) {
|
||||
// return new Object[0];
|
||||
// }
|
||||
// if (!source.getClass().isArray()) {
|
||||
// throw new IllegalArgumentException("Source is not an array: " + source);
|
||||
// }
|
||||
// int length = Array.getLength(source);
|
||||
// if (length == 0) {
|
||||
// return new Object[0];
|
||||
// }
|
||||
// Class<?> wrapperType = Array.get(source, 0).getClass();
|
||||
// Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// newArray[i] = Array.get(source, i);
|
||||
// }
|
||||
// return newArray;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// //---------------------------------------------------------------------
|
||||
// // Convenience methods for content-based equality/hash-code handling
|
||||
// //---------------------------------------------------------------------
|
||||
//
|
||||
// /**
|
||||
// * Determine if the given objects are equal, returning {@code true} if
|
||||
// * both are {@code null} or {@code false} if only one is {@code null}.
|
||||
// * <p>Compares arrays with {@code Arrays.equals}, performing an equality
|
||||
// * check based on the array elements rather than the array reference.
|
||||
// * @param o1 first Object to compare
|
||||
// * @param o2 second Object to compare
|
||||
// * @return whether the given objects are equal
|
||||
// * @see Object#equals(Object)
|
||||
// * @see java.util.Arrays#equals
|
||||
// */
|
||||
// public static boolean nullSafeEquals(Object o1, Object o2) {
|
||||
// if (o1 == o2) {
|
||||
// return true;
|
||||
// }
|
||||
// if (o1 == null || o2 == null) {
|
||||
// return false;
|
||||
// }
|
||||
// if (o1.equals(o2)) {
|
||||
// return true;
|
||||
// }
|
||||
// if (o1.getClass().isArray() && o2.getClass().isArray()) {
|
||||
// return arrayEquals(o1, o2);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Compare the given arrays with {@code Arrays.equals}, performing an equality
|
||||
// * check based on the array elements rather than the array reference.
|
||||
// * @param o1 first array to compare
|
||||
// * @param o2 second array to compare
|
||||
// * @return whether the given objects are equal
|
||||
// * @see #nullSafeEquals(Object, Object)
|
||||
// * @see java.util.Arrays#equals
|
||||
// */
|
||||
// private static boolean arrayEquals(Object o1, Object o2) {
|
||||
// if (o1 instanceof Object[] && o2 instanceof Object[]) {
|
||||
// return Arrays.equals((Object[]) o1, (Object[]) o2);
|
||||
// }
|
||||
// if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
|
||||
// return Arrays.equals((boolean[]) o1, (boolean[]) o2);
|
||||
// }
|
||||
// if (o1 instanceof byte[] && o2 instanceof byte[]) {
|
||||
// return Arrays.equals((byte[]) o1, (byte[]) o2);
|
||||
// }
|
||||
// if (o1 instanceof char[] && o2 instanceof char[]) {
|
||||
// return Arrays.equals((char[]) o1, (char[]) o2);
|
||||
// }
|
||||
// if (o1 instanceof double[] && o2 instanceof double[]) {
|
||||
// return Arrays.equals((double[]) o1, (double[]) o2);
|
||||
// }
|
||||
// if (o1 instanceof float[] && o2 instanceof float[]) {
|
||||
// return Arrays.equals((float[]) o1, (float[]) o2);
|
||||
// }
|
||||
// if (o1 instanceof int[] && o2 instanceof int[]) {
|
||||
// return Arrays.equals((int[]) o1, (int[]) o2);
|
||||
// }
|
||||
// if (o1 instanceof long[] && o2 instanceof long[]) {
|
||||
// return Arrays.equals((long[]) o1, (long[]) o2);
|
||||
// }
|
||||
// if (o1 instanceof short[] && o2 instanceof short[]) {
|
||||
// return Arrays.equals((short[]) o1, (short[]) o2);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return as hash code for the given object; typically the value of
|
||||
// * {@code Object#hashCode()}}. If the object is an array,
|
||||
// * this method will delegate to any of the {@code nullSafeHashCode}
|
||||
// * methods for arrays in this class. If the object is {@code null},
|
||||
// * this method returns 0.
|
||||
// * @see Object#hashCode()
|
||||
// * @see #nullSafeHashCode(Object[])
|
||||
// * @see #nullSafeHashCode(boolean[])
|
||||
// * @see #nullSafeHashCode(byte[])
|
||||
// * @see #nullSafeHashCode(char[])
|
||||
// * @see #nullSafeHashCode(double[])
|
||||
// * @see #nullSafeHashCode(float[])
|
||||
// * @see #nullSafeHashCode(int[])
|
||||
// * @see #nullSafeHashCode(long[])
|
||||
// * @see #nullSafeHashCode(short[])
|
||||
// */
|
||||
// public static int nullSafeHashCode(Object obj) {
|
||||
// if (obj == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// if (obj.getClass().isArray()) {
|
||||
// if (obj instanceof Object[]) {
|
||||
// return nullSafeHashCode((Object[]) obj);
|
||||
// }
|
||||
// if (obj instanceof boolean[]) {
|
||||
// return nullSafeHashCode((boolean[]) obj);
|
||||
// }
|
||||
// if (obj instanceof byte[]) {
|
||||
// return nullSafeHashCode((byte[]) obj);
|
||||
// }
|
||||
// if (obj instanceof char[]) {
|
||||
// return nullSafeHashCode((char[]) obj);
|
||||
// }
|
||||
// if (obj instanceof double[]) {
|
||||
// return nullSafeHashCode((double[]) obj);
|
||||
// }
|
||||
// if (obj instanceof float[]) {
|
||||
// return nullSafeHashCode((float[]) obj);
|
||||
// }
|
||||
// if (obj instanceof int[]) {
|
||||
// return nullSafeHashCode((int[]) obj);
|
||||
// }
|
||||
// if (obj instanceof long[]) {
|
||||
// return nullSafeHashCode((long[]) obj);
|
||||
// }
|
||||
// if (obj instanceof short[]) {
|
||||
// return nullSafeHashCode((short[]) obj);
|
||||
// }
|
||||
// }
|
||||
// return obj.hashCode();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hash code based on the contents of the specified array.
|
||||
// * If {@code array} is {@code null}, this method returns 0.
|
||||
// */
|
||||
// public static int nullSafeHashCode(Object[] array) {
|
||||
// if (array == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// int hash = INITIAL_HASH;
|
||||
// for (Object element : array) {
|
||||
// hash = MULTIPLIER * hash + nullSafeHashCode(element);
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hash code based on the contents of the specified array.
|
||||
// * If {@code array} is {@code null}, this method returns 0.
|
||||
// */
|
||||
// public static int nullSafeHashCode(boolean[] array) {
|
||||
// if (array == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// int hash = INITIAL_HASH;
|
||||
// for (boolean element : array) {
|
||||
// hash = MULTIPLIER * hash + hashCode(element);
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hash code based on the contents of the specified array.
|
||||
// * If {@code array} is {@code null}, this method returns 0.
|
||||
// */
|
||||
// public static int nullSafeHashCode(byte[] array) {
|
||||
// if (array == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// int hash = INITIAL_HASH;
|
||||
// for (byte element : array) {
|
||||
// hash = MULTIPLIER * hash + element;
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hash code based on the contents of the specified array.
|
||||
// * If {@code array} is {@code null}, this method returns 0.
|
||||
// */
|
||||
// public static int nullSafeHashCode(char[] array) {
|
||||
// if (array == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// int hash = INITIAL_HASH;
|
||||
// for (char element : array) {
|
||||
// hash = MULTIPLIER * hash + element;
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hash code based on the contents of the specified array.
|
||||
// * If {@code array} is {@code null}, this method returns 0.
|
||||
// */
|
||||
// public static int nullSafeHashCode(double[] array) {
|
||||
// if (array == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// int hash = INITIAL_HASH;
|
||||
// for (double element : array) {
|
||||
// hash = MULTIPLIER * hash + hashCode(element);
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hash code based on the contents of the specified array.
|
||||
// * If {@code array} is {@code null}, this method returns 0.
|
||||
// */
|
||||
// public static int nullSafeHashCode(float[] array) {
|
||||
// if (array == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// int hash = INITIAL_HASH;
|
||||
// for (float element : array) {
|
||||
// hash = MULTIPLIER * hash + hashCode(element);
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hash code based on the contents of the specified array.
|
||||
// * If {@code array} is {@code null}, this method returns 0.
|
||||
// */
|
||||
// public static int nullSafeHashCode(int[] array) {
|
||||
// if (array == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// int hash = INITIAL_HASH;
|
||||
// for (int element : array) {
|
||||
// hash = MULTIPLIER * hash + element;
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hash code based on the contents of the specified array.
|
||||
// * If {@code array} is {@code null}, this method returns 0.
|
||||
// */
|
||||
// public static int nullSafeHashCode(long[] array) {
|
||||
// if (array == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// int hash = INITIAL_HASH;
|
||||
// for (long element : array) {
|
||||
// hash = MULTIPLIER * hash + hashCode(element);
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hash code based on the contents of the specified array.
|
||||
// * If {@code array} is {@code null}, this method returns 0.
|
||||
// */
|
||||
// public static int nullSafeHashCode(short[] array) {
|
||||
// if (array == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// int hash = INITIAL_HASH;
|
||||
// for (short element : array) {
|
||||
// hash = MULTIPLIER * hash + element;
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return the same value as {@link Boolean#hashCode()}}.
|
||||
// * @see Boolean#hashCode()
|
||||
// */
|
||||
// public static int hashCode(boolean bool) {
|
||||
// return (bool ? 1231 : 1237);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return the same value as {@link Double#hashCode()}}.
|
||||
// * @see Double#hashCode()
|
||||
// */
|
||||
// public static int hashCode(double dbl) {
|
||||
// return hashCode(Double.doubleToLongBits(dbl));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return the same value as {@link Float#hashCode()}}.
|
||||
// * @see Float#hashCode()
|
||||
// */
|
||||
// public static int hashCode(float flt) {
|
||||
// return Float.floatToIntBits(flt);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return the same value as {@link Long#hashCode()}}.
|
||||
// * @see Long#hashCode()
|
||||
// */
|
||||
// public static int hashCode(long lng) {
|
||||
// return (int) (lng ^ (lng >>> 32));
|
||||
// }
|
||||
//
|
||||
//
|
||||
// //---------------------------------------------------------------------
|
||||
// // Convenience methods for toString output
|
||||
// //---------------------------------------------------------------------
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of an object's overall identity.
|
||||
// * @param obj the object (may be {@code null})
|
||||
// * @return the object's identity as String representation,
|
||||
// * or an empty String if the object was {@code null}
|
||||
// */
|
||||
// public static String identityToString(Object obj) {
|
||||
// if (obj == null) {
|
||||
// return EMPTY_STRING;
|
||||
// }
|
||||
// return obj.getClass().getName() + "@" + getIdentityHexString(obj);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a hex String form of an object's identity hash code.
|
||||
// * @param obj the object
|
||||
// * @return the object's identity code in hex notation
|
||||
// */
|
||||
// public static String getIdentityHexString(Object obj) {
|
||||
// return Integer.toHexString(System.identityHashCode(obj));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a content-based String representation if {@code obj} is
|
||||
// * not {@code null}; otherwise returns an empty String.
|
||||
// * <p>Differs from {@link #nullSafeToString(Object)} in that it returns
|
||||
// * an empty String rather than "null" for a {@code null} value.
|
||||
// * @param obj the object to build a display String for
|
||||
// * @return a display String representation of {@code obj}
|
||||
// * @see #nullSafeToString(Object)
|
||||
// */
|
||||
// public static String getDisplayString(Object obj) {
|
||||
// if (obj == null) {
|
||||
// return EMPTY_STRING;
|
||||
// }
|
||||
// return nullSafeToString(obj);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Determine the class name for the given object.
|
||||
// * <p>Returns {@code "null"} if {@code obj} is {@code null}.
|
||||
// * @param obj the object to introspect (may be {@code null})
|
||||
// * @return the corresponding class name
|
||||
// */
|
||||
// public static String nullSafeClassName(Object obj) {
|
||||
// return (obj != null ? obj.getClass().getName() : NULL_STRING);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the specified Object.
|
||||
// * <p>Builds a String representation of the contents in case of an array.
|
||||
// * Returns {@code "null"} if {@code obj} is {@code null}.
|
||||
// * @param obj the object to build a String representation for
|
||||
// * @return a String representation of {@code obj}
|
||||
// */
|
||||
// public static String nullSafeToString(Object obj) {
|
||||
// if (obj == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// if (obj instanceof String) {
|
||||
// return (String) obj;
|
||||
// }
|
||||
// if (obj instanceof Object[]) {
|
||||
// return nullSafeToString((Object[]) obj);
|
||||
// }
|
||||
// if (obj instanceof boolean[]) {
|
||||
// return nullSafeToString((boolean[]) obj);
|
||||
// }
|
||||
// if (obj instanceof byte[]) {
|
||||
// return nullSafeToString((byte[]) obj);
|
||||
// }
|
||||
// if (obj instanceof char[]) {
|
||||
// return nullSafeToString((char[]) obj);
|
||||
// }
|
||||
// if (obj instanceof double[]) {
|
||||
// return nullSafeToString((double[]) obj);
|
||||
// }
|
||||
// if (obj instanceof float[]) {
|
||||
// return nullSafeToString((float[]) obj);
|
||||
// }
|
||||
// if (obj instanceof int[]) {
|
||||
// return nullSafeToString((int[]) obj);
|
||||
// }
|
||||
// if (obj instanceof long[]) {
|
||||
// return nullSafeToString((long[]) obj);
|
||||
// }
|
||||
// if (obj instanceof short[]) {
|
||||
// return nullSafeToString((short[]) obj);
|
||||
// }
|
||||
// String str = obj.toString();
|
||||
// return (str != null ? str : EMPTY_STRING);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the contents of the specified array.
|
||||
// * <p>The String representation consists of a list of the array's elements,
|
||||
// * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
|
||||
// * by the characters {@code ", "} (a comma followed by a space). Returns
|
||||
// * {@code "null"} if {@code array} is {@code null}.
|
||||
// * @param array the array to build a String representation for
|
||||
// * @return a String representation of {@code array}
|
||||
// */
|
||||
// public static String nullSafeToString(Object[] array) {
|
||||
// if (array == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// int length = array.length;
|
||||
// if (length == 0) {
|
||||
// return EMPTY_ARRAY;
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// if (i == 0) {
|
||||
// sb.append(ARRAY_START);
|
||||
// }
|
||||
// else {
|
||||
// sb.append(ARRAY_ELEMENT_SEPARATOR);
|
||||
// }
|
||||
// sb.append(String.valueOf(array[i]));
|
||||
// }
|
||||
// sb.append(ARRAY_END);
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the contents of the specified array.
|
||||
// * <p>The String representation consists of a list of the array's elements,
|
||||
// * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
|
||||
// * by the characters {@code ", "} (a comma followed by a space). Returns
|
||||
// * {@code "null"} if {@code array} is {@code null}.
|
||||
// * @param array the array to build a String representation for
|
||||
// * @return a String representation of {@code array}
|
||||
// */
|
||||
// public static String nullSafeToString(boolean[] array) {
|
||||
// if (array == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// int length = array.length;
|
||||
// if (length == 0) {
|
||||
// return EMPTY_ARRAY;
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// if (i == 0) {
|
||||
// sb.append(ARRAY_START);
|
||||
// }
|
||||
// else {
|
||||
// sb.append(ARRAY_ELEMENT_SEPARATOR);
|
||||
// }
|
||||
//
|
||||
// sb.append(array[i]);
|
||||
// }
|
||||
// sb.append(ARRAY_END);
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the contents of the specified array.
|
||||
// * <p>The String representation consists of a list of the array's elements,
|
||||
// * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
|
||||
// * by the characters {@code ", "} (a comma followed by a space). Returns
|
||||
// * {@code "null"} if {@code array} is {@code null}.
|
||||
// * @param array the array to build a String representation for
|
||||
// * @return a String representation of {@code array}
|
||||
// */
|
||||
// public static String nullSafeToString(byte[] array) {
|
||||
// if (array == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// int length = array.length;
|
||||
// if (length == 0) {
|
||||
// return EMPTY_ARRAY;
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// if (i == 0) {
|
||||
// sb.append(ARRAY_START);
|
||||
// }
|
||||
// else {
|
||||
// sb.append(ARRAY_ELEMENT_SEPARATOR);
|
||||
// }
|
||||
// sb.append(array[i]);
|
||||
// }
|
||||
// sb.append(ARRAY_END);
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the contents of the specified array.
|
||||
// * <p>The String representation consists of a list of the array's elements,
|
||||
// * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
|
||||
// * by the characters {@code ", "} (a comma followed by a space). Returns
|
||||
// * {@code "null"} if {@code array} is {@code null}.
|
||||
// * @param array the array to build a String representation for
|
||||
// * @return a String representation of {@code array}
|
||||
// */
|
||||
// public static String nullSafeToString(char[] array) {
|
||||
// if (array == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// int length = array.length;
|
||||
// if (length == 0) {
|
||||
// return EMPTY_ARRAY;
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// if (i == 0) {
|
||||
// sb.append(ARRAY_START);
|
||||
// }
|
||||
// else {
|
||||
// sb.append(ARRAY_ELEMENT_SEPARATOR);
|
||||
// }
|
||||
// sb.append("'").append(array[i]).append("'");
|
||||
// }
|
||||
// sb.append(ARRAY_END);
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the contents of the specified array.
|
||||
// * <p>The String representation consists of a list of the array's elements,
|
||||
// * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
|
||||
// * by the characters {@code ", "} (a comma followed by a space). Returns
|
||||
// * {@code "null"} if {@code array} is {@code null}.
|
||||
// * @param array the array to build a String representation for
|
||||
// * @return a String representation of {@code array}
|
||||
// */
|
||||
// public static String nullSafeToString(double[] array) {
|
||||
// if (array == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// int length = array.length;
|
||||
// if (length == 0) {
|
||||
// return EMPTY_ARRAY;
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// if (i == 0) {
|
||||
// sb.append(ARRAY_START);
|
||||
// }
|
||||
// else {
|
||||
// sb.append(ARRAY_ELEMENT_SEPARATOR);
|
||||
// }
|
||||
//
|
||||
// sb.append(array[i]);
|
||||
// }
|
||||
// sb.append(ARRAY_END);
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the contents of the specified array.
|
||||
// * <p>The String representation consists of a list of the array's elements,
|
||||
// * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
|
||||
// * by the characters {@code ", "} (a comma followed by a space). Returns
|
||||
// * {@code "null"} if {@code array} is {@code null}.
|
||||
// * @param array the array to build a String representation for
|
||||
// * @return a String representation of {@code array}
|
||||
// */
|
||||
// public static String nullSafeToString(float[] array) {
|
||||
// if (array == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// int length = array.length;
|
||||
// if (length == 0) {
|
||||
// return EMPTY_ARRAY;
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// if (i == 0) {
|
||||
// sb.append(ARRAY_START);
|
||||
// }
|
||||
// else {
|
||||
// sb.append(ARRAY_ELEMENT_SEPARATOR);
|
||||
// }
|
||||
//
|
||||
// sb.append(array[i]);
|
||||
// }
|
||||
// sb.append(ARRAY_END);
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the contents of the specified array.
|
||||
// * <p>The String representation consists of a list of the array's elements,
|
||||
// * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
|
||||
// * by the characters {@code ", "} (a comma followed by a space). Returns
|
||||
// * {@code "null"} if {@code array} is {@code null}.
|
||||
// * @param array the array to build a String representation for
|
||||
// * @return a String representation of {@code array}
|
||||
// */
|
||||
// public static String nullSafeToString(int[] array) {
|
||||
// if (array == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// int length = array.length;
|
||||
// if (length == 0) {
|
||||
// return EMPTY_ARRAY;
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// if (i == 0) {
|
||||
// sb.append(ARRAY_START);
|
||||
// }
|
||||
// else {
|
||||
// sb.append(ARRAY_ELEMENT_SEPARATOR);
|
||||
// }
|
||||
// sb.append(array[i]);
|
||||
// }
|
||||
// sb.append(ARRAY_END);
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the contents of the specified array.
|
||||
// * <p>The String representation consists of a list of the array's elements,
|
||||
// * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
|
||||
// * by the characters {@code ", "} (a comma followed by a space). Returns
|
||||
// * {@code "null"} if {@code array} is {@code null}.
|
||||
// * @param array the array to build a String representation for
|
||||
// * @return a String representation of {@code array}
|
||||
// */
|
||||
// public static String nullSafeToString(long[] array) {
|
||||
// if (array == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// int length = array.length;
|
||||
// if (length == 0) {
|
||||
// return EMPTY_ARRAY;
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// if (i == 0) {
|
||||
// sb.append(ARRAY_START);
|
||||
// }
|
||||
// else {
|
||||
// sb.append(ARRAY_ELEMENT_SEPARATOR);
|
||||
// }
|
||||
// sb.append(array[i]);
|
||||
// }
|
||||
// sb.append(ARRAY_END);
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return a String representation of the contents of the specified array.
|
||||
// * <p>The String representation consists of a list of the array's elements,
|
||||
// * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
|
||||
// * by the characters {@code ", "} (a comma followed by a space). Returns
|
||||
// * {@code "null"} if {@code array} is {@code null}.
|
||||
// * @param array the array to build a String representation for
|
||||
// * @return a String representation of {@code array}
|
||||
// */
|
||||
// public static String nullSafeToString(short[] array) {
|
||||
// if (array == null) {
|
||||
// return NULL_STRING;
|
||||
// }
|
||||
// int length = array.length;
|
||||
// if (length == 0) {
|
||||
// return EMPTY_ARRAY;
|
||||
// }
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// if (i == 0) {
|
||||
// sb.append(ARRAY_START);
|
||||
// }
|
||||
// else {
|
||||
// sb.append(ARRAY_ELEMENT_SEPARATOR);
|
||||
// }
|
||||
// sb.append(array[i]);
|
||||
// }
|
||||
// sb.append(ARRAY_END);
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
//}
|
@ -0,0 +1,842 @@
|
||||
package com.taobao.arthas.bytekit.utils;
|
||||
///*
|
||||
// * Copyright 2002-2017 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.alibaba.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;
|
||||
//import java.util.Map;
|
||||
//
|
||||
///**
|
||||
// * 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$";
|
||||
//
|
||||
// private static final Method[] NO_METHODS = {};
|
||||
//
|
||||
// private static final Field[] NO_FIELDS = {};
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods
|
||||
// * from Java 8 based interfaces, allowing for fast iteration.
|
||||
// */
|
||||
// private static final Map<Class<?>, Method[]> declaredMethodsCache =
|
||||
// new ConcurrentReferenceHashMap<Class<?>, Method[]>(256);
|
||||
//
|
||||
// /**
|
||||
// * Cache for {@link Class#getDeclaredFields()}, allowing for fast iteration.
|
||||
// */
|
||||
// private static final Map<Class<?>, Field[]> declaredFieldsCache =
|
||||
// new ConcurrentReferenceHashMap<Class<?>, Field[]>(256);
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * 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) {
|
||||
// Assert.notNull(clazz, "Class must not be null");
|
||||
// Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified");
|
||||
// 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) {
|
||||
// Assert.notNull(clazz, "Class must not be null");
|
||||
// Assert.notNull(name, "Method name must not be null");
|
||||
// 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) {
|
||||
// Assert.notNull(method, "Method must not be null");
|
||||
// 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) {
|
||||
// Assert.notNull(clazz, "Class must not be null");
|
||||
// Method[] result = declaredMethodsCache.get(clazz);
|
||||
// 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;
|
||||
// }
|
||||
// declaredMethodsCache.put(clazz, (result.length == 0 ? NO_METHODS : result));
|
||||
// }
|
||||
// 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) {
|
||||
// Assert.notNull(clazz, "Class must not be null");
|
||||
// Field[] result = declaredFieldsCache.get(clazz);
|
||||
// if (result == null) {
|
||||
// result = clazz.getDeclaredFields();
|
||||
// declaredFieldsCache.put(clazz, (result.length == 0 ? NO_FIELDS : result));
|
||||
// }
|
||||
// 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) {
|
||||
// Assert.notNull(src, "Source for field copy cannot be null");
|
||||
// Assert.notNull(dest, "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);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Clear the internal method/field cache.
|
||||
// * @since 4.2.4
|
||||
// */
|
||||
// public static void clearCache() {
|
||||
// declaredMethodsCache.clear();
|
||||
// declaredFieldsCache.clear();
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * 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);
|
||||
// }
|
||||
// };
|
||||
//
|
||||
//}
|
@ -0,0 +1,54 @@
|
||||
package com.taobao.arthas.bytekit.utils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.util.CheckClassAdapter;
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 = false)
|
||||
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 = false
|
||||
, 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:");
|
||||
}
|
||||
|
||||
}
|
@ -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:");
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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:");
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
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.AtLine;
|
||||
import com.taobao.arthas.bytekit.utils.Decompiler;
|
||||
|
||||
public class AtLineTest {
|
||||
@Rule
|
||||
public OutputCapture capture = new OutputCapture();
|
||||
|
||||
static class Sample {
|
||||
|
||||
public int testLine(int i) {
|
||||
String s = "" + i;
|
||||
if(i > 0) {
|
||||
String abc = s + i;
|
||||
i++;
|
||||
i = i * 100
|
||||
+ i
|
||||
- 100 + Math.max(100, i);
|
||||
i += s.length() + abc.length();
|
||||
}else {
|
||||
if(i == -1) {
|
||||
try {
|
||||
System.err.println("i is -1");
|
||||
throw new RuntimeException();
|
||||
} catch (Exception e) {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return i * 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestAccessInterceptor {
|
||||
|
||||
@AtLine(lines = { -1}, inline = false)
|
||||
public static void atLine(
|
||||
@Binding.This Object object,
|
||||
@Binding.Class Object clazz
|
||||
,
|
||||
@Binding.Line int line,
|
||||
@Binding.Args Object[] args
|
||||
,
|
||||
@Binding.ArgNames String[] argNames
|
||||
,
|
||||
@Binding.LocalVars Object[] vars,
|
||||
@Binding.LocalVarNames String[] varNames
|
||||
) {
|
||||
System.err.println("atLine: this" + object);
|
||||
System.err.println("line: " + line);
|
||||
System.err.println("args: " + Arrays.toString(args));
|
||||
System.err.println("argNames: " + Arrays.toString(argNames));
|
||||
|
||||
System.err.println("vars: " + Arrays.toString(vars));
|
||||
System.err.println("varNames: " + Arrays.toString(varNames));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLine() throws Exception {
|
||||
TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("*")
|
||||
.redefine(true);
|
||||
byte[] bytes = helper.process(Sample.class);
|
||||
|
||||
new Sample().testLine(100);
|
||||
|
||||
System.err.println(Decompiler.decompile(bytes));
|
||||
|
||||
assertThat(capture.toString()).contains("atLine: this");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
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.AtSyncEnter;
|
||||
import com.taobao.arthas.bytekit.utils.Decompiler;
|
||||
|
||||
public class AtSyncEnterTest {
|
||||
@Rule
|
||||
public OutputCapture capture = new OutputCapture();
|
||||
|
||||
static class Sample {
|
||||
|
||||
public int testLine(int i) {
|
||||
String s = "" + i;
|
||||
synchronized (s) {
|
||||
if(i > 0) {
|
||||
String abc = s + i;
|
||||
i++;
|
||||
i = i * 100
|
||||
+ i
|
||||
- 100 + Math.max(100, i);
|
||||
i += s.length() + abc.length();
|
||||
}else {
|
||||
if(i == -1) {
|
||||
try {
|
||||
System.err.println("i is -1");
|
||||
throw new RuntimeException();
|
||||
} catch (Exception e) {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return i * 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestInterceptor {
|
||||
|
||||
@AtSyncEnter(whenComplete=false, inline = false)
|
||||
public static void atSyncEnter(
|
||||
@Binding.This Object object,
|
||||
@Binding.Class Object clazz
|
||||
,
|
||||
@Binding.Args Object[] args
|
||||
,
|
||||
@Binding.ArgNames String[] argNames
|
||||
,
|
||||
@Binding.LocalVars Object[] vars,
|
||||
@Binding.LocalVarNames String[] varNames
|
||||
,
|
||||
@Binding.Monitor Object monitor
|
||||
) {
|
||||
System.err.println("atSyncEnter: this" + object);
|
||||
System.err.println("args: " + Arrays.toString(args));
|
||||
System.err.println("argNames: " + Arrays.toString(argNames));
|
||||
|
||||
System.err.println("vars: " + Arrays.toString(vars));
|
||||
System.err.println("varNames: " + Arrays.toString(varNames));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
TestHelper helper = TestHelper.builder().interceptorClass(TestInterceptor.class).methodMatcher("*")
|
||||
.redefine(true);
|
||||
byte[] bytes = helper.process(Sample.class);
|
||||
|
||||
new Sample().testLine(100);
|
||||
|
||||
System.err.println(Decompiler.decompile(bytes));
|
||||
|
||||
assertThat(capture.toString()).contains("atSyncEnter: this");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
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.AtSyncExit;
|
||||
import com.taobao.arthas.bytekit.utils.Decompiler;
|
||||
|
||||
public class AtSyncExitTest {
|
||||
@Rule
|
||||
public OutputCapture capture = new OutputCapture();
|
||||
|
||||
static class Sample {
|
||||
|
||||
public int testLine(int i) {
|
||||
String s = "" + i;
|
||||
synchronized (s) {
|
||||
if(i > 0) {
|
||||
String abc = s + i;
|
||||
i++;
|
||||
i = i * 100
|
||||
+ i
|
||||
- 100 + Math.max(100, i);
|
||||
i += s.length() + abc.length();
|
||||
}else {
|
||||
if(i == -1) {
|
||||
try {
|
||||
System.err.println("i is -1");
|
||||
throw new RuntimeException();
|
||||
} catch (Exception e) {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return i * 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestInterceptor {
|
||||
|
||||
@AtSyncExit(whenComplete=false, inline = false)
|
||||
public static void atSyncExit(
|
||||
@Binding.This Object object,
|
||||
@Binding.Class Object clazz
|
||||
,
|
||||
@Binding.Args Object[] args
|
||||
,
|
||||
@Binding.ArgNames String[] argNames
|
||||
,
|
||||
@Binding.LocalVars Object[] vars,
|
||||
@Binding.LocalVarNames String[] varNames
|
||||
,
|
||||
@Binding.Monitor Object monitor
|
||||
) {
|
||||
System.err.println("atSyncExit: this" + object);
|
||||
System.err.println("args: " + Arrays.toString(args));
|
||||
System.err.println("argNames: " + Arrays.toString(argNames));
|
||||
|
||||
System.err.println("vars: " + Arrays.toString(vars));
|
||||
System.err.println("varNames: " + Arrays.toString(varNames));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
TestHelper helper = TestHelper.builder().interceptorClass(TestInterceptor.class).methodMatcher("*")
|
||||
.redefine(true);
|
||||
byte[] bytes = helper.process(Sample.class);
|
||||
|
||||
new Sample().testLine(100);
|
||||
|
||||
System.err.println(Decompiler.decompile(bytes));
|
||||
|
||||
assertThat(capture.toString()).contains("atSyncExit: this");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
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.AtThrow;
|
||||
import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
|
||||
import com.taobao.arthas.bytekit.utils.Decompiler;
|
||||
|
||||
public class AtThrowTest {
|
||||
@Rule
|
||||
public OutputCapture capture = new OutputCapture();
|
||||
|
||||
static class Sample {
|
||||
|
||||
public static long testThrow(int i , long l, String s) {
|
||||
try {
|
||||
if(i < 0) {
|
||||
throw new RuntimeException("eeeee");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
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 {
|
||||
|
||||
@AtThrow(inline = false)
|
||||
public static void atThrow(
|
||||
@Binding.This Object object,
|
||||
@Binding.Class Object clazz
|
||||
,
|
||||
@Binding.LocalVars Object[] vars,
|
||||
@Binding.Throwable Throwable t
|
||||
) {
|
||||
System.err.println("atThrow: this" + object);
|
||||
System.err.println("vars: " + Arrays.toString(vars));
|
||||
System.err.println("t: " + t);
|
||||
|
||||
assertThat(t).hasMessage("eeeee");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrow() throws Exception {
|
||||
TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("testThrow")
|
||||
.redefine(true);
|
||||
byte[] bytes = helper.process(Sample.class);
|
||||
|
||||
Sample.testThrow(-1, 0, null);
|
||||
|
||||
System.err.println(Decompiler.decompile(bytes));
|
||||
|
||||
assertThat(capture.toString()).contains("atThrow: this");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.taobao.arthas.bytekit.asm.interceptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import com.taobao.arthas.bytekit.asm.MethodProcessor;
|
||||
import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
|
||||
import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser;
|
||||
import com.taobao.arthas.bytekit.utils.AgentUtils;
|
||||
import com.taobao.arthas.bytekit.utils.AsmUtils;
|
||||
import com.taobao.arthas.bytekit.utils.MatchUtils;
|
||||
import com.taobao.arthas.bytekit.utils.VerifyUtils;
|
||||
|
||||
public class TestHelper {
|
||||
|
||||
private Class<?> interceptorClass;
|
||||
|
||||
private boolean redefine;
|
||||
|
||||
private String methodMatcher = "*";
|
||||
|
||||
private boolean asmVerity = true;
|
||||
|
||||
public static TestHelper builder() {
|
||||
return new TestHelper();
|
||||
}
|
||||
|
||||
public TestHelper interceptorClass(Class<?> interceptorClass) {
|
||||
this.interceptorClass = interceptorClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestHelper redefine(boolean redefine) {
|
||||
this.redefine = redefine;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestHelper methodMatcher(String methodMatcher) {
|
||||
this.methodMatcher = methodMatcher;
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] process(Class<?> transform) throws Exception {
|
||||
DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
|
||||
|
||||
List<InterceptorProcessor> interceptorProcessors = defaultInterceptorClassParser.parse(interceptorClass);
|
||||
|
||||
ClassNode classNode = AsmUtils.loadClass(transform);
|
||||
|
||||
List<MethodNode> matchedMethods = new ArrayList<MethodNode>();
|
||||
for (MethodNode methodNode : classNode.methods) {
|
||||
if (MatchUtils.wildcardMatch(methodNode.name, methodMatcher)) {
|
||||
matchedMethods.add(methodNode);
|
||||
}
|
||||
}
|
||||
|
||||
for (MethodNode methodNode : matchedMethods) {
|
||||
MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode);
|
||||
for (InterceptorProcessor interceptor : interceptorProcessors) {
|
||||
interceptor.process(methodProcessor);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] bytes = AsmUtils.toBytes(classNode);
|
||||
if (asmVerity) {
|
||||
VerifyUtils.asmVerify(bytes);
|
||||
}
|
||||
|
||||
if (redefine) {
|
||||
AgentUtils.redefine(transform, bytes);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package com.taobao.arthas.bytekit.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import com.taobao.arthas.bytekit.utils.AsmUtils;
|
||||
import com.taobao.arthas.bytekit.utils.VerifyUtils;
|
||||
|
||||
public class AsmUtilsTest {
|
||||
|
||||
abstract static class TestClass {
|
||||
public static synchronized List<String> sss(int i, long l, List<String> list) throws IOException, ArrayIndexOutOfBoundsException {
|
||||
return null;
|
||||
}
|
||||
protected abstract String hello(String ss);
|
||||
}
|
||||
|
||||
static class TestConstructorClass {
|
||||
public TestConstructorClass(int i, String s) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodDeclaration() throws IOException {
|
||||
ClassNode classNode = AsmUtils.loadClass(TestClass.class);
|
||||
MethodNode sss = AsmUtils.findFirstMethod(classNode.methods, "sss");
|
||||
|
||||
MethodNode hello = AsmUtils.findFirstMethod(classNode.methods, "hello");
|
||||
|
||||
MethodNode constructor = AsmUtils.findFirstMethod(AsmUtils.loadClass(TestConstructorClass.class).methods, "<init>");
|
||||
|
||||
String helloDeclaration = AsmUtils.methodDeclaration(Type.getType(TestClass.class), hello);
|
||||
String sssDeclaration = AsmUtils.methodDeclaration(Type.getType(TestClass.class), sss);
|
||||
|
||||
String constructorDeclaration = AsmUtils.methodDeclaration(Type.getType(TestConstructorClass.class), constructor);
|
||||
|
||||
System.err.println(helloDeclaration);
|
||||
System.err.println(sssDeclaration);
|
||||
System.err.println(constructorDeclaration);
|
||||
|
||||
Assertions.assertThat(helloDeclaration).isEqualTo("protected abstract java.lang.String hello(java.lang.String)");
|
||||
Assertions.assertThat(sssDeclaration).isEqualTo(
|
||||
"public static synchronized java.util.List sss(int, long, java.util.List) throws java.io.IOException, java.lang.ArrayIndexOutOfBoundsException");
|
||||
Assertions.assertThat(constructorDeclaration).isEqualTo("public com.taobao.arthas.bytekit.utils.AsmUtilsTest$TestConstructorClass(int, java.lang.String)");
|
||||
}
|
||||
|
||||
public static byte[] emptyMethodBytes() throws Exception {
|
||||
ClassWriter cw = new ClassWriter(0);
|
||||
MethodVisitor mv;
|
||||
|
||||
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, "LEmptyClass", null, "java/lang/Object", null);
|
||||
|
||||
{
|
||||
mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
{
|
||||
mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "emptyMethod", "()V", null, null);
|
||||
// mv.visitCode();
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
// mv.visitMaxs(0, 0);
|
||||
// mv.visitEnd();
|
||||
}
|
||||
cw.visitEnd();
|
||||
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyMethodTest() throws Exception {
|
||||
|
||||
byte[] emptyMethodBytes = emptyMethodBytes();
|
||||
|
||||
VerifyUtils.asmVerify(emptyMethodBytes);
|
||||
VerifyUtils.instanceVerity(emptyMethodBytes);
|
||||
|
||||
ClassNode classNode = AsmUtils.toClassNode(emptyMethodBytes);
|
||||
MethodNode methodNode = AsmUtils.findFirstMethod(classNode.methods, "emptyMethod");
|
||||
|
||||
AbstractInsnNode first = methodNode.instructions.getFirst();
|
||||
AbstractInsnNode last = methodNode.instructions.getLast();
|
||||
System.err.println(first);
|
||||
System.err.println(last);
|
||||
|
||||
int size = methodNode.instructions.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
System.err.println(methodNode.instructions.get(i));
|
||||
}
|
||||
|
||||
// String asmCode = AsmUtils.toASMCode(classNode);
|
||||
// System.err.println(asmCode);
|
||||
}
|
||||
|
||||
private String aaa = "";
|
||||
public void xxx () {
|
||||
aaa = "bbb";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFieldAccess() throws IOException {
|
||||
ClassNode classNode = AsmUtils.loadClass(AsmUtilsTest.class);
|
||||
|
||||
MethodNode methodNode = AsmUtils.findFirstMethod(classNode.methods, "xxx");
|
||||
|
||||
int size = methodNode.instructions.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
System.err.println(methodNode.instructions.get(i));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.taobao.arthas.bytekit.utils;
|
||||
|
||||
public class EmptyClass {
|
||||
|
||||
public static void emptyMethod() {
|
||||
|
||||
}
|
||||
|
||||
}
|
12
pom.xml
12
pom.xml
@ -57,12 +57,14 @@
|
||||
<modules>
|
||||
<module>spy</module>
|
||||
<module>common</module>
|
||||
<module>bytekit</module>
|
||||
<module>core</module>
|
||||
<module>agent</module>
|
||||
<module>client</module>
|
||||
<module>memorycompiler</module>
|
||||
<module>boot</module>
|
||||
<module>demo</module>
|
||||
<module>apm-demo</module>
|
||||
<module>testcase</module>
|
||||
<module>site</module>
|
||||
<module>packaging</module>
|
||||
@ -86,6 +88,16 @@
|
||||
<artifactId>asm-commons</artifactId>
|
||||
<version>7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-util</artifactId>
|
||||
<version>7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-analysis</artifactId>
|
||||
<version>7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.middleware</groupId>
|
||||
<artifactId>termd-core</artifactId>
|
||||
|
Loading…
Reference in New Issue
Block a user