ASM系列学习笔记

2023-04-20  本文已影响0人  丹丹无敌

ASM的版本发展

ASM Release Release Date Java Support
2.0 2005-05-17 Java 5 language support
3.2 2009-06-11 support for the new invokedynamic code.
4.0 2011-10-29 Java 7 language support
5.0 2014-03-16 Java 8 language support
6.0 2017-09-23 Java 9 language support
6.1 2018-03-11 Java 10 language support
7.0 2018-10-27 Java 11 language support
7.1 2019-03-03 Java 13 language support
8.0 2020-03-28 Java 14 language support
9.0 2020-09-22 Java 16 language support
9.1 2021-02-06 Java 17 language support

ASM最重要的三个类关系

ClassReader、ClassVisitor和ClassWriter类。这三个类的关系,可以描述成下图:


image.png

这三个类的作用,可以简单理解成这样:

Java ClassFile

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

在.class文件当中,定义的字段,要遵循field_info的结构。

field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

同样的,在.class文件当中,定义的方法,要遵循method_info的结构。

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

在method_info结构中,方法当中方法体的代码,是存在于Code属性结构中,其结构如下:

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

ASM生成新的类学习

ClassVisitor类

ClassVisitor是一个抽象类,要想使用它,就必须有具体的子类来继承它。比较常见的ClassVisitor子类有ClassWriter类(Core API)和ClassNode类(Tree API)。

三个类关系如下:

