ASM 关键接口 MethodVisitor

简介: ASM 关键接口 MethodVisitor

Label label = new Label()

这个语句中,label的作用是为了条件跳转,其实也可以理解成字节码指令的参数。

所以label必须对应一条字节码指令,通过visitLabel(label)来调用,并且visitLabel的调用必须紧跟随着label对象指定的指令。

如例子中,第一个label指向goto后,所以顺序必须是:mv.visitJumpInsn(Opcodes.GOTO, end);


当ASM的ClassReader读取到Method时就转入MethodVisitor接口处理。

方法的定义,以及方法中指令的定义都会通过MethodVisitor接口通知给程序。我们假设有下面这样的一个类:


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

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

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

  /**
   * 访问方法的指令。 方法指令是调用方法的指令。
   *
   * @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局部变量值的指令。

 /**
   * @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

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


目录
相关文章
|
存储 Java 计算机视觉
ASM - TreeApi Method组件和接口简介
ASM - TreeApi Method组件和接口简介
227 0
|
Java Spring SQL
ASM + 接口 动态生成类
好吧 ,我承认上篇文章 确认让人看不明白ASM到底有什么用,那么这篇来举个例子吧 ,比如 我制定任意的接口(接口名称,接口方法都是随意的) package com.
808 0
|
7月前
|
Oracle 关系型数据库
oracle asm 磁盘显示offline
oracle asm 磁盘显示offline
355 2
|
2月前
|
存储 Oracle 关系型数据库
数据库数据恢复—Oracle ASM磁盘组故障数据恢复案例
Oracle数据库数据恢复环境&故障: Oracle ASM磁盘组由4块磁盘组成。Oracle ASM磁盘组掉线 ,ASM实例不能mount。 Oracle数据库故障分析&恢复方案: 数据库数据恢复工程师对组成ASM磁盘组的磁盘进行分析。对ASM元数据进行分析发现ASM存储元数据损坏,导致磁盘组无法挂载。
|
7月前
|
存储 Oracle 关系型数据库
【数据库数据恢复】Oracle数据库ASM磁盘组掉线的数据恢复案例
oracle数据库ASM磁盘组掉线,ASM实例不能挂载。数据库管理员尝试修复数据库,但是没有成功。
【数据库数据恢复】Oracle数据库ASM磁盘组掉线的数据恢复案例
|
SQL Oracle 关系型数据库
Oracle ASM磁盘和磁盘组的常用SQL语句
Oracle ASM磁盘和磁盘组的常用SQL语句
289 0
|
文字识别 Oracle NoSQL
oracle 11g 单机asm配置
oracle 11g 单机asm配置
675 0
|
Oracle 关系型数据库
❤️Oracle ASM加磁盘及剔盘操作❤️
❤️Oracle ASM加磁盘及剔盘操作❤️
342 0
|
存储 机器学习/深度学习 Oracle
Oracle 11gR2 ASM存储日常管理手册
Oracle 11gR2 ASM存储日常管理手册 目 录1 文档简介 31.1 编写目的 31.2 适用范围 31.3 名词解释 31.4 格式约定 42 配置多链路和LUN 42.
1148 0
|
Oracle 关系型数据库
oracle Grid 是如何找到voteidks和asm spfile.
oracle Grid 是如何找到voteidks和asm spfile.
1547 0