Core API Method之接口和组件

来源:互联网 发布:云计算的概念 编辑:程序博客网 时间:2024/05/20 22:02

Presentation

    生成和转换编译方法的ASM API是基于MethodVisitor接口,它通过ClassVisitor的vistMethod方法返回。除了annotation和debug信息外,基于指令参数类型个数,该接口为每个字节码指令类别定义了一个方法。这些方法必须以以下顺序调用:

visitAnnotationDefault?( visitAnnotation | visitParameterAnnotation | visitAttribute )*( visitCode( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |visitLocalVariable | visitLineNumber )*visitMaxs )?visitEnd
    如果有annotation和attribute,必须首先访问。接着就是方法的指令。对这些方法来说,在visitCode和visitMaxs之间,代码必须顺序访问。

public interface MethodVisitor {    AnnotationVisitor visitAnnotationDefault();    AnnotationVisitor visitAnnotation(String desc, boolean visible);    AnnotationVisitor visitParameterAnnotation(int parameter,              String desc, boolean visible);    void visitAttribute(Attribute attr);    void visitCode();    void visitFrame(int type, int nLocal, Object[] local, int nStack,         Object[] stack);    void visitInsn(int opcode);    void visitIntInsn(int opcode, int operand);    void visitVarInsn(int opcode, int var);    void visitTypeInsn(int opcode, String desc);    void visitFieldInsn(int opc, String owner, String name, String desc);    void visitMethodInsn(int opc, String owner, String name, String desc);    void visitJumpInsn(int opcode, Label label);    void visitLabel(Label label);    void visitLdcInsn(Object cst);    void visitIincInsn(int var, int increment);    void visitTableSwitchInsn(int min, int max, Label dflt,        Label labels[]);    void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]);    void visitMultiANewArrayInsn(String desc, int dims);    void visitTryCatchBlock(Label start, Label end, Label handler,       String type);    void visitLocalVariable(String name, String desc, String signature,       Label start, Label end, int index);    void visitLineNumber(int line, Label start);    void visitMaxs(int maxStack, int maxLocals);    void visitEnd();}
    visitCode和visitMaxs方法可以用来探测方法指令代码的开始和结束。
    ClassVisitor和MethodVisitor接口结合使用来生成完整的类:
ClassVisitor cv = ...;cv.visit(...);MethodVisitor mv1 = cv.visitMethod(..., "m1", ...);mv1.visitCode();mv1.visitInsn(...);...mv1.visitMaxs(...);mv1.visitEnd();MethodVisitor mv2 = cv.visitMethod(..., "m2", ...);mv2.visitCode();mv2.visitInsn(...);...mv2.visitMaxs(...);mv2.visitEnd();cv.visitEnd();
    记住,没有必要来完成一个method来结束访问另外一个方法。实际上MethodVisitor实例是完全独立的,可以以任何顺序访问。
ClassVisitor cv = ...;cv.visit(...);MethodVisitor mv1 = cv.visitMethod(..., "m1", ...);mv1.visitCode();mv1.visitInsn(...);...MethodVisitor mv2 = cv.visitMethod(..., "m2", ...);mv2.visitCode();mv2.visitInsn(...);...mv1.visitMaxs(...);mv1.visitEnd();...mv2.visitMaxs(...);mv2.visitEnd();cv.visitEnd();
    ASM基于MethodVisotr接口提供了三个核心组件来生成和转换方法:ClassReader类转换编译方法的内容,且调用MethodVisitor对象的相关方法。

       1) ClassReader类转换编译方法的内容,且调用MethodVisitor对象的相关方法。
       2) ClassWriter的visitMethod方法返回MethodVisitor接口的实现,它直接以二进制方式构建编译方法。
       3) MethodAdapter是MethodVisitor的一个实现,它代理另一个MethodVisitor实例的所有方法。
   ClassWriter可选项
      计算一个方法的stack map frame并不容易,你必须计算所有的frame,且找到对应跳转到目标对象的frame,或无条件跳转,最后压缩剩余的frames。同样,计算本地变量和操作栈的大小并不容易。幸运的是ASM可以为你计算,通过指定什么被自动计算的方式来创建ClassWriter。
      1) new ClassWriter(0),不会计算任何东西,你必须计算自己的frames和本地变量和操作栈大小。
      2) new ClassWriter(ClassWriter.COMPUTER_MAXS),本地变量和操作栈的大小被计算。你必须调用visitMaxs,但是可是使用任何参数,它们会被忽略,重计算。使用这个选项,你不得不计算frames。
      3) new ClassWriter(ClassWriter.COMPUTER_FRAMES),一切都被计算好了。没有必要调用visitFrame,但是必须调用visitMaxs。
    使用这些选项很便利,但有很大的代价:COMPUTE_MAXS选项使得ClassWriter慢10%,使用COMPUTE_FRAMES慢2倍。
    如果你选择自己计算frames,你可使得ClassWriter类为你压缩。如果这样的话,你必须访问未压缩frames,visitFrame(F_NEW,nLocals,locals,nStack, stack),这里的nLocals和nStacks是本地变量和操作栈大小,locals和stack是包含对应类型的数组。

