1. 程式人生 > >ASM框架使用(五)--Tree API修改類和方法

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為:
在這裡插入圖片描述

子類是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種資料流分析方式:

  1. 向前分析,對每一個指令,執行完這個指令後的楨與執行前的楨狀態對比
  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);
}
}