前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ASM 关键接口 MethodVisitor

ASM 关键接口 MethodVisitor

作者头像
JavaEdge
发布2020-05-26 17:01:41
3.9K1
发布2020-05-26 17:01:41
举报
文章被收录于专栏:JavaEdgeJavaEdge

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中指定的一些额外约束):

代码语言:javascript
复制
visitAnnotationDefault?
( visitAnnotation | visitParameterAnnotation | visitAttribute )\*
( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxx Insn | visitLocalVariable | visitLineNumber ) \*
visitMaxs )?
visitEnd

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

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

visitCode

ASM开始扫描这个方法。

visitMaxs(maxStack, maxLocals);

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

visitEnd();

表示方法输出完毕

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

visitMethodInsn

代码语言:javascript
复制
  /**
   * 访问方法的指令。 方法指令是调用方法的指令。
   *
   * @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);
    }
  }

visitVarInsn

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

代码语言:javascript
复制
 /**
   * @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);
    }
  }

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

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

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

实现类 - MethodWriter

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

visitMaxs

代码语言:javascript
复制
  @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;
    }
  }

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方法的参数,我们就不必更新这些参数了:

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • visitCode
  • visitMaxs(maxStack, maxLocals);
  • visitEnd();
  • visitMethodInsn
  • visitVarInsn
  • ASM提供了三个基于MethodVisitor API的核心组件,用于生成和转换方法:
  • 实现类 - MethodWriter
    • visitMaxs
    • AnalyzerAdapter实现类
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档