Generating methods

    在上面提及的getF方法的字节码可以通过以下方法来生成:mv.visitCode();
mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, "pkg/Bean", "f", "I");mv.visitInsn(IRETURN);mv.visitMaxs(1, 1);mv.visitEnd();

    第一个调用开始字节码生成,后面的三个调用生成方法的三条指令。visitMaxs必须在所有指令被访问过后才被调用。它定义执行帧的本地变量和操作栈大小。最后一个方法调用结束方法的生成。
    setF方法的字节码和构造器的字节码生成的方式类似。更加复杂的是checkAndSetF方法:

mv.visitCode();mv.visitVarInsn(ILOAD, 1);Label label = new Label();mv.visitJumpInsn(IFLT, label);mv.visitVarInsn(ALOAD, 0);mv.visitVarInsn(ILOAD, 1);mv.visitFieldInsn(PUTFIELD, "pkg/Bean", "f", "I");Label end = new Label();mv.visitJumpInsn(GOTO, end);mv.visitLabel(label);mv.visitFrame(F_SAME, 0, null, 0, null);mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");mv.visitInsn(DUP);mv.visitMethodInsn(INVOKESPECIAL,"java/lang/IllegalArgumentException", "<init>", "()V");mv.visitInsn(ATHROW);mv.visitLabel(end);mv.visitFrame(F_SAME, 0, null, 0, null);mv.visitInsn(RETURN);mv.visitMaxs(2, 2);mv.visitEnd();
    visitCode和visitEnd之间的调用和checkAndSetF方法做了精确的映射。一个Lable对象指明了visitLable后面的指令是属于这个lable范畴的。可能有多个lable指向同一个指令,但是一个label必须指向一个指令。换句话说,可能使用不同的lable多次调用visitLable,但是使用在一个指令中的lable必须使用visitLable一次。最后一个约束,lable常量是不能共享的,每个方法必须有它自己的lable。

Transforming methods

    方法可以像类一样被转换,例如通过方法适配器(传递它接受的方法调用):更改参数来改变个别指令。MethodAdapter类提供了诸如method visitor的基本实现,它除了传递接它接受的方法调用外,不做任何事情。
    为了理解method adapter怎么使用,看下以下的简单例子:移除方法内部的NOP指令。

public class RemoveNopAdapter extends MethodAdapter {    public RemoveNopAdapter(MethodVisitor mv) {        super(mv);    }    @Override    public void visitInsn(int opcode) {        if (opcode != NOP) {            mv.visitInsn(opcode);        }    }}public class RemoveNopClassAdapter extends ClassAdapter {     public RemoveNopClassAdapter(ClassVisitor cv) {         super(cv);     }     @Override     public MethodVisitor visitMethod(int access, String name,            String desc, String signature, String[] exceptions) {        MethodVisitor mv;        mv = cv.visitMethod(access, name, desc, signature, exceptions);        if (mv != null) {            mv = new RemoveNopAdapter(mv);        }        return mv;    }}
    class adapter仅仅构建了一个method adapter(它封装了在chain中被下一个class visitor返回的method visitor),并且返回这个adapter。method adapter连的构建与class adapter chain相似。
    然而,这不是强制的,能以不同于class adapter chain的构建方式来构建method adapter chain。甚至,每个方法能有不同的method adapter chain。例如,class adapter可以仅在方法中选择移除NOPs,不在构造器。
