• ASM:(8)MethodVisitor和MethodWriter


    原文:

    https://lsieun.github.io/java-asm-01/method-visitor-intro.html

    https://lsieun.github.io/java-asm-01/method-visitor-examples.html

    MethodVisitor介绍

    通过调用ClassVisitor类的visitMethod()方法,会返回一个MethodVisitor类型的对象。

    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions);
    

    在本文当中,我们将对MethodVisitor类进行介绍。

    从类的结构来说,MethodVisitor类与ClassVisitor类和FieldVisitor类是非常相似性的。

    class info

    第一个部分,MethodVisitor类是一个abstract类。

    public abstract class MethodVisitor {
    }
    

    fields

    第二个部分,MethodVisitor类定义的字段有哪些。

    public abstract class MethodVisitor {
        protected final int api;
        protected MethodVisitor mv;
    }
    

    constructors

    第三个部分,MethodVisitor类定义的构造方法有哪些。

    public abstract class MethodVisitor {
        public MethodVisitor(final int api) {
            this(api, null);
        }
    
        public MethodVisitor(final int api, final MethodVisitor methodVisitor) {
            this.api = api;
            this.mv = methodVisitor;
        }
    }
    

    methods

    第四个部分,MethodVisitor类定义的方法有哪些。在MethodVisitor类当中,定义了许多的visitXxx()方法,我们列出了其中的一些方法,内容如下:

    public abstract class MethodVisitor {
        public void visitCode();
    
        public void visitInsn(final int opcode);
        public void visitIntInsn(final int opcode, final int operand);
        public void visitVarInsn(final int opcode, final int var);
        public void visitTypeInsn(final int opcode, final String type);
        public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor);
        public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor,
                                    final boolean isInterface);
        public void visitInvokeDynamicInsn(final String name, final String descriptor, final Handle bootstrapMethodHandle,
                                           final Object... bootstrapMethodArguments);
        public void visitJumpInsn(final int opcode, final Label label);
        public void visitLabel(final Label label);
        public void visitLdcInsn(final Object value);
        public void visitIincInsn(final int var, final int increment);
        public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels);
        public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels);
        public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions);
    
        public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type);
    
        public void visitMaxs(final int maxStack, final int maxLocals);
        public void visitEnd();
    
        // ......
    }
    

    对于这些visitXxx()方法,它们分别有什么作用呢?我们有三方面的资料可能参阅:

    方法的调用顺序

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

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

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

    • 第一组,在visitCode()方法之前的方法。这一组的方法,主要负责parameter、annotation和attributes等内容,这些内容并不是方法当中“必不可少”的一部分;我们暂时不去考虑这些内容,可以忽略这一组方法。
    • 第二组,在visitCode()方法和visitMaxs()方法之间的方法。这一组的方法,主要负责当前方法的“方法体”内的opcode内容。其中,visitCode()方法,标志着方法体的开始,而visitMaxs()方法,标志着方法体的结束。
    • 第三组,是visitEnd()方法。这个visitEnd()方法,是最后一个进行调用的方法。

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

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

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

    • 第一步,调用visitCode()方法,调用一次。
    • 第二步,调用visitXxxInsn()方法,可以调用多次。对这些方法的调用,就是在构建方法的“方法体”。
    • 第三步,调用visitMaxs()方法,调用一次。
    • 第四步,调用visitEnd()方法,调用一次。

    MethodWriter介绍

    MethodWriter类的父类是MethodVisitor类。在ClassWriter类里,visitMethod()方法的实现就是通过MethodWriter类来实现的。

    class info

    第一个部分,MethodWriter类的父类是MethodVisitor类。

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

    final class MethodWriter extends MethodVisitor {
    }
    

    fields

    第二个部分,MethodWriter类定义的字段有哪些。

    MethodWriter类当中,定义了很多的字段。下面的几个字段,是与方法的访问标识(access flag)、方法名(method name)和描述符(method descriptor)等直接相关的字段:

    final class MethodWriter extends MethodVisitor {
        private final int accessFlags;
        private final int nameIndex;
        private final String name;
        private final int descriptorIndex;
        private final String descriptor;
        private Attribute firstAttribute;
    }
    

    这些字段与ClassFile当中的method_info也是对应的:

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

    下面的几个字段,是与“方法体”直接相关的几个字段:

    final class MethodWriter extends MethodVisitor {
        private int maxStack;
        private int maxLocals;
        private final ByteVector code = new ByteVector();
        private Handler firstHandler;
        private Handler lastHandler;
        private final int numberOfExceptions;
        private final int[] exceptionIndexTable;
        private Attribute firstCodeAttribute;
    }
    

    这些字段对应于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];
    }
    

    constructors

    第三个部分,MethodWriter类定义的构造方法有哪些。

    final class MethodWriter extends MethodVisitor {
        MethodWriter(SymbolTable symbolTable, int access, String name, String descriptor, String signature, String[] exceptions, int compute) {
            super(Opcodes.ASM9);
            this.symbolTable = symbolTable;
            this.accessFlags = "<init>".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access;
            this.nameIndex = symbolTable.addConstantUtf8(name);
            this.name = name;
            this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
            this.descriptor = descriptor;
            this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature);
            if (exceptions != null && exceptions.length > 0) {
                numberOfExceptions = exceptions.length;
                this.exceptionIndexTable = new int[numberOfExceptions];
                for (int i = 0; i < numberOfExceptions; ++i) {
                    this.exceptionIndexTable[i] = symbolTable.addConstantClass(exceptions[i]).index;
                }
            } else {
                numberOfExceptions = 0;
                this.exceptionIndexTable = null;
            }
            this.compute = compute;
            if (compute != COMPUTE_NOTHING) {
                // Update maxLocals and currentLocals.
                int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
                if ((access & Opcodes.ACC_STATIC) != 0) {
                    --argumentsSize;
                }
                maxLocals = argumentsSize;
                currentLocals = argumentsSize;
                // Create and visit the label for the first basic block.
                firstBasicBlock = new Label();
                visitLabel(firstBasicBlock);
            }
        }
    }
    

    methods

    第四个部分,MethodWriter类定义的方法有哪些。

    MethodWriter类当中,也有两个重要的方法:computeMethodInfoSize()putMethodInfo()方法。这两个方法也是在ClassWriter类的toByteArray()方法内使用到。

    final class MethodWriter extends MethodVisitor {
        int computeMethodInfoSize() {
            // ......
            // 2 bytes each for access_flags, name_index, descriptor_index and attributes_count.
            int size = 8;
            // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
            if (code.length > 0) {
                if (code.length > 65535) {
                    throw new MethodTooLargeException(symbolTable.getClassName(), name, descriptor, code.length);
                }
                symbolTable.addConstantUtf8(Constants.CODE);
                // The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack,
                // max_locals, code_length and attributes_count, plus the ByteCode and the exception table.
                size += 16 + code.length + Handler.getExceptionTableSize(firstHandler);
                if (stackMapTableEntries != null) {
                    boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
                    symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap");
                    // 6 header bytes and 2 bytes for number_of_entries.
                    size += 8 + stackMapTableEntries.length;
                }
                // ......
            }
            if (numberOfExceptions > 0) {
                symbolTable.addConstantUtf8(Constants.EXCEPTIONS);
                size += 8 + 2 * numberOfExceptions;
            }
            //......
            return size;
        }
    
        void putMethodInfo(final ByteVector output) {
            boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
            int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
            output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
            // ......
            // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
            int attributeCount = 0;
            if (code.length > 0) {
                ++attributeCount;
            }
            if (numberOfExceptions > 0) {
                ++attributeCount;
            }
            // ......
            // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
            output.putShort(attributeCount);
            if (code.length > 0) {
                // 2, 2, 4 and 2 bytes respectively for max_stack, max_locals, code_length and
                // attributes_count, plus the ByteCode and the exception table.
                int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler);
                int codeAttributeCount = 0;
                if (stackMapTableEntries != null) {
                    // 6 header bytes and 2 bytes for number_of_entries.
                    size += 8 + stackMapTableEntries.length;
                    ++codeAttributeCount;
                }
                // ......
                output
                    .putShort(symbolTable.addConstantUtf8(Constants.CODE))
                    .putInt(size)
                    .putShort(maxStack)
                    .putShort(maxLocals)
                    .putInt(code.length)
                    .putByteArray(code.data, 0, code.length);
                Handler.putExceptionTable(firstHandler, output);
                output.putShort(codeAttributeCount);
                // ......
            }
            if (numberOfExceptions > 0) {
                output
                    .putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS))
                    .putInt(2 + 2 * numberOfExceptions)
                    .putShort(numberOfExceptions);
                for (int exceptionIndex : exceptionIndexTable) {
                  output.putShort(exceptionIndex);
                }
            }
            // ......
        }
    }
    

    MethodWriter类的使用

    关于MethodWriter类的使用,它主要出现在ClassWriter类当中的visitMethod()toByteArray()方法内。

    visitMethod方法

    ClassWriter类当中,visitMethod()方法代码如下:

    public class ClassWriter extends ClassVisitor {
        public final MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            MethodWriter methodWriter = new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute);
            if (firstMethod == null) {
                firstMethod = methodWriter;
            } else {
                lastMethod.mv = methodWriter;
            }
            return lastMethod = methodWriter;
        }
    }
    

    toByteArray方法

    public class ClassWriter extends ClassVisitor {
        public byte[] toByteArray() {
    
            // First step: compute the size in bytes of the ClassFile structure.
            // The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version,
            // constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count,
            // methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too.
            int size = 24 + 2 * interfaceCount;
            // ......
            int methodsCount = 0;
            MethodWriter methodWriter = firstMethod;
            while (methodWriter != null) {
                ++methodsCount;
                size += methodWriter.computeMethodInfoSize();        // 这里是对MethodWriter.computeMethodInfoSize()方法的调用
                methodWriter = (MethodWriter) methodWriter.mv;
            }
            // ......
    
            // Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in
            // dynamic resizes) and fill it with the ClassFile content.
            ByteVector result = new ByteVector(size);
            result.putInt(0xCAFEBABE).putInt(version);
            symbolTable.putConstantPool(result);
            int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0;
            result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass);
            result.putShort(interfaceCount);
            for (int i = 0; i < interfaceCount; ++i) {
                result.putShort(interfaces[i]);
            }
            // ......
            result.putShort(methodsCount);
            boolean hasFrames = false;
            boolean hasAsmInstructions = false;
            methodWriter = firstMethod;
            while (methodWriter != null) {
                hasFrames |= methodWriter.hasFrames();
                hasAsmInstructions |= methodWriter.hasAsmInstructions();
                methodWriter.putMethodInfo(result);                    // 这里是对MethodWriter.putMethodInfo()方法的调用
                methodWriter = (MethodWriter) methodWriter.mv;
            }
            // ......
    
            // Third step: replace the ASM specific instructions, if any.
            if (hasAsmInstructions) {
                return replaceAsmInstructions(result.data, hasFrames);
            } else {
                return result.data;
            }
        }
    }
    

    总结

    本文主要对MethodWriter类进行介绍,内容总结如下:

    • 第一点,对于MethodWriter类的各个不同部分进行介绍,以便从整体上来理解MethodWriter类。
    • 第二点,关于MethodWriter类的使用,它主要出现在ClassWriter类当中的visitMethod()toByteArray()方法内。
    • 第三点,从应用ASM的角度来说,只需要知道MethodWriter类的存在就可以了,不需要深究;从理解ASM源码的角度来说,MethodWriter类也是值得研究的。

    MethodVisitor代码示例

    示例一:<init>()方法

    .class文件中,构造方法的名字是<init>,它表示instance initialization method的缩写。

    预期目标

    public class HelloWorld {
    }
    

    或者:

    public class HelloWorld {
        public HelloWorld() {
            super();
        }
    }
    

    编码实现

    public class MethodVisitorTest implements Opcodes {
    
        public static void main(String[] args) throws Exception {
            // (1) 生成byte[]内容
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
        }
    
        public static byte[] dump() throws Exception {
            // (1) 创建ClassWriter对象
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            // (2) 调用visitXxx()方法
            cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
            {
                MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
                mv1.visitCode();
                mv1.visitVarInsn(ALOAD, 0);
                mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
                mv1.visitInsn(RETURN);
                mv1.visitMaxs(1, 1);
                mv1.visitEnd();
            }
            cw.visitEnd();
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    
    }
    

    Frame的变化

    对于HelloWorld类中<init>()方法对应的Instruction内容如下:

    $ javap -c sample.HelloWorld
    public sample.HelloWorld();
      Code:
         0: aload_0
         1: invokespecial #9                  // Method java/lang/Object."<init>":()V
         4: return
    

    该方法对应的Frame变化情况如下:

    <init>()V
    [uninitialized_this] []
    [uninitialized_this] [uninitialized_this]
    [sample/HelloWorld] []
    [] []
    

    在这里,我们看到一个很“不一样”的变量,就是uninitialized_this,它就是一个“引用”,它指向的内存空间还没有初始化;等经过初始化之后,uninitialized_this变量就变成this变量。

    小总结

    通过上面的示例,我们注意四个知识点:

    • 第一点,如何使用ClassWriter

      类。

      • 第一步,创建ClassWriter类的实例。
      • 第二步,调用ClassWriter类的visitXxx()方法。
      • 第三步,调用ClassWriter类的toByteArray()方法。
    • 第二点,在使用MethodVisitor类时,其中visitXxx()方法需要遵循的调用顺序。

      • 第一步,调用visitCode()方法,调用一次
      • 第二步,调用visitXxxInsn()方法,可以调用多次
      • 第三步,调用visitMaxs()方法,调用一次
      • 第四步,调用visitEnd()方法,调用一次
    • 第三点,在.class文件中,构造方法的名字是<init>。从Instruction的角度来讲,调用构造方法会用到invokespecial指令。

    • 第四点,从Frame的角度来讲,在构造方法<init>()中,local variables当中索引为0的位置存储的是什么呢?如果还没有进行初始化操作,就是uninitialized_this变量;如果已经进行了初始化操作,就是this变量。

                         ┌─── static method ─────┼─── invokestatic
                         │
                         │                       ┌─── invokevirtual (class)
                         │                       │
                         │                       │                                   ┌─── constructor
                         │                       │                                   │
    method invocation ───┼─── instance method ───┼─── invokespecial (class) ─────────┼─── private
                         │                       │                                   │
                         │                       │                                   └─── super
                         │                       │
                         │                       └─── invokeinterface (interface)
                         │
                         └─── dynamic method ────┼─── invokedynamic
    

    示例二:<clinit>方法

    .class文件中,静态初始化方法的名字是<clinit>,它表示class initialization method的缩写。

    预期目标

    public class HelloWorld {
        static {
            System.out.println("class initialization method");
        }
    }
    

    编码实现

    public class MethodVisitorTest implements Opcodes {
        public static void main(String[] args) throws Exception {
            // (1) 生成byte[]内容
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
        }
    
        public static byte[] dump() throws Exception {
            // (1) 创建ClassWriter对象
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    
            // (2) 调用visitXxx()方法
            cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
    
            {
                MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
                mv1.visitCode();
                mv1.visitVarInsn(ALOAD, 0);
                mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
                mv1.visitInsn(RETURN);
                mv1.visitMaxs(1, 1);
                mv1.visitEnd();
            }
    
            {
                MethodVisitor mv2 = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
                mv2.visitCode();
                mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv2.visitLdcInsn("class initialization method");
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                mv2.visitInsn(RETURN);
                mv2.visitMaxs(2, 0);
                mv2.visitEnd();
            }
    
            cw.visitEnd();
    
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    }
    

    Frame的变化

    对于HelloWorld类中<clinit>()方法对应的Instruction内容如下:

    $ javap -c sample.HelloWorld
    static {};
      Code:
         0: getstatic     #18                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #20                 // String class initialization method
         5: invokevirtual #26                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
    

    该方法对应的Frame变化情况如下:

    <clinit>()V
    [] []
    [] [java/io/PrintStream]
    [] [java/io/PrintStream, java/lang/String]
    [] []
    [] []
    

    小总结

    通过上面的示例,我们注意三个知识点:

    • 第一点,如何使用ClassWriter类。
    • 第二点,在使用MethodVisitor类时,其中visitXxx()方法需要遵循的调用顺序。
    • 第三点,在.class文件中,静态初始化方法的名字是<clinit>,它的方法描述符是()V

    示例三:创建对象

    预期目标

    假如有一个GoodChild类,内容如下:

    public class GoodChild {
        public String name;
        public int age;
    
        public GoodChild(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    

    我们的预期目标是生成一个HelloWorld类:

    public class HelloWorld {
        public void test() {
            GoodChild child = new GoodChild("Lucy", 8);
        }
    }
    

    编码实现

    public class MethodVisitorTest implements Opcodes {
        public static void main(String[] args) throws Exception {
            // (1) 生成byte[]内容
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
        }
    
        public static byte[] dump() throws Exception {
            // (1) 创建ClassWriter对象
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    
            // (2) 调用visitXxx()方法
            cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
    
            {
                MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
                mv1.visitCode();
                mv1.visitVarInsn(ALOAD, 0);
                mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
                mv1.visitInsn(RETURN);
                mv1.visitMaxs(1, 1);
                mv1.visitEnd();
            }
    
            {
                MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
                mv2.visitCode();
                mv2.visitTypeInsn(NEW, "sample/GoodChild");
                mv2.visitInsn(DUP);
                mv2.visitLdcInsn("Lucy");
                mv2.visitIntInsn(BIPUSH, 8);
                mv2.visitMethodInsn(INVOKESPECIAL, "sample/GoodChild", "<init>", "(Ljava/lang/String;I)V", false);
                mv2.visitVarInsn(ASTORE, 1);
                mv2.visitInsn(RETURN);
                mv2.visitMaxs(4, 2);
                mv2.visitEnd();
            }
    
            cw.visitEnd();
    
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    }
    

    运行效果:

    image-20220414154809288

    Frame的变化

    对于HelloWorld类中test()方法对应的Instruction内容如下:

    $ javap -c sample.HelloWorld
    public void test();
      Code:
         0: new           #11                 // class sample/GoodChild
         3: dup
         4: ldc           #13                 // String Lucy
         6: bipush        8
         8: invokespecial #16                 // Method sample/GoodChild."<init>":(Ljava/lang/String;I)V
        11: astore_1
        12: return
    

    该方法对应的Frame变化情况如下:

    test()V
    [sample/HelloWorld] []
    [sample/HelloWorld] [uninitialized_sample/GoodChild]
    [sample/HelloWorld] [uninitialized_sample/GoodChild, uninitialized_sample/GoodChild]
    [sample/HelloWorld] [uninitialized_sample/GoodChild, uninitialized_sample/GoodChild, java/lang/String]
    [sample/HelloWorld] [uninitialized_sample/GoodChild, uninitialized_sample/GoodChild, java/lang/String, int]
    [sample/HelloWorld] [sample/GoodChild]
    [sample/HelloWorld, sample/GoodChild] []
    [] []
    

    小总结

    通过上面的示例,我们注意四个知识点:

    • 第一点,如何使用ClassWriter类。
    • 第二点,在使用MethodVisitor类时,其中visitXxx()方法需要遵循的调用顺序。
    • 第三点,从Instruction的角度来讲,创建对象的指令集合:
      • new
      • dup
      • invokespecial
    • 第四点,从Frame的角度来讲,在创建新对象的时候,执行new指令之后,它是uninitialized状态,执行invokespecial指令之后,它是一个“合格”的对象。

    示例四:调用方法

    预期目标

    public class HelloWorld {
        public void test(int a, int b) {
            int val = Math.max(a, b); // 对static方法进行调用
            System.out.println(val);  // 对non-static方法进行调用
        }
    }
    

    编码实现

    public class MethodVisitorTest implements Opcodes {
        public static void main(String[] args) throws Exception {
            // (1) 生成byte[]内容
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
        }
    
        public static byte[] dump() throws Exception {
            // (1) 创建ClassWriter对象
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    
            // (2) 调用visitXxx()方法
            cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
    
            {
                MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
                mv1.visitCode();
                mv1.visitVarInsn(ALOAD, 0);
                mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
                mv1.visitInsn(RETURN);
                mv1.visitMaxs(1, 1);
                mv1.visitEnd();
            }
    
            {
                MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(II)V", null, null);
                mv2.visitCode();
                mv2.visitVarInsn(ILOAD, 1);
                mv2.visitVarInsn(ILOAD, 2);
                mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Math", "max", "(II)I", false);
                mv2.visitVarInsn(ISTORE, 3);
                mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv2.visitVarInsn(ILOAD, 3);
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
                mv2.visitInsn(RETURN);
                mv2.visitMaxs(2, 4);
                mv2.visitEnd();
            }
    
            cw.visitEnd();
    
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    }
    

    执行结果:

    image-20220414155241932

    验证结果

    import java.lang.reflect.Method;
    
    public class HelloWorldRun {
        public static void main(String[] args) throws Exception {
            Class<?> clazz = Class.forName("sample.HelloWorld");
            Object obj = clazz.newInstance();
    
            Method m = clazz.getDeclaredMethod("test", int.class, int.class);
            m.invoke(obj, 10, 20);
        }
    }
    

    Frame的变化

    对于HelloWorld类中test()方法对应的Instruction内容如下:

    $ javap -c sample.HelloWorld
    public void test(int, int);
      Code:
         0: iload_1
         1: iload_2
         2: invokestatic  #21                 // Method java/lang/Math.max:(II)I
         5: istore_3
         6: getstatic     #27                 // Field java/lang/System.out:Ljava/io/PrintStream;
         9: iload_3
        10: invokevirtual #33                 // Method java/io/PrintStream.println:(I)V
        13: return
    

    该方法对应的Frame变化情况如下:

    test(II)V
    [sample/HelloWorld, int, int] []
    [sample/HelloWorld, int, int] [int]
    [sample/HelloWorld, int, int] [int, int]
    [sample/HelloWorld, int, int] [int]
    [sample/HelloWorld, int, int, int] []
    [sample/HelloWorld, int, int, int] [java/io/PrintStream]
    [sample/HelloWorld, int, int, int] [java/io/PrintStream, int]
    [sample/HelloWorld, int, int, int] []
    [] []
    

    小总结

    通过上面的示例,我们注意四个知识点:

    • 第一点,如何使用ClassWriter类。
    • 第二点,在使用MethodVisitor类时,其中visitXxx()方法需要遵循的调用顺序。
    • 第三点,从Instruction的角度来讲,调用static方法是使用invokestatic指令,调用non-static方法一般使用invokevirtual指令。
    • 第四点,从Frame的角度来讲,实现方法的调用,需要先将this变量和方法接收的参数放到operand stack上。

    示例五:不调用visitMaxs()方法

    在创建ClassWriter对象时,使用了ClassWriter.COMPUTE_FRAMES选项。

    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    

    使用ClassWriter.COMPUTE_FRAMES后,ASM会自动计算max stacks、max locals和stack map frames的具体值。 从代码的角度来说,使用ClassWriter.COMPUTE_FRAMES,会忽略我们在代码中visitMaxs()方法和visitFrame()方法传入的具体参数值。 换句话说,无论我们传入的参数值是否正确,ASM会帮助我们从新计算一个正确的值,代替我们在代码中传入的参数。

    • 第1种情况,在创建ClassWriter对象时,flags参数使用ClassWriter.COMPUTE_FRAMES值,在调用mv.visitMaxs(0, 0)方法之后,仍然能得到一个正确的.class文件。
    • 第2种情况,在创建ClassWriter对象时,flags参数使用0值,在调用mv.visitMaxs(0, 0)方法之后,得到的.class文件就不能正确运行。

    需要注意的是,在创建ClassWriter对象时,flags参数使用ClassWriter.COMPUTE_FRAMES值,我们可以给visitMaxs()方法传入一个错误的值,但是不能省略对于visitMaxs()方法的调用。 如果我们省略掉visitCode()visitEnd()方法,生成的.class文件也不会出错;当然,并不建议这么做。但是,如果我们省略掉对于visitMaxs()方法的调用,生成的.class文件就会出错。

    如果省略掉对于visitMaxs()方法的调用,会出现如下错误:

    Exception in thread "main" java.lang.VerifyError: Operand stack overflow
    

    示例六:不同的MethodVisitor交叉使用

    假如我们有两个MethodVisitor对象mv1mv2,如下所示:

    MethodVisitor mv1 = cw.visitMethod(...);
    MethodVisitor mv2 = cw.visitMethod(...);
    

    同时,我们也知道MethodVisitor类里的visitXxx()方法需要遵循一定的调用顺序:

    • 第一步,调用visitCode()方法,调用一次
    • 第二步,调用visitXxxInsn()方法,可以调用多次
    • 第三步,调用visitMaxs()方法,调用一次
    • 第四步,调用visitEnd()方法,调用一次

    对于mv1mv2这两个对象来说,它们的visitXxx()方法的调用顺序是彼此独立的、不会相互干扰。

    一般情况下,我们可以如下写代码,这样逻辑比较清晰:

    MethodVisitor mv1 = cw.visitMethod(...);
    mv1.visitCode(...);
    mv1.visitXxxInsn(...)
    mv1.visitMaxs(...);
    mv1.visitEnd();
    
    MethodVisitor mv2 = cw.visitMethod(...);
    mv2.visitCode(...);
    mv2.visitXxxInsn(...)
    mv2.visitMaxs(...);
    mv2.visitEnd();
    

    但是,我们也可以这样来写代码:

    MethodVisitor mv1 = cw.visitMethod(...);
    MethodVisitor mv2 = cw.visitMethod(...);
    
    mv1.visitCode(...);
    mv2.visitCode(...);
    
    mv2.visitXxxInsn(...)
    mv1.visitXxxInsn(...)
    
    mv1.visitMaxs(...);
    mv1.visitEnd();
    mv2.visitMaxs(...);
    mv2.visitEnd();
    

    在上面的代码中,mv1mv2这两个对象的visitXxx()方法交叉调用,这是可以的。 换句话说,只要每一个MethodVisitor对象在调用visitXxx()方法时,遵循了调用顺序,那结果就是正确的; 不同的MethodVisitor对象,是相互独立的、不会彼此影响。

    那么,可能有的同学会问:MethodVisitor对象交叉使用有什么作用呢?有没有什么场景下的应用呢?回答是“有的”。 在ASM当中,有一个org.objectweb.asm.commons.StaticInitMerger类,其中有一个MethodVisitor mergedClinitVisitor字段,它就是一个很好的示例,在后续内容中,我们会介绍到这个类。

    总结

    • 第一点,要注意MethodVisitor类里visitXxx()的调用顺序:
      • 第一步,调用visitCode()方法,调用一次
      • 第二步,调用visitXxxInsn()方法,可以调用多次
      • 第三步,调用visitMaxs()方法,调用一次
      • 第四步,调用visitEnd()方法,调用一次
    • 第二点,在.class文件当中,构造方法的名字是<init>,静态初始化方法的名字是<clinit>
    • 第三点,针对方法里包含的Instruction内容,需要放到Frame当中才能更好的理解。对每一条Instruction来说,它都有可能引起local variables和operand stack的变化。
    • 第四点,在使用COMPUTE_FRAMES的前提下,我们可以给visitMaxs()方法参数传入错误的值,但不能忽略对于visitMaxs()方法的调用。
    • 第五点,不同的MethodVisitor对象,它们的visitXxx()方法是彼此独立的,只要各自遵循方法的调用顺序,就能够得到正确的结果。
  • 相关阅读:
    前端UI框架
    Knowledge
    Microsoft SQL Server
    ASP.NET MVC
    将博客搬至CSDN
    python中的数据类型
    python基础知识
    接口和抽象类的区别
    面向对象的四大特征
    数据结构学习笔记
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/16145086.html
Copyright © 2020-2023  润新知