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为:
子类是Xxx InsnNode,相当于MethodVisitor的visitXxx Insn。
创建一个方法:
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);
}
}