ASM 关键接口 MethodVisitor

举报
JavaEdge 发表于 2021/06/03 23:34:14 2021/06/03
【摘要】 Label label = new Label() 这个语句中,label的作用是为了条件跳转,其实也可以理解成字节码指令的参数。 所以label必须对应一条字节码指令,通过visitLabel(label)来调用,并且visitLabel的调用必须紧跟随着label对象指定的指令。 如例子中,第一个label指向goto后,所以顺序必须是:mv.visitJumpIn...

Label label = new Label()
这个语句中,label的作用是为了条件跳转,其实也可以理解成字节码指令的参数。
所以label必须对应一条字节码指令,通过visitLabel(label)来调用,并且visitLabel的调用必须紧跟随着label对象指定的指令。
如例子中,第一个label指向goto后,所以顺序必须是:mv.visitJumpInsn(Opcodes.GOTO, end);

当ASM的ClassReader读取到Method时就转入MethodVisitor接口处理。
方法的定义,以及方法中指令的定义都会通过MethodVisitor接口通知给程序。我们假设有下面这样的一个类:

下面是这个MethodVisitor接口的所有方法定义。本文只会介绍主要的方法,因此不会逐个对方法做依次介绍:

这些方法必须按照以下顺序调用(和MethodVisitor接口在Javadoc中指定的一些额外约束):

visitAnnotationDefault?
( visitAnnotation | visitParameterAnnotation | visitAttribute )\*
( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxx Insn | visitLocalVariable | visitLineNumber ) \*
visitMaxs )?
visitEnd

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这意味着,如有注释和属性的话,则必须先访问,后面是非抽象方法的字节码。
对于这些方法,这些代码必须按顺序访问,在唯一一个‘visitCode’方法调用和唯一一个‘visitMaxs’方法调用之间。

该接口的方法数量如此之多,甚至是ClassVisitor接口的3倍以上。但是值得关心的接口只有下面这几个,其余的都是和代码有关系:

visitCode

ASM开始扫描这个方法。

visitMaxs(maxStack, maxLocals);

该方法是visitEnd之前调用的方法,可以反复调用。
用以确定类方法在执行时候的堆栈大小。

visitEnd();

表示方法输出完毕

visitCode 和 visitMaxs 方法可用于检测该方法的字节代码在一个事件序列中的
开始与结束。和类的情况一样,visitEnd 方法也必须在最后调用,用于检测一个方法在一个事件序列中的结束。

visitMethodInsn

  /** * 访问方法的指令。 方法指令是调用方法的指令。 * * @param opcode 要访问的类型指令的操作码。可以是INVOKEVIRTUAL,INVOKESPECIAL,INVOKESTATIC或INVOKEINTERFACE。 * @param owner 方法的所有者类的内部名称 (see {@link * Type#getInternalName()}). * @param name 方法名 * @param descriptor the method's descriptor (see {@link Type}). * @param isInterface if the method's owner class is an interface. */
  public void visitMethodInsn( final int opcode, final String owner, final String name, final String descriptor, final boolean isInterface) { if (api < Opcodes.ASM5 && (opcode & Opcodes.SOURCE_DEPRECATED) == 0) { if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) { throw new UnsupportedOperationException("INVOKESPECIAL/STATIC on interfaces requires ASM5"); } visitMethodInsn(opcode, owner, name, descriptor); return; } if (mv != null) { mv.visitMethodInsn(opcode & ~Opcodes.SOURCE_MASK, owner, name, descriptor, isInterface); }
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

visitVarInsn

访问局部变量指令。 局部变量指令是加载loads或存储stores局部变量值的指令。

 /** * @param opcode 要访问的局部变量指令的操作码。 该操作码是 * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. * @param var 要访问的指令的操作数。该操作数是局部变量的索引。 */
  public void visitVarInsn(final int opcode, final int var) { if (mv != null) { mv.visitVarInsn(opcode, var); }
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

需要注意的是,没有必要为了开始访问另外一个方法,而结束当前访问的方法。
实际上,‘MethodVisitor’实例间是完全独立的,可以用任何顺序调用(但必须在‘cv.visitEnd()’调用之前使用):

ASM提供了三个基于MethodVisitor API的核心组件,用于生成和转换方法:

ClassReader类解析一个编译后的方法,并且通过传递ClassVisitor作为accept方法的参数获得的返回,调用MethodVisitor’相应的方法。
ClassWriter的‘visitMethod’返回了MethodVisitor抽象类的一个实现,该实现可以直接用二进制的方式构建编译后的方法。
MethodVisitor类可以传递所有调用它的方法给另一个MethodVisitor类。MethodVisitor类可以看作一个事件过滤器。

实现类 - MethodWriter

生成相应的“ method_info”结构的MethodVisitor,如Java虚拟机规范(JVMS)中所定义。

visitMaxs

  @Override
  public void visitMaxs(final int maxStack, final int maxLocals) { if (compute == COMPUTE_ALL_FRAMES) { computeAllFrames(); } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { computeMaxStackAndLocal(); } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { this.maxStack = maxRelativeStackSize; } else { this.maxStack = maxStack; this.maxLocals = maxLocals; }
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

AnalyzerAdapter实现类

一个MethodVisitor,用于跟踪visitFrame调用之间的 stack map frame 更改。 该适配器必须与ClassReader.EXPAND_FRAMES选项一起使用。 每个visitX指令都将委托给链中的下一个访问者(如果有),然后模拟该指令对 stack map frame(由局部变量和堆栈表示)的影响。 链中的下一个访问者可以通过读取其visitX方法中的这些字段的值来获取每条指令之前的 stack map frame 的状态(这需要引用链中位于其之前的AnalyzerAdapter)。 如果此适配器与不包含堆栈映射表属性的类一起使用(即Java 6之前的类),则此适配器可能无法为每条指令计算堆栈映射框架。 在这种情况下,不会抛出异常,但是对于这些指令,locals和stack字段将为null。

这个方法适配器会根据 visitFrame 方法中被访问的帧,计算出每一个指令之前的栈哈希帧。

为了节省空间,visitFrame仅仅会在一个方法中某些特定的指令前调用,并且“其他的帧也可以从这些帧简单容易的推算出来”。这就是AnalyzerAdapter的作用。

当然在它仅能作用在包含了预先计算过栈哈希帧的编译类,即使用Java 6或者更改版本编译的类(或者像之前的示例一样,使用含有COMPUTE_FRAMES参数的ASM adapter将类升级到Java 6):

‘stack’属性在AnalyzerAdapter类中有定义,并且包含了在操作栈中的类型。
更确切地说,对于一个‘visitXxx Insn’指令,在覆盖方法被调用前,这个列表包含了在该条指令前操作栈的状态。
需要注意的是覆盖方法必须被调用,这样栈里的属性才能正确地更新(因此使用父类的原始方法,而不是mv的方法)。
另外,调用父类的方法也可以插入新指令:效果是AyalyzerAdapter会计算出这些指令对应的帧。
因此,该适配器会基于它计算出的帧更新visitMaxs方法的参数,我们就不必更新这些参数了:

文章来源: javaedge.blog.csdn.net,作者:JavaEdge.,版权归原作者所有,如需转载,请联系作者。

原文链接:javaedge.blog.csdn.net/article/details/105273233

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。