上级目录

The CheckClassAdapter class checks that its methods are called in the appropriate order, and with valid arguments, before delegating to the next visitor.

两种使用方式

到目前为止,我们主要介绍了 Class Generation 和 Class Transformation 操作。我们可以借助于 CheckClassAdapter 类来检查生成的字节码内容是否正确,主要有两种使用方式:

  • 在生成类或转换类的过程中进行检查
  • 在生成类或转换类的结束后进行检查

第一种方式

第一种使用方式,是在生成类(Class Generation)或转换类(Class Transformation)的过程中进行检查:

// 第一步,应用于 Class Generation
// 串联 ClassVisitor:cv --- cca --- cw
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
CheckClassAdapter cca = new CheckClassAdapter(cw);
ClassVisitor cv = new MyClassVisitor(cca);

// 第二步,应用于 Class Transformation
byte[] bytes = ... // 这里是 class file bytes
ClassReader cr = new ClassReader(bytes);
cr.accept(cv, 0);

示例如下:

import lsieun.utils.FileUtils;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.util.CheckClassAdapter;

import static org.objectweb.asm.Opcodes.*;

public class CheckClassAdapterExample01Generate {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成 byte[] 内容
        byte[] bytes = dump();

        // (2) 保存 byte[] 到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建 ClassWriter 对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassVisitor cv = new CheckClassAdapter(cw);

        // (2) 调用 visitXxx() 方法
        cv.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            FieldVisitor fv = cv.visitField(ACC_PRIVATE, "intValue", "I", null, null);
            fv.visitEnd();
        }

        {
            MethodVisitor mv1 = cv.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 = cv.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            mv2.visitCode();
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("Hello World");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(2, 1);
            mv2.visitEnd();
        }
        cv.visitEnd();

        // (3) 调用 toByteArray() 方法
        return cw.toByteArray();
    }
}

第二种方式

第二种使用方式,是在生成类(Class Generation)或转换类(Class Transformation)的结束后进行检查:

byte[] bytes = ... // 这里是 class file bytes
PrintWriter printWriter = new PrintWriter(System.out);
CheckClassAdapter.verify(new ClassReader(bytes), false, printWriter);

示例如下:

import lsieun.utils.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.util.CheckClassAdapter;

import java.io.PrintWriter;

import static org.objectweb.asm.Opcodes.*;

public class CheckClassAdapterExample02Generate {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成 byte[] 内容
        byte[] bytes = dump();

        // (2) 保存 byte[] 到文件
        FileUtils.writeBytes(filepath, bytes);

        // (3) 检查
        PrintWriter printWriter = new PrintWriter(System.out);
        CheckClassAdapter.verify(new ClassReader(bytes), false, printWriter);
    }

    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);

        {
            FieldVisitor fv = cw.visitField(ACC_PRIVATE, "intValue", "I", null, null);
            fv.visitEnd();
        }

        {
            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.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("Hello World");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(2, 1);
            mv2.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用 toByteArray() 方法
        return cw.toByteArray();
    }
}

检测案例

检测:方法调用顺序

如果将 mv2.visitLdcInsn()mv2.visitFieldInsn() 顺序调换:

mv2.visitLdcInsn("Hello World");
mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

会出现如下错误:

Method owner: expected Ljava/io/PrintStream;, but found Ljava/lang/String;

检测:方法参数不对

如果将方法的描述符((Ljava/lang/String;)V)修改成 (I)V

mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv2.visitLdcInsn("Hello World");
mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);

会出现如下错误:

Argument 1: expected I, but found Ljava/lang/String;

检测:没有 return 语句

如果注释掉 mv2.visitInsn(RETURN); 语句,会出现如下错误:

org.objectweb.asm.tree.analysis.AnalyzerException: Execution can fall off the end of the code

检测:不调用 visitMaxs() 方法

如果注释掉 mv2.visitMaxs(2, 1); 语句,会出现如下错误:

org.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 0: Insufficient maximum stack size.

检测不出:重复类成员

如果出现重复的字段或者重复的方法,CheckClassAdapter 类是检测不出来的:

{
	FieldVisitor fv = cw.visitField(ACC_PRIVATE, "intValue", "I", null, null);
	fv.visitEnd();
}

{
	FieldVisitor fv = cw.visitField(ACC_PRIVATE, "intValue", "I", null, null);
	fv.visitEnd();
}

总结

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

  • 第一点,作为一个工具类,CheckClassAdapter 类的主要作用是检查生成的 byte[] 内容是否合法,但是它能够实现的检查功能是有限的,有一些问题是无法检测出来的。
  • 第二点,在编写 ASM 代码的过程中,除了使用 CheckClassAdapter 类帮助检查,我们自身所具备的“细心认真的态度”和“缜密的思考”是非常重要的、不可替代的因素。