public abstract class ClassVisitor {
    protected final int api;
    protected ClassVisitor cv;
}
ClassVisitor类当中,这些visitXxx()方法,遵循一定的调用顺序。这个调用顺序如下:
visit
[visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
(
 visitAnnotation |
 visitTypeAnnotation |
 visitAttribute
)*
(
 visitNestMember |
 visitInnerClass |
 visitRecordComponent |
 visitField |
 visitMethod
)* 
visitEnd

其中,涉及到一些符号,它们的含义如下:

public abstract class ClassVisitor {
    public void visit(
        final int version,
        final int access,
        final String name,
        final String signature,
        final String superName,
        final String[] interfaces);
    public FieldVisitor visitField( // 访问字段
        final int access,
        final String name,
        final String descriptor,
        final String signature,
        final Object value);
    public MethodVisitor visitMethod( // 访问方法
        final int access,
        final String name,
        final String descriptor,
        final String signature,
        final String[] exceptions);
    public void visitEnd();
    // ......
}

在ClassVisitor类当中,有许多visitXxx()方法,但是,我们只需要关注这4个方法:visit()、visitField()、visitMethod()和visitEnd()。

在ClassVisitor的visit()方法、visitField()方法和visitMethod()方法中都带有signature参数。这个signature参数“泛型”密切相关;换句话说,如果处理的是一个带有泛型信息的类、字段或方法,那么就需要给signature参数提供一定的值;如果处理的类、字段或方法不带有“泛型”信息,那么将signature参数设置为null就可以了。

visit(version, access, name, signature, superName, interfaces)方法的各个参数:
visitField()和visitMethod()方法的各个参数:

这两个方法的前4个参数是相同的,不同的地方只在于第5个参数。

public class HelloWorld {
    // 这是一个常量字段,使用static、final关键字修饰
    public static final int constant_field = 10;
    // 这是一个非常量字段
    public int non_constant_field;

    public void test() throws FileNotFoundException, IOException {
        // do nothing
    }
}

对于上面的代码,

在ClassFile当中,描述符(descriptor)是对“类型”的简单化描述。

Java类型 ClassFile描述符
boolean Z(Z表示Zero,零表示'false',非零表示'true')
byte B
char C
double D
float F
int I
long J
short S
void V
non-array reference L<InternalName>;
array reference [

对字段描述符的举例:

对方法描述符的举例:

<init>()和<clinit>()方法

对于一个类(Class)来说,如果没有提供任何构造方法,Java编译器会自动生成一个默认构造方法。在所有的.class文件中,构造方法的名字是<init>()。

另外,如果在.class文件中包含静态代码块,那么就会有一个<clinit>()方法。

public class HelloWorld {
    static {
        System.out.println("static code block");
    }
}

上面的静态代码码,对应于visitMethod(ACC_STATIC, "<clinit>", "()V", null, null)的调用。

FieldVisitor类

FieldVisitor类内定义的多个visitXxx()方法,也需要遵循一定的调用顺序,如下所示:

(
 visitAnnotation |
 visitTypeAnnotation |
 visitAttribute
)*
visitEnd

FieldWriter类

FieldWriter类的父类是FieldVisitor类。需要注意的是,FieldWriter类并不带有public修饰,因此它的有效访问范围只局限于它所处的package当中,不能像其它的public类一样被外部所使用。

final class FieldWriter extends FieldVisitor {
}

MethodVisitor类

在MethodVisitor类当中,定义了许多的visitXxx()方法,这些方法的调用,也要遵循一定的顺序。

(visitParameter)*
[visitAnnotationDefault]
(visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
[
    visitCode
    (
        visitFrame |
        visitXxxInsn |
        visitLabel |
        visitInsnAnnotation |
        visitTryCatchBlock |
        visitTryCatchAnnotation |
        visitLocalVariable |
        visitLocalVariableAnnotation |
        visitLineNumber
    )*
    visitMaxs
]
visitEnd

我们可以把这些visitXxx()方法分成三组:

对这些visitXxx()方法进行精简之后,内容如下:

[
    visitCode
    (
        visitFrame |
        visitXxxInsn |
        visitLabel |
        visitTryCatchBlock
    )*
    visitMaxs
]
visitEnd

这些方法的调用顺序,可以记忆如下:

需要注意的一点,ClassVisitor类有自己的visitXxx()方法,MethodVisitor类也有自己的visitXxx()方法,两者是不一样的,要注意区分。另外,ClassVisitor.visitMethod()方法提供的是“方法声明”所需要的信息,它会返回一个MethodVisitor对象,这个MethodVisitor对象就用来实现“方法体”里面的代码逻辑。

MethodWriter类

MethodWriter类的父类是MethodVisitor类。需要注意的是,MethodWriter类并不带有public修饰,因此它的有效访问范围只局限于它所处的package当中,不能像其它的public类一样被外部所使用。

final class MethodWriter extends MethodVisitor {
}

方法的初始Frame

在方法刚开始的时候,operand stack是空,不需要存储任何的数据,而local variables的初始状态,则需要考虑三个因素:

static方法

假设HelloWorld当中有一个静态add(int, int)方法,如下所示:

public class HelloWorld {
    public static int add(int a, int b) {
        return a + b;
    }
}

我们可以通过运行HelloWorldFrameCore类,来查看add(int, int)方法的初始Frame:

[int, int] []

在上面的结果中,第一个[]中存放的是local variables的数据,在第二个[]中存放的是operand stack的数据。

该方法包含的Instruction内容如下(使用javap -c HelloWorld命令查看):

public static int add(int, int);
  Code:
     0: iload_0
     1: iload_1
     2: iadd
     3: ireturn

该方法整体的Frame变化如下:

add(II)I
[int, int] []
[int, int] [int]
[int, int] [int, int]
[int, int] [int]
[] []

non-static方法

假设HelloWorld当中有一个非静态add(int, int)方法,如下所示:

public class HelloWorld {
    public int add(int a, int b) {
        return a + b;
    }
}

我们可以通过运行HelloWorldFrameCore类,来查看add(int, int)方法的初始Frame:

[***/HelloWorld, int, int] []

该方法包含的Instruction内容如下:

public int add(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: iadd
     3: ireturn

该方法整体的Frame变化如下:

add(II)I
[sample/HelloWorld, int, int] []
[sample/HelloWorld, int, int] [int]
[sample/HelloWorld, int, int] [int, int]
[sample/HelloWorld, int, int] [int]
[] []

long和double类型

假设HelloWorld当中有一个非静态add(long, long)方法,如下所示:

public class HelloWorld {
    public long add(long a, long b) {
        return a + b;
    }
}

我们可以通过运行HelloWorldFrameCore类,来查看add(long, long)方法的初始Frame:

[sample/HelloWorld, long, top, long, top] []

该方法包含的Instruction内容如下:

public long add(long, long);
  Code:
     0: lload_1
     1: lload_3
     2: ladd
     3: lreturn

该方法整体的Frame变化如下:

add(JJ)J
[sample/HelloWorld, long, top, long, top] []
[sample/HelloWorld, long, top, long, top] [long, top]
[sample/HelloWorld, long, top, long, top] [long, top, long, top]
[sample/HelloWorld, long, top, long, top] [long, top]
[] []

方法初始的Frame总结:

上一篇 下一篇

猜你喜欢

热点阅读