mv = cv.visitMethod(access, name, desc, signature, exceptions);if (mv != null && !name.equals("<init>")) {    mv = new RemoveNopAdapter(mv);}
    method adapter chian可以拥有比class adapter chain多的拓扑图。例如class adapter chain是线性的,method adapter可以有分支。
public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) {    MethodVisitor mv1, mv2;    mv1 = cv.visitMethod(access, name, desc, signature, exceptions);    mv2 = cv.visitMethod(access, "_" + name, desc, signature, exceptions);    return new MultiMethodAdapter(mv1, mv2);}

Stateless transformations

    假如我们想测量程序中每个类的花费时间,我们需要在每个类中加入一个静态timer域,且需要加入每个方法的执行时间到这个timer域。我们需要在类中加入一下代码:

public static long timer;public void m() throws Exception {    timer -= System.currentTimeMillis();    Thread.sleep(100);    timer += System.currentTimeMillis();}
    为了对ASM怎么实现这个有大体的理解,我们编译两个类且比较这两个版本的输出。
GETSTATIC C.timer : JINVOKESTATIC java/lang/System.currentTimeMillis()JLSUBPUTSTATIC C.timer : JLDC 100INVOKESTATIC java/lang/Thread.sleep(J)VGETSTATIC C.timer : JINVOKESTATIC java/lang/System.currentTimeMillis()JLADDPUTSTATIC C.timer : JRETURNMAXSTACK = 4MAXLOCALS = 1
    我们必须在方法的开头添加4个指令,且在返回指令浅四个其他指令。我们需要更新最大操作栈大小。方法开头的代码使用visitCode方法访问。我们通过覆写该方法添加前4个指令:
public void visitCode() {    mv.visitCode();    mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");    mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",               "currentTimeMillis", "()J");    mv.visitInsn(LSUB);    mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");}
    代码中的owner必须设置为被转换的class的名称。我们必须在return之前添加后四个指令(在return或athrow之前)。这些指令没有任何参数,可以在visitInsn方法中访问。
public void visitInsn(int opcode) {    if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {        mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",             "currentTimeMillis", "()J");        mv.visitInsn(LADD);        mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");    }    mv.visitInsn(opcode);}
    最后我们必须更新操作栈的大小。我们添加了push long值的指令,因此需要4个slot。在方法开始的时候,操作站初始化所以我们知道在方法开头添加的4个指令需要4个slot。我们插入代码离开栈时,状态为改变(入栈多少,出栈多少)。结果是,如果原始代码需要栈的大小为s,栈大小为max(s,4)。不幸的是,我们在返回之前添加了指令。我们并不知道在此之前操作栈的大小。我们仅知道小于或等于s。因此我们仅能说添加代码后,在返回指令之前需要的操作栈最大值s+4。我们必须覆盖visitMaxs方法:

public void visitMaxs(int maxStack, int maxLocals) {    mv.visitMaxs(maxStack + 4, maxLocals);}

依赖于COMPUTE_MAX选项,不必为操作栈大小的事烦心。

package com.fanshadoop.example;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.FieldVisitor;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Opcodes;public class AddTimerAdapter extends ClassVisitor implements Opcodes {private String owner;private boolean isInterface;public AddTimerAdapter(ClassVisitor cv) {super(ASM4, cv);}@Overridepublic void visit(int version, int access, String name, String signature,String superName, String[] interfaces) {cv.visit(version, access, name, signature, superName, interfaces);owner = name;isInterface = (access & ACC_INTERFACE) != 0;}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc,String signature, String[] exceptions) {MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);if (!isInterface && mv != null && !name.equals("<init>")) {mv = new AddTimerMethodAdapter(mv);}return mv;}@Overridepublic void visitEnd() {if (!isInterface) {FieldVisitor fv = cv.visitField(ACC_PUBLIC + ACC_STATIC, "timer","J", null, null);if (fv != null) {fv.visitEnd();}}cv.visitEnd();}class AddTimerMethodAdapter extends MethodVisitor {public AddTimerMethodAdapter(MethodVisitor mv) {super(ASM4, mv);}@Overridepublic void visitCode() {mv.visitCode();mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","currentTimeMillis", "()J");mv.visitInsn(LSUB);mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");}@Overridepublic void visitInsn(int opcode) {if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","currentTimeMillis", "()J");mv.visitInsn(LADD);mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");}mv.visitInsn(opcode);}@Overridepublic void visitMaxs(int maxStack, int maxLocals) {mv.visitMaxs(maxStack + 4, maxLocals);}}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stub}}

