ASM框架使用(五)--Tree API修改類和方法
Tree API通過ClassNode建立和修改類,ClassNode類的API:
建立一個類:
ClassNode cn = new ClassNode(); cn.version = V1_5; cn.access = ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE; cn.name = "pkg/Comparable"; cn.superName = "java/lang/Object"; cn.interfaces.add("pkg/Mesurable"); cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "Ljava/lang/String;", null, "sss")); cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, new Integer(0))); cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I", null, new Integer(1))); cn.methods.add(new MethodNode(ACC_PUBLIC + ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null)); ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS); cn.accept(cw);
Tree API的效能比core API要低30%,但是更加靈活,使用更加方便。
給一個類新增欄位和方法:
private static final String CONST_NM = "N1221";
public void doT() {
System.out.println("ss1");
}
ClassReader classReader=new ClassReader("bytecode.Node"); ClassNode cn = new ClassNode(); classReader.accept(cn,ClassReader.EXPAND_FRAMES); cn.fields.add(new FieldNode(ACC_PRIVATE+ACC_STATIC+ACC_FINAL,"CONST_NM","Ljava/lang/String;",null,"N1221")); MethodNode mn=new MethodNode(ACC_PUBLIC,"doT","()V",null,null); mn.visitCode(); mn.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mn.visitLdcInsn("ss1"); mn.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mn.visitEnd(); cn.methods.add(mn); ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS); cn.accept(cw);
樹API有它的優點,能做一些核心API做不了的事情,比如給一個類添加註解,註解中包含內容的數字簽名。這在Core API中需要訪問完所有的類內容之後才能計算,但是這時候已經不能添加註解了(也可以通過2遍Reader實現,但是比較麻煩)。使用Tree API可以任意訪問元素的特性,就沒有這個問題。
Tree API使用 MethodNode 類產生和修改方法。
大多數字段和意思和ClassNode中的差不多,只有一個instructions不同。
AbstractInsnNode和InsnList是一 一對應的。把一個AbstractInsnNode新增到一個List中之前,需要先把它從上一個list中移除。
AbstractInsnNode是代表位元組碼指令的類的父類,API為:
建立一個方法:
public static MethodNode createMethod(){
MethodNode mn = new MethodNode(ACC_PUBLIC,"checkAndSet","(I)V",null,null);
InsnList il = mn.instructions;
il.add(new VarInsnNode(ILOAD, 1));
LabelNode label = new LabelNode();
il.add(new JumpInsnNode(IFLT, label));
il.add(new VarInsnNode(ALOAD, 0));
il.add(new VarInsnNode(ILOAD, 1));
il.add(new FieldInsnNode(PUTFIELD, "org/by/Cwtest", "f", "I"));
LabelNode end = new LabelNode();
il.add(new JumpInsnNode(GOTO, end));
il.add(label);
il.add(new FrameNode(F_SAME, 0, null, 0, null));
il.add(new TypeInsnNode(NEW, "java/lang/IllegalArgumentException"));
il.add(new InsnNode(DUP));
il.add(new MethodInsnNode(INVOKESPECIAL,
"java/lang/IllegalArgumentException", "<init>", "()V",false));
il.add(new InsnNode(ATHROW));
il.add(end);
il.add(new FrameNode(F_SAME, 0, null, 0, null));
il.add(new InsnNode(RETURN));
mn.maxStack = 2;
mn.maxLocals = 2;
return mn;
}
效果:
public void checkAndSet(int var1) {
if(var1 >= 0) {
this.f = var1;
} else {
throw new IllegalArgumentException();
}
}
使用Tree API分析方法程式碼,主要是資料流和控制流。
資料流分析是通過為每一個位元組碼指令計算方法的執行楨的狀態,
控制流的分析通過程式碼的定向連通圖。
有2種資料流分析方式:
- 向前分析,對每一個指令,執行完這個指令後的楨與執行前的楨狀態對比
- 向後分析,對每一個指令,執行這個指令前的楨與執行後的楨狀態對比
向前的資料流分析通過模擬當前位元組碼指令在楨中執行,出棧,結合,入棧。
這看起來和jvm的直譯器差不多,但是它們不一樣,這個的目的是模擬出所有的執行路徑和所有的可能值,而不是有指定引數所決定的特定路徑。
ASM的位元組碼分析的API在org.objectweb.asm.tree.analysis中,像包名所描述的那樣,它是基於tree api的。事實上,這個包提供了向前分析資料流的方式。
為了執行多種資料流分析,演算法被分為兩部分:一部分是固定地,由框架提供,另一部分由users自己定義。
Analyzer和Frame是框架提供的工具類。
Interpreter和Value是給使用者自定義覆蓋的工具類。
通過覆蓋Analyzer的newControlFlowEdge和newControlFlowExceptionEdge方法,可以分析控制流。
BasicInterpreter和BasicValue是預定義的類:
- UNINITIALIZED_VALUE 所有可能的值
- INT_VALUE 表示int, short, byte, boolean or char
- FLOAT_VALUE 表示 float
- LONG_VALUE 表示 long
- DOUBLE_VALUE 表示double
- REFERENCE_VALUE 表示所有的類和陣列引用
- RETURNADDRESS_VALUE 用於子程式(java 6之後被移除了)
下面是使用分析器,去除不可達的程式碼
public class RemoveDeadCodeAdapter extends MethodVisitor {
String owner;
MethodVisitor next;
public RemoveDeadCodeAdapter(String owner, int access, String name,
String desc, MethodVisitor mv) {
super(ASM4, new MethodNode(access, name, desc, null, null));
this.owner = owner;
next = mv;
}
@Override public void visitEnd() {
MethodNode mn = (MethodNode) mv;
Analyzer<BasicValue> a =
new Analyzer<BasicValue>(new BasicInterpreter());
try {
a.analyze(owner, mn);
Frame<BasicValue>[] frames = a.getFrames();
AbstractInsnNode[] insns = mn.instructions.toArray();
for (int i = 0; i < frames.length; ++i) {
if (frames[i] == null && !(insns[i] instanceof LabelNode)) {
mn.instructions.remove(insns[i]);
}
}
} catch (AnalyzerException ignored) {
}
mn.accept(next);
}
}