当方法内部的 instructions 执行的时候,可能会遇到异常情况,那么 JVM 会将其封装成一个具体的 Exception 子类的对象; 然后,把这个“异常对象”放在 operand stack 上,接下来要怎么处理呢?
对于 operand stack 上的“异常对象”,有两种处理情况:
- 第一种,当前方法处理不了,只能再交给别的方法进行处理
- 第二种,当前方法能够处理,就按照自己的处理逻辑来进行处理了
在本文当中,我们就只关注第二种情况,也就是当前方法能够这个“异常对象”。
在当前方法中,处理异常的机制,在 Code
属性结构当中,分成了两个部分进行存储:
- 第一部分,是位于
exception_table[]
内,它类似于中国古代打仗当中“军师”(运筹帷幄),它会告诉我们应该怎么处理这个“异常对象”。 - 第二部分,是位于
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];
}
异常处理的顺序
代码举例
假如有一个 HelloWorld
类,代码如下:
public class HelloWorld {
public void test(int a, int b) {
try {
int val = a / b;
System.out.println(val);
}
catch (ArithmeticException ex1) {
System.out.println("catch ArithmeticException");
}
catch (Exception ex2) {
System.out.println("catch Exception");
}
}
}
接着,我们来写一个 HelloWorldRun
类,来对 HelloWorld.test()
方法进行调用。
import sample.HelloWorld;
public class HelloWorldRun {
public static void main(String[] args) throws Exception {
HelloWorld instance = new HelloWorld();
instance.test(10, 0);
}
}
我们来运行一下 HelloWorldRun
类,查看一下输出结果:
catch ArithmeticException
exception table 结构
那么,异常处理的逻辑是存储在哪里呢?
从 ClassFile 的角度来看,异常处理的逻辑,是存在 Code
属性的 exception_table
部分。
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];
}
接着,我们对 HelloWorld.class
文件进行分析,其 test()
方法的 Code
结构当中各个条目具体取值如下:
Code_attribute {
attribute_name_index='000D' (#13)
attribute_length='000000C1' (193)
max_stack='0002' (2)
max_locals='0004' (4)
code_length='00000024' (36)
code: 1B1C6C3EB200021DB60003A700184EB200021205B60006A7000C4EB200021208B60006B1
exception_table_length='0002' (2) // 注意,这里的数量是 2
exception_table[0] { // 这里是第 1 个异常处理
start_pc='0000' (0)
end_pc='000B' (11)
handler_pc='000E' (14)
catch_type='0004' (#4) // 这里表示捕获的异常是 ArithmeticException
}
exception_table[1] { // 这里是第 2 个异常处理
start_pc='0000' (0)
end_pc='000B' (11)
handler_pc='001A' (26)
catch_type='0007' (#7) // 这里表示捕获的异常是 Exception 类型
}
attributes_count='0003' (3)
LineNumberTable: 000E00000026...
LocalVariableTable: 000F0000003E...
StackMapTable: 001C0000000B...
}
再接下来,我们结合着 javap
指令来理解一下 exception table 的含义:
$ javap -c sample.HelloWorld
Compiled from "HelloWorld.java"
public class sample.HelloWorld {
...
public void test(int, int);
Code:
0: iload_1
1: iload_2
2: idiv
3: istore_3
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_3
8: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
11: goto 35
14: astore_3
15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
18: ldc #5 // String catch ArithmeticException
20: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: goto 35
26: astore_3
27: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
30: ldc #8 // String catch Exception
32: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: return
Exception table:
from to target type
0 11 14 Class java/lang/ArithmeticException
0 11 26 Class java/lang/Exception
}
调换 exception table 顺序
在这里,我们想调换一下 exception table 顺序:先捕获 Exception
异常,再捕获 ArithmeticException
异常。
我们想通过 Java 语言来实现调换一下 exception table 顺序,会发现代码会报错:
public class HelloWorld {
public void test(int a, int b) {
try {
int val = a / b;
System.out.println(val);
}
catch (Exception ex2) {
System.out.println("catch Exception");
}
catch (ArithmeticException ex1) { // Exception 'java.lang.ArithmeticException' has already been caught
System.out.println("catch ArithmeticException");
}
}
}
那么,应该怎么做呢?我们就通过 ASM 来帮助我们实现调换一下 exception table 顺序。
首先,我们来看一下,原本 HelloWorld
类的代码如何通过 ASM 代码来进行实现:
import lsieun.utils.FileUtils;
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;
public class HelloWorldGenerateCore {
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);
// (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);
Label startLabel = new Label();
Label endLabel = new Label();
Label exceptionHandler01 = new Label();
Label exceptionHandler02 = new Label();
Label returnLabel = new Label();
mv2.visitCode();
mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandler01, "java/lang/ArithmeticException");
mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandler02, "java/lang/Exception");
mv2.visitLabel(startLabel);
mv2.visitVarInsn(ILOAD, 1);
mv2.visitVarInsn(ILOAD, 2);
mv2.visitInsn(IDIV);
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.visitLabel(endLabel);
mv2.visitJumpInsn(GOTO, returnLabel);
mv2.visitLabel(exceptionHandler01);
mv2.visitVarInsn(ASTORE, 3);
mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv2.visitLdcInsn("catch ArithmeticException");
mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv2.visitJumpInsn(GOTO, returnLabel);
mv2.visitLabel(exceptionHandler02);
mv2.visitVarInsn(ASTORE, 3);
mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv2.visitLdcInsn("catch Exception");
mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv2.visitLabel(returnLabel);
mv2.visitInsn(RETURN);
mv2.visitMaxs(2, 4);
mv2.visitEnd();
}
cw.visitEnd();
// (3) 调用 toByteArray()方法
return cw.toByteArray();
}
}
接下来,我们调换一下上述 ASM 代码中两个 MethodVisitor.visitTryCatchBlock()
方法调用的顺序:
mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandler02, "java/lang/Exception");
mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandler01, "java/lang/ArithmeticException");
那么,我们执行一下 HelloWorldGenerateCore
类,就可以生成 HelloWorld.class
文件。
然后,我们执行一下 javap -c sample.HelloWorld
命令,来验证一下:
$ javap -c sample.HelloWorld
public class sample.HelloWorld {
...
public void test(int, int);
Code:
0: iload_1
1: iload_2
2: idiv
3: istore_3
4: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_3
8: invokevirtual #26 // Method java/io/PrintStream.println:(I)V
11: goto 35
14: astore_3
15: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
18: ldc #28 // String catch ArithmeticException
20: invokevirtual #31 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: goto 35
26: astore_3
27: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
30: ldc #33 // String catch Exception
32: invokevirtual #31 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: return
Exception table:
from to target type
0 11 26 Class java/lang/Exception
0 11 14 Class java/lang/ArithmeticException
}
再接着,我们来运行一下 HelloWorldRun
类,查看一下输出结果:
catch Exception
这样的一个输出结果,说明:exception table 就是根据先后顺序来判定的,而不是找到最匹配的异常类型。
为整个方法添加异常处理的逻辑
代码举例
假如有一个 HelloWorld
类,代码如下:
public class HelloWorld {
public void test(String name, int age) {
try {
int length = name.length();
System.out.println("length = " + length);
}
catch (NullPointerException ex) {
System.out.println("name is null");
}
int val = div(10, age);
System.out.println("val = " + val);
}
public int div(int a, int b) {
return a / b;
}
}
我们想实现的预期目标:将整个 test()
方法添加一个 try-catch 语句。
首先,我们来运行一下 HelloWorldRun
类:
import sample.HelloWorld;
public class HelloWorldRun {
public static void main(String[] args) throws Exception {
HelloWorld instance = new HelloWorld();
instance.test(null, 0);
}
}
其输出结果如下:
name is null
Exception in thread "main" java.lang.ArithmeticException: / by zero
at sample.HelloWorld.div(HelloWorld.java:18)
at sample.HelloWorld.test(HelloWorld.java:13)
at run.HelloWorldRun.main(HelloWorldRun.java:8)
使用 javap
命令查看 exception table 的内容:
$ javap -c sample.HelloWorld
Compiled from "HelloWorld.java"
public class sample.HelloWorld {
...
public void test(java.lang.String, int);
Code:
0: aload_1
1: invokevirtual #2 // Method java/lang/String.length:()I
4: istore_3
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: new #4 // class java/lang/StringBuilder
11: dup
12: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
15: ldc #6 // String length =
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: iload_3
21: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
24: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: goto 42
33: astore_3
34: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #12 // String name is null
39: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: aload_0
43: bipush 10
45: iload_2
46: invokevirtual #13 // Method div:(II)I
49: istore_3
50: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
53: new #4 // class java/lang/StringBuilder
56: dup
57: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
60: ldc #14 // String val =
62: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
65: iload_3
66: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
69: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
72: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
75: return
Exception table:
from to target type
0 30 33 Class java/lang/NullPointerException
...
}
正确的异常捕获处理
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;
public class MethodWithWholeTryCatchVisitor extends ClassVisitor {
private final String methodName;
private final String methodDesc;
public MethodWithWholeTryCatchVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) {
super(api, classVisitor);
this.methodName = methodName;
this.methodDesc = methodDesc;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (mv != null && !"<init>".equals(name) && methodName.equals(name) && methodDesc.equals(descriptor)) {
boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
boolean isNativeMethod = (access & ACC_NATIVE) != 0;
if (!isAbstractMethod && !isNativeMethod) {
mv = new MethodWithWholeTryCatchAdapter(api, mv, access, descriptor);
}
}
return mv;
}
private static class MethodWithWholeTryCatchAdapter extends MethodVisitor {
private final int methodAccess;
private final String methodDesc;
private final Label startLabel = new Label();
private final Label endLabel = new Label();
private final Label handlerLabel = new Label();
public MethodWithWholeTryCatchAdapter(int api, MethodVisitor methodVisitor, int methodAccess, String methodDesc) {
super(api, methodVisitor);
this.methodAccess = methodAccess;
this.methodDesc = methodDesc;
}
public void visitCode() {
// 首先,处理自己的代码逻辑
// (1) startLabel
super.visitLabel(startLabel);
// 其次,调用父类的方法实现
super.visitCode();
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// 首先,处理自己的代码逻辑
// (2) endLabel
super.visitLabel(endLabel);
// (3) handlerLabel
super.visitLabel(handlerLabel);
int localIndex = getLocalIndex();
super.visitVarInsn(ASTORE, localIndex);
super.visitVarInsn(ALOAD, localIndex);
super.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "(Ljava/io/PrintStream;)V", false);
super.visitVarInsn(ALOAD, localIndex);
super.visitInsn(Opcodes.ATHROW);
// (4) visitTryCatchBlock
super.visitTryCatchBlock(startLabel, endLabel, handlerLabel, "java/lang/Exception");
// 其次,调用父类的方法实现
super.visitMaxs(maxStack, maxLocals);
}
private int getLocalIndex() {
Type t = Type.getType(methodDesc);
Type[] argumentTypes = t.getArgumentTypes();
boolean isStaticMethod = ((methodAccess & ACC_STATIC) != 0);
int localIndex = isStaticMethod ? 0 : 1;
for (Type argType : argumentTypes) {
localIndex += argType.getSize();
}
return localIndex;
}
}
}
进行转换:
import lsieun.utils.FileUtils;
import org.objectweb.asm.*;
public class HelloWorldTransformCore {
public static void main(String[] args) {
String relative_path = "sample/HelloWorld.class";
String filepath = FileUtils.getFilePath(relative_path);
byte[] bytes1 = FileUtils.readBytes(filepath);
//(1)构建 ClassReader
ClassReader cr = new ClassReader(bytes1);
//(2)构建 ClassWriter
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//(3)串连 ClassVisitor
int api = Opcodes.ASM9;
ClassVisitor cv = new MethodWithWholeTryCatchVisitor(api, cw, "test", "(Ljava/lang/String;I)V");
//(4)结合 ClassReader 和 ClassVisitor
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
//(5)生成 byte[]
byte[] bytes2 = cw.toByteArray();
FileUtils.writeBytes(filepath, bytes2);
}
}
转换完成之后,再次运行 HelloWorldRun
类,得到如下输出内容:
name is null
java.lang.ArithmeticException: / by zero
at sample.HelloWorld.div(Unknown Source)
at sample.HelloWorld.test(Unknown Source)
at run.HelloWorldRun.main(HelloWorldRun.java:8)
Exception in thread "main" java.lang.ArithmeticException: / by zero
at sample.HelloWorld.div(Unknown Source)
at sample.HelloWorld.test(Unknown Source)
at run.HelloWorldRun.main(HelloWorldRun.java:8)
使用 javap
命令查看 exception table 的内容:
$ javap -c sample.HelloWorld
public class sample.HelloWorld {
...
public void test(java.lang.String, int);
Code:
0: aload_1
1: invokevirtual #18 // Method java/lang/String.length:()I
4: istore_3
5: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
8: new #26 // class java/lang/StringBuilder
11: dup
12: invokespecial #27 // Method java/lang/StringBuilder."<init>":()V
15: ldc #29 // String length =
17: invokevirtual #33 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: iload_3
21: invokevirtual #36 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
24: invokevirtual #40 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: invokevirtual #46 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: goto 42
33: astore_3
34: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #48 // String name is null
39: invokevirtual #46 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: aload_0
43: bipush 10
45: iload_2
46: invokevirtual #52 // Method div:(II)I
49: istore_3
50: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
53: new #26 // class java/lang/StringBuilder
56: dup
57: invokespecial #27 // Method java/lang/StringBuilder."<init>":()V
60: ldc #54 // String val =
62: invokevirtual #33 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
65: iload_3
66: invokevirtual #36 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
69: invokevirtual #40 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
72: invokevirtual #46 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
75: return
76: astore_3
77: aload_3
78: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
81: invokevirtual #60 // Method java/lang/Exception.printStackTrace:(Ljava/io/PrintStream;)V
84: aload_3
85: athrow
Exception table:
from to target type
0 30 33 Class java/lang/NullPointerException
0 76 76 Class java/lang/Exception
...
}
错误的异常捕获处理
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;
public class MethodWithWholeTryCatchVisitor extends ClassVisitor {
private final String methodName;
private final String methodDesc;
public MethodWithWholeTryCatchVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) {
super(api, classVisitor);
this.methodName = methodName;
this.methodDesc = methodDesc;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (mv != null && !"<init>".equals(name) && methodName.equals(name) && methodDesc.equals(descriptor)) {
boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
boolean isNativeMethod = (access & ACC_NATIVE) != 0;
if (!isAbstractMethod && !isNativeMethod) {
mv = new MethodWithWholeTryCatchAdapter(api, mv, access, descriptor);
}
}
return mv;
}
private static class MethodWithWholeTryCatchAdapter extends MethodVisitor {
private final int methodAccess;
private final String methodDesc;
private final Label startLabel = new Label();
private final Label endLabel = new Label();
private final Label handlerLabel = new Label();
public MethodWithWholeTryCatchAdapter(int api, MethodVisitor methodVisitor, int methodAccess, String methodDesc) {
super(api, methodVisitor);
this.methodAccess = methodAccess;
this.methodDesc = methodDesc;
}
public void visitCode() {
// 首先,处理自己的代码逻辑
// (1) visitTryCatchBlock 和 startLabel (注意,修改的是 visitTryCatchBlock 方法的位置)
super.visitTryCatchBlock(startLabel, endLabel, handlerLabel, "java/lang/Exception");
super.visitLabel(startLabel);
// 其次,调用父类的方法实现
super.visitCode();
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// 首先,处理自己的代码逻辑
// (2) endLabel
super.visitLabel(endLabel);
// (3) handlerLabel
super.visitLabel(handlerLabel);
int localIndex = getLocalIndex();
super.visitVarInsn(ASTORE, localIndex);
super.visitVarInsn(ALOAD, localIndex);
super.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "(Ljava/io/PrintStream;)V", false);
super.visitVarInsn(ALOAD, localIndex);
super.visitInsn(Opcodes.ATHROW);
// 其次,调用父类的方法实现
super.visitMaxs(maxStack, maxLocals);
}
private int getLocalIndex() {
Type t = Type.getType(methodDesc);
Type[] argumentTypes = t.getArgumentTypes();
boolean isStaticMethod = ((methodAccess & ACC_STATIC) != 0);
int localIndex = isStaticMethod ? 0 : 1;
for (Type argType : argumentTypes) {
localIndex += argType.getSize();
}
return localIndex;
}
}
}
转换完成之后,我们运行 HelloWorldRun
来查看一下输出内容:
java.lang.NullPointerException
at sample.HelloWorld.test(Unknown Source)
at run.HelloWorldRun.main(HelloWorldRun.java:8)
Exception in thread "main" java.lang.NullPointerException
at sample.HelloWorld.test(Unknown Source)
at run.HelloWorldRun.main(HelloWorldRun.java:8)
我们使用 javap
命令查看 exception table 的内容:
$ javap -c sample.HelloWorld
public class sample.HelloWorld {
...
public void test(java.lang.String, int);
Code:
0: aload_1
1: invokevirtual #20 // Method java/lang/String.length:()I
4: istore_3
5: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream;
8: new #28 // class java/lang/StringBuilder
11: dup
12: invokespecial #29 // Method java/lang/StringBuilder."<init>":()V
15: ldc #31 // String length =
17: invokevirtual #35 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: iload_3
21: invokevirtual #38 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
24: invokevirtual #42 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: invokevirtual #48 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: goto 42
33: astore_3
34: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #50 // String name is null
39: invokevirtual #48 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: aload_0
43: bipush 10
45: iload_2
46: invokevirtual #54 // Method div:(II)I
49: istore_3
50: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream;
53: new #28 // class java/lang/StringBuilder
56: dup
57: invokespecial #29 // Method java/lang/StringBuilder."<init>":()V
60: ldc #56 // String val =
62: invokevirtual #35 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
65: iload_3
66: invokevirtual #38 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
69: invokevirtual #42 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
72: invokevirtual #48 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
75: return
76: astore_3
77: aload_3
78: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream;
81: invokevirtual #60 // Method java/lang/Exception.printStackTrace:(Ljava/io/PrintStream;)V
84: aload_3
85: athrow
Exception table:
from to target type
0 76 76 Class java/lang/Exception
0 30 33 Class java/lang/NullPointerException
...
}
操作数栈上的异常对象
当方法执行过程中,遇到异常的时候,在 operand stack 上会有一个“异常对象”。
这个“异常对象”,可能有对应的代码处理逻辑;但是,我们想对这个“异常对象”进行多次处理,那下一步要怎么处理呢?
有两种处理方式:
- 第一种方式,就是将“异常对象”存储到 local variable 内;后续使用的时候,再将“异常对象”加载到 operand stack 上。
- 第二种方式,就是在 operand stack 上,要使用的时候,就进行一次
dup
(复制)操作。
存储异常对象
对于第一种情况,我们要将“异常对象”存储到 local variable 当中,但是,我们应该把这个“异常对象”存储到哪个位置上呢?换句话说,我们怎么计算“异常对象”在 local variable 当中的位置呢?我们需要考虑三个因素:
- 第一个因素,是否需要存储
this
变量。 - 第二个因素,是否需要存储方法的参数(method parameters)。
- 第三个因素,是否需要存储方法内部定义的局部变量(method local var)。
考虑这三个因素之后,我们就可以确定“异常对象”应该存储的一个什么位置上了。
在上面的 MethodWithWholeTryCatchAdapter
当中,有一个 getLocalIndex()
方法。
这个方法就是考虑了 this
和方法的参数(method parameters),之后就是“异常对象”可以存储的位置。
再者,方法内部定义的局部变量(method local var),有的时候有,有的时候没有,大家根据实际的情况来决定是否添加。
class MethodWithWholeTryCatchAdapter extends MethodVisitor {
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// 首先,处理自己的代码逻辑
// (2) endLabel
super.visitLabel(endLabel);
// (3) handlerLabel
super.visitLabel(handlerLabel);
int localIndex = getLocalIndex();
super.visitVarInsn(ASTORE, localIndex);
super.visitVarInsn(ALOAD, localIndex);
super.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "(Ljava/io/PrintStream;)V", false);
super.visitVarInsn(ALOAD, localIndex);
super.visitInsn(Opcodes.ATHROW);
// 其次,调用父类的方法实现
super.visitMaxs(maxStack, maxLocals);
}
private int getLocalIndex() {
Type t = Type.getType(methodDesc);
Type[] argumentTypes = t.getArgumentTypes();
boolean isStaticMethod = ((methodAccess & ACC_STATIC) != 0);
int localIndex = isStaticMethod ? 0 : 1;
for (Type argType : argumentTypes) {
localIndex += argType.getSize();
}
return localIndex;
}
}
操作数栈上复制
第二种方式,就是不依赖于 local variable,也就不用去计算一个具体的位置了。
那么,我们想对“异常对象”进行多次的处理,直接在 operand stack 进行 dup
(复制),就能够进行多次处理了。
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;
public class MethodWithWholeTryCatchVisitor extends ClassVisitor {
private final String methodName;
private final String methodDesc;
public MethodWithWholeTryCatchVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) {
super(api, classVisitor);
this.methodName = methodName;
this.methodDesc = methodDesc;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (mv != null && !"<init>".equals(name) && methodName.equals(name) && methodDesc.equals(descriptor)) {
boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
boolean isNativeMethod = (access & ACC_NATIVE) != 0;
if (!isAbstractMethod && !isNativeMethod) {
mv = new MethodWithWholeTryCatchAdapter(api, mv, access, descriptor);
}
}
return mv;
}
private static class MethodWithWholeTryCatchAdapter extends MethodVisitor {
private final int methodAccess;
private final String methodDesc;
private final Label startLabel = new Label();
private final Label endLabel = new Label();
private final Label handlerLabel = new Label();
public MethodWithWholeTryCatchAdapter(int api, MethodVisitor methodVisitor, int methodAccess, String methodDesc) {
super(api, methodVisitor);
this.methodAccess = methodAccess;
this.methodDesc = methodDesc;
}
public void visitCode() {
// 首先,处理自己的代码逻辑
// (1) startLabel
super.visitLabel(startLabel);
// 其次,调用父类的方法实现
super.visitCode();
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// 首先,处理自己的代码逻辑
// (2) endLabel
super.visitLabel(endLabel);
// (3) handlerLabel
super.visitLabel(handlerLabel);
super.visitInsn(DUP); // 注意,这里使用了 DUP 指令
super.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "(Ljava/io/PrintStream;)V", false);
super.visitInsn(Opcodes.ATHROW);
// (4) visitTryCatchBlock
super.visitTryCatchBlock(startLabel, endLabel, handlerLabel, "java/lang/Exception");
// 其次,调用父类的方法实现
super.visitMaxs(maxStack, maxLocals);
}
}
}