概述
3.2.4无状态转换
假定,我们想要测量一个程序中花费在每个类上的时间,那么我们需要在每个类上添加一个静态的计时字段,并且我们需要将每个方法的执行之间加到这个字段上。也就是说我们想转换类似于C这样的一个类:
public class C {
public void m() throws Exception {
Thread.sleep(100);
}
}
转换之后:
public class C {
public static long timer;
public void m() throws Exception {
timer -= System.currentTimeMillis();
Thread.sleep(100);
timer += System.currentTimeMillis();
}
}
为了弄清楚ASM是如何实现的,我们将编译这两个类,然后对比它们的TraceClassVisitor或者ASMifierClassVisitor的输出。通过TraceClassVisitor我们可以发现以下不同(粗体表示):
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
通过上面我们看到了,我们必须在这个方法的最开始添加四条指令,以及在这个方法返回之前添加另外四条指令。最后 我们需要更新操作数栈的大小。因此,我们可以在方法适配器中重写这个方法以增加最开始的四条指令:
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必须设置为即将转换的类的名字。现在我们需要添加另外四条位于RETURN之前的指令,同时这四条指令也必须位于xRETURN或者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);
}
最后,我们仍然需要更新操作数栈的大小。这些指令中,我们往操作数栈中放置了两个long行数据,因此需要四个方框。在这个方法开始的时候,这个操作数栈是被初始化为空的,因此我们知道最开始的四条指令需要的栈大小为4.我们也知道,我们插入的代码对这个栈大小没有改变(因为从栈中弹出的数据与入栈的数据一样多)。结果是,如果最初的代码需要的栈大小为s,那么转换方法之后栈的大小的最大值为max(4,s)。不幸地是,我们还需要在RETURN指令之前添加四条指令,在这里我们不知道这个操作数栈的大小。我们只知道它的大小应该小于等于s。最后,我们可以认为在RETURN指令之前添加的代码需要的操作数栈的大小最大为s+4。这种最坏的情形在实际中很少发生:就一般的编译器而言,RETURN指令之前,操作数栈中一般只包含方法的返回值,那么它的大小就可能是0,1或者最大为2.但是,如果我们希望处理所有可能的情况,那么我们应该使用最坏的这种情况。所以,我们需要重写visitMaxs方法:
public void visitMaxs(int maxStack, int maxLocals) {
mv.visitMaxs(maxStack + 4, maxLocals);
}
当然了,我们也不必为操作数栈的大小而烦恼,我们可以依赖于COMPUTE_MAXS选项,它会为我们计算最优的值而不是最坏的值。同时,就这么一个简单的转换,也没必须花费这么经理来手动更新maxStack。
现在,一个有趣的问题是:如何处理栈映射帧?原始的代码不包含任何帧,转换后的代码页不包含,难道这是因为我们使用的例子很特殊吗?这里是否有一些情形帧必须被更新吗?答案是否定的,因为,第一,插入的代码没有改变操作数栈,第二,插入的代码没有包含跳转指令,第三,这些跳转指令,或者更正式的,程序的控制流没有修改。这意味着原始的帧没有改变,既然新插入的代码没有增加帧,那么原始帧压缩后也没有改变。
现在,我们可以将这些元素和ClassAdapter以及MethodAdapter结合起来:
public class AddTimerAdapter extends ClassAdapter {
private String owner;
private boolean isInterface;
public AddTimerAdapter(ClassVisitor cv) {
super(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 MethodAdapter {
public AddTimerMethodAdapter(MethodVisitor mv) {
super(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);
}
}
}
这里的类适配器是用来实例化方法适配器(除了构造方法),同时也用来添加计时字段和保存将被转换的类名到一个字段中,这样在方法适配器中可以访问到这个类名。
最后
以上就是勤恳小丸子为你收集整理的ASM指南翻译-12 无状态转换的全部内容,希望文章能够帮你解决ASM指南翻译-12 无状态转换所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复