ASM指南翻译-14 方法生成与转换的相关工具

15 篇文章 0 订阅
15 篇文章 0 订阅

 

3.3工具

org.objectweb.asm.commons包中预先定义了一些方法适配器,它们可以辅助你定义你自己的适配器。这个章节将介绍其中的三个适配器,展示如何在AddTimerAdapter示例中使用它们(3.2.4)。它也展示了如何使用前面章节中提到的工具来删除方法或者转换。

 

3.3.1基本工具

2.3节中出现的工具也可以针对方法使用。

Type  

 

很多字节码指令,如XloadxADD或者xRETURN,都依赖于它所处理的类型。Type这个类提供了一个getOpcode方法,针对这些指令,它可以获取一个给定类型的opcode。这个方法以一个int类型的opcode作为参数,然后返回针对调用该方法对象的类型的opcode。例如t.getOpcode(IMUL)返回FMUL,其中tType.FLOAT_TYPE.

 

TraceClassVisitor

 

这个类在前面章节已经介绍过了,主要是打印它所访问类的字节码的文本表示,当然也包括方法的文本表示。这个类也可以用来追踪转换链中方法生成和转换的内容。例如:

java -classpath asm.jar:asm-util.jar \

         org.objectweb.asm.util.TraceClassVisitor \

         java.lang.Void

将会打印出一下内容:

// class version 49.0 (49)

// access flags 49

public final class java/lang/Void {

         // access flags 25

         // signature Ljava/lang/Class<Ljava/lang/Void;>;

         // declaration: java.lang.Class<java.lang.Void>

         public final static Ljava/lang/Class; TYPE

         // access flags 2

         private <init>()V

                   ALOAD 0

                   INVOKESPECIAL java/lang/Object.<init> ()V

                   RETURN

                   MAXSTACK = 1

                   MAXLOCALS = 1

         // access flags 8

         static <clinit>()V

                   LDC "void"

                   INVOKESTATIC java/lang/Class.getPrimitiveClass (...)...

                   PUTSTATIC java/lang/Void.TYPE : Ljava/lang/Class;

                   RETURN

                   MAXSTACK = 1

                   MAXLOCALS = 0

}

上面的代码展示了如何生成一个静态块static { … },即<clinit>方法。注意,如果你想在链的某个点追踪一个方法的内容,你可以选择使用TraceMethodVisitor,而不是用TraceClassVisitor来追踪所有的内容:

public MethodVisitor visitMethod(int access, String name,

         String desc, String signature, String[] exceptions) {

         MethodVisitor mv = cv.visitMethod(access, name, desc, signature,

         exceptions);

         if (debug && mv != null && ...) { // if this method must be traced

                   mv = new TraceMethodVisitor(mv) {

                            @Override public void visitEnd() {

                                     print(aPrintWriter); // print it after it has been visited

                            }

                   };

         }

         return new MyMethodAdapter(mv);

}

上面的代码将会打印出使用MyMethodAdapter转换后的内容。

 

CheckClassAdapter

 

这个类在前面的章节中也介绍过,用来检查ClassVisitor方法是否按照合适的顺序来调用,如果给以合适的参数,它可以用来检查MethodVisitor中的方法。也就是说它可以用来检查MethodVisitor接口在转换链中是否被正确使用。就像TraceMethodVisitor一样,你可以选择使用CheckMethodAdapter来检查单个方法而不是所有方法:

public MethodVisitor visitMethod(int access, String name,

String desc, String signature, String[] exceptions) {

         MethodVisitor mv = cv.visitMethod(access, name, desc, signature,

         exceptions);

         if (debug && mv != null && ...) { // if this method must be checked

                   mv = new CheckMethodAdapter(mv);

         }

         return new MyMethodAdapter(mv);

}

上面的代码用来检查MyMethodAdapter是否正确使用了MethodVisitor接口。但是它不能检查字节码是否正确:如它无法检测ISTORE 1 ALOAD 1是无效的。

 

ASMifierClassVisitor

 

这个类同样在前面章节中介绍过了,它同样也可以用在方法上。它可以用来了解如何使用ASM来生成一些编译后的代码:编写对应的源代码,然后使用javac编译,在使用ASMifierClassVisitor来访问这个类即可。这样你就可以得到ASM代码来生成源码对应的字节码。

 

3.3.2 AnalyzerAdapter

 

