CheckClassAdapter介绍

CheckClassAdapter

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

1. 两种使用方式

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

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

1. 两种使用方式

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

  • 在生成类或转换类的过程中进行检查
  • 在生成类或转换类的结束后进行检查
1.1. 第一种方式

第一种使用方式,是在生成类(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();
    }
}
1.2. 第二种方式

第二种使用方式,是在生成类(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();
    }
}

2. 检测案例

2.1. 检测:方法调用顺序

如果将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;
2.2. 检测:方法参数不对

如果将方法的描述符((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;
2.3. 检测:没有return语句

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

org.objectweb.asm.tree.analysis.AnalyzerException: Execution can fall off the end of the code
2.4. 检测:不调用visitMaxs()方法

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

org.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 0: Insufficient maximum stack size.
2.5. 检测不出:重复类成员

如果出现重复的字段或者重复的方法,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();
}

3. 总结

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值