Statefull transformations

    上章节看的transformation是本地的,不依赖我们访问的当前的代码的指令:在开始添加的代码总是相同,总是被添加。这些transformation被成为stateless transformation。
    更复杂的transformation需要记住当前代码访问的一些指令状态。例如,移除所有ICONST_0_IADD。很明确的是,当IADD指令访问时,仅当最后一个访问指令是ICONST_0时被移除。这需要在method adapter内部存储状态,类似这些transformation被称为statefull transformation。
   当ICONST_0被加载时,仅当下一个指令为IADD时,它应该被删除。但下一指令到目前为止未知。解决的方法是将这个决定延期到下一个指令:如果是IADD移除两个指令,否则忽略ICONST_0和当前指令。
   为了实现移除或替换指令序列,引入MethodVisitor子类,它的vistXxxInsn方法调用普通的visitInsn()方法:

public abstract class PatternMethodAdapter extends MethodVisitor {    protected final static int SEEN_NOTHING = 0;    protected int state;    public PatternMethodAdapter(int api, MethodVisitor mv) {        super(api, mv);    }   @Overrid public void visitInsn(int opcode) {        visitInsn();        mv.visitInsn(opcode);    }    @Override public void visitIntInsn(int opcode, int operand) {        visitInsn();        mv.visitIntInsn(opcode, operand);    }    protected abstract void visitInsn();    }    public class RemoveAddZeroAdapter extends PatternMethodAdapter {        private static int SEEN_ICONST_0 = 1;        public RemoveAddZeroAdapter(MethodVisitor mv) {            super(ASM4, mv);        }       @Override public void visitInsn(int opcode) {            if (state == SEEN_ICONST_0) {                if (opcode == IADD) {                    state = SEEN_NOTHING;                   return;                }            }            visitInsn();            if (opcode == ICONST_0) {                    state = SEEN_ICONST_0;                    return;             }             mv.visitInsn(opcode);       }       @Override protected void visitInsn() {             if (state == SEEN_ICONST_0) {                 mv.visitInsn(ICONST_0);             }        state = SEEN_NOTHING;    }}

Labels and frames

    labels和frames仅在它们相关的指令之前被访问。换句话说,他们在与指令的同时被访问,尽管他们不是指令本身。这对探测指令序列有影响,但事实上,这也是优点所在。如果某些指令会跳转到ICONST_0,这意味着这儿有lable指向该指令。移除这两个指令后,该label会指向被移除的指令的后继者。这种情况下,在ICONST_0和IADD之间必须存在一个容易被探测的label。
    对于stack map frames来说也是如此:如果stack map frame在两个指令之间被访问,我么不能移除他们。这两种情况都可以以模式匹配逻辑的方式考虑将lable和frames做为指令的方式来处理。

public abstract class PatternMethodAdapter extends MethodVisitor {    @Override public void visitFrame(int type, int nLocal, Object[] local,int nStack, Object[] stack) {visitInsn();mv.visitFrame(type, nLocal, local, nStack, stack);    }    @Override public void visitLabel(Label label) {visitInsn();mv.visitLabel(label);    }    @Override public void visitMaxs(int maxStack, int maxLocals) {visitInsn();mv.visitMaxs(maxStack, maxLocals);    }}
    注意:visitMaxs也调用visitInsn方法,用来处理方法的尾部是序列的前缀,且必须被探测的情况。


原创粉丝点击