这个方法适配器基于visitFrame访问过的帧,来对比每个指令的栈映射帧。如在3.1.5节中解释的一样,visitFrame方法只能在一个方法中的某些指令之前被调用,这样可以节省空间,并且“其它的帧可以很容易很快地从这些帧推断出来”。这就是这个章节即将做的事情。这个适配器只能工作在那些包含预先计算过的栈映射帧的类上,并且这些类需要使用java6或者更高编译器编译。

 

AddTimerAdapter示例中,这个适配器可以获得在RETURN指令之前的操作数栈的大小,从而可以在visitMaxs方法中为maxStack计算一个最佳的值(事实上,在实际操作中这个方法不推荐使用,因为它没有COMPUTE_MAXS有效):

class AddTimerMethodAdapter2 extends AnalyzerAdapter {

         private int maxStack;

         public AddTimerMethodAdapter2(String owner, int access,

                   String name, String desc, MethodVisitor mv) {

                  super(owner, access, name, desc, mv);

         }

         @Override public void visitCode() {

                   super.visitCode();

                   mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");

                   mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

                            "currentTimeMillis", "()J");

                   mv.visitInsn(LSUB);

                   mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

                   maxStack = 4;

         }

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

                            maxStack = Math.max(maxStack, stack.size() + 4);

                   }

                   super.visitInsn(opcode);

         }

         @Override public void visitMaxs(int maxStack, int maxLocals) {

                   super.visitMaxs(Math.max(this.maxStack, maxStack), maxLocals);

         }

}

其中stack字段是在AnalyzerAdapter类中定义的,包含了操作数栈中的类型。更经确定地讲,在一个visitXxxInsn方法中,并且在被重写的方法调用之前,这个列表包含了操作数栈的状态。注意,被重写的那个方法也必须被调用,这样stack字段才会被正确的更新(以后将使用super来代替原始代码中的mv

 

此外,可以通过调用父类的方法来插入新的指令:效果就是AnalyzerAdapter将会计算这些指令帧大小。因此,因为这个适配器会基于自己的计算来更新visitMaxs方法的参数,我们就不需要自己更新它们了:

class AddTimerMethodAdapter3 extends AnalyzerAdapter {

         public AddTimerMethodAdapter3(String owner, int access,

                   String name, String desc, MethodVisitor mv) {

                   super(owner, access, name, desc, mv);

         }

         @Override public void visitCode() {

                   super.visitCode();

                   super.visitFieldInsn(GETSTATIC, owner, "timer", "J");

                   super.visitMethodInsn(INVOKESTATIC, "java/lang/System",

                            "currentTimeMillis", "()J");

                   super.visitInsn(LSUB);

                   super.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

         }

         @Override public void visitInsn(int opcode) {

                   if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {

                            super.visitFieldInsn(GETSTATIC, owner, "timer", "J");

                            super.visitMethodInsn(INVOKESTATIC, "java/lang/System",

                            "currentTimeMillis", "()J");

                            super.visitInsn(LADD);

                            super.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

                   }

                   super.visitInsn(opcode);

         }

}

 

 

3.3.3 LocalVariablesSorter

 

这个方法适配器对方法中局部变量以它们出现的顺序进行重新编号。例如,一个包含两个参数的方法,第一个局部变量的索引值大于或者等于3,那么前面还应该有三个局部变量,以及两个方法参数,它们是不能改变的,因此第一个局部变量的索引值应该是3,第二个就应该是4(有疑问?),后面依次类推.这个适配器对于想在方法中插入新的变量会有帮助。如果没有这个适配器,虽然也可以实现添加新的局部变量到代码的最后,但是不幸地是,它们的编号直到visitMaxs方法的末尾才可知。

 

为了展示如何使用这个适配器,假设我们希望通过添加一个局部变量来实现AddTimerAdapter

public class C {

         public static long timer;

         public void m() throws Exception {

                   long t = System.currentTimeMillis();

                   Thread.sleep(100);

                   timer += System.currentTimeMillis() - t;

         }

}

 

可以通过继承LocalVariablesSorter,并使用其中的newLocal方法,很容易就实现上面的功能。

class AddTimerMethodAdapter4 extends LocalVariablesSorter {

         private int time;

         public AddTimerMethodAdapter4(int access, String desc,

                   MethodVisitor mv) {

                   super(access, desc, mv);

         }

         @Override public void visitCode() {

                   super.visitCode();

                   mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

                            "currentTimeMillis", "()J");

                   time = newLocal(Type.LONG_TYPE);

                   mv.visitVarInsn(LSTORE, time);

         }

         @Override public void visitInsn(int opcode) {

                   if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {

                            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

                                     "currentTimeMillis", "()J");

                            mv.visitVarInsn(LLOAD, time);

                            mv.visitInsn(LSUB);

                            mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");

                            mv.visitInsn(LADD);

                            mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

                   }

                   super.visitInsn(opcode);

         }

         @Override public void visitMaxs(int maxStack, int maxLocals) {

                   super.visitMaxs(maxStack + 4, maxLocals);

         }

}

 

