我是靠谱客的博主 勤恳小丸子,最近开发中收集的这篇文章主要介绍ASM指南翻译-12 无状态转换,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

 

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 无状态转换所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部