Core API Method之接口和组件

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 : J
INVOKESTATIC java/lang/System.currentTimeMillis()J
LSUB
PUTSTATIC C.timer : J
LDC 100
INVOKESTATIC java/lang/Thread.sleep(J)V
GETSTATIC C.timer : J
INVOKESTATIC java/lang/System.currentTimeMillis()J
LADD
PUTSTATIC C.timer : J
RETURN
MAXSTACK = 4
MAXLOCALS = 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);
	}

	@Override
	public 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;
	}

	@Override
	public 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;
	}

	@Override
	public 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);
		}

		@Override
		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");
		}

		@Override
		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);
		}

		@Override
		public 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方法,用来处理方法的尾部是序列的前缀,且必须被探测的情况。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值