本文属于Java ASM系列一:Core API当中的一篇。
对于《Java ASM系列一:Core API》有配套的视频讲解,可以点击这里和这里进行查看;同时,也可以点击这里查看源码资料。
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.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
Cla***eader cr = new Cla***eader(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 Cla***eader(bytes), false, printWriter);
示例如下:
import lsieun.utils.FileUtils;
import org.objectweb.asm.Cla***eader;
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 Cla***eader(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
类进行介绍,内容总结如下:
- 第一点,作为一个工具类,
CheckClassAdapter
类的主要作用是检查生成的byte[]
内容是否合法,但是它能够实现的检查功能是有限的,有一些问题是无法检测出来的。 - 第二点,在编写ASM代码的过程中,除了使用
CheckClassAdapter
类帮助检查,我们自身所具备的“细心认真的态度”和“缜密的思考”是非常重要的、不可替代的因素。