注意,当局部变量被重新编号以后,之前与方法关联的帧就变为无效了,更不用说插入新的局部变量了。让人感到有希望的是,我们可以避免从头计算这些帧:事实上没有帧被删除或者被添加,只需要对原始帧中的局部变量进行重排序就能获得转换后的帧了。LocalVariablesSorter将会自动进行这些操作。如果你需要对栈映射帧进行增量更新,你可以从这个类的源码中获取灵感。

 

如你所见,使用一个局部变量并不能解决在之前版本中遇到的问题,就是计算maxStack在最坏情况下的值。如果你希望使用AnalyzerAdapter来解决这个问题,那么除了LocalVariablesSorter,你必须通过委托的方式来使用这些适配器而不是继承(因为多继承是不可能的):

 

class AddTimerMethodAdapter5 extends MethodAdapter {

         public LocalVariablesSorter lvs;

         public AnalyzerAdapter aa;

         private int time;

         private int maxStack;

         public AddTimerMethodAdapter5(MethodVisitor mv) {

                   super(mv);

         }

         @Override public void visitCode() {

                   mv.visitCode();

                   mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

                            "currentTimeMillis", "()J");

                   time = lvs.newLocal(Type.LONG_TYPE);

                   mv.visitVarInsn(LSTORE, time);

                  maxStack = 4;

         }

         @Override public void visitInsn(int opcode) {

                   if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {

                            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

                                     "currentTimeMillis", "()J");

                            mv.visitVarInsn(LLOAD, time);

                            mv.visitInsn(LSUB);

                            mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");

                            mv.visitInsn(LADD);

                            mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

                            maxStack = Math.max(aa.stack.size() + 4, maxStack);

                   }

                   mv.visitInsn(opcode);

         }

         @Override public void visitMaxs(int maxStack, int maxLocals) {

                   mv.visitMaxs(Math.max(this.maxStack, maxStack), maxLocals);

         }

}

 

为了使用上面的适配器,你必要将LocalVariablesSorterAnalyzerAdapter链接起来,然后链接你的适配器:第一个适配器将对局部变量进行排序并更新帧,AnalyzerAdapter适配器将根据前一步骤中执行结果计算中间帧,这样你的适配器就可以访问那些已经重编号的帧了。这个链可以在visitMethod方法中如下构造:

mv = cv.visitMethod(access, name, desc, signature, exceptions);

if (!isInterface && mv != null && !name.equals("<init>")) {

         AddTimerMethodAdapter5 at = new AddTimerMethodAdapter5(mv);

         at.aa = new AnalyzerAdapter(owner, access, name, desc, at);

         at.lvs = new LocalVariablesSorter(access, desc, at.aa);

         return at.lvs;

}

 

 

3.3.4 AdviceAdapter

 

这个方法适配器是一个抽象的类,可以用来在方法的开始插入代码,也可以在RETURNATHROW指令之前。它最大的优势就是可以适用于构造方法,在构造方法中,代码不能直接插入到方法的开始,而是插入到调用父类构造方法之后。事实上,这个适配器的大部分代码都是用来检测调用父类的构造方法的。

 

如果你仔细的查看3.2.4章节中的AddTimerAdapter类,你会发现AddTimerMethodAdapter适配器没有用在构造方法上,就是因为这个问题。通过继承AdviceAdapter这个适配器,可以让这个适配器也能够很好的工作在构造方法上(注意,AdviceAdapter是继承自LocalVariablesSorter,因此我们也可以很容易地使用一个局部变量):

class AddTimerMethodAdapter6 extends AdviceAdapter {

         public AddTimerMethodAdapter6(int access, String name, String desc,

                   MethodVisitor mv) {

                   super(mv, access, name, desc);

         }

         @Override protected void onMethodEnter() {

                   mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");

                   mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

                            "currentTimeMillis", "()J");

                   mv.visitInsn(LSUB);

                   mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

         }

         @Override protected void onMethodExit(int opcode) {

                   mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");

                   mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

                            "currentTimeMillis", "()J");

                   mv.visitInsn(LADD);

                   mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

         }

         @Override public void visitMaxs(int maxStack, int maxLocals) {

                   super.visitMaxs(maxStack + 4, maxLocals);

         }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值