Javaのバイトコードで遊ぶ (例外処理)

Javaのバイトコードで遊ぶ (Hello World)

例外の送出と捕捉を試してみます。

try-catchブロックの定義にはvisitTryCatchBlockを用います。

visitTryCatchBlock(Label start, Label end, Label handler, String type)

startとendは、例外ハンドラのスコープの開始と終了を示します。 handlerは例外ハンドラのラベルです。 typeは捕捉する例外クラスです。

これを使うと、クラスファイルにException tableなるものが出力されるようになります。

Exception table:
   from    to  target type
       0    16    19   Class java/lang/IllegalArgumentException
       0    16    25   Class java/lang/Exception

あと自分がハマったところですが… 例外ハンドラの開始時には、スタックに例外インスタンスが積まれています。 これは使わない場合でもPOPするなり、ローカル変数に退避するなりしてスタックから除いておくのが無難です。 無視して放置していたら、後続の処理時にstack mapの状態が違う、というエラーが出てハマりました。

以下サンプルのコードです。

package example;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class TryCatch implements Opcodes {
  private static byte[] generate(String className) {
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    MethodVisitor mv;

    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object", null);

    // constructor
    mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
    mv.visitCode();
    mv.visitVarInsn(ALOAD, 0);
    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
    mv.visitInsn(RETURN);
    mv.visitMaxs(1, 1);
    mv.visitEnd();

    // main
    mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
    mv.visitCode();

    Label from = new Label();
    Label to = new Label();
    Label illegalArgExHandler = new Label();
    Label anyExHandler = new Label();
    Label finallyHandler = new Label();

    // try-catch block definitions
    mv.visitTryCatchBlock(from, to, illegalArgExHandler, "java/lang/IllegalArgumentException");
    mv.visitTryCatchBlock(from, to, anyExHandler, "java/lang/Exception");

    // from
    mv.visitLabel(from);
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    mv.visitLdcInsn("try-catch example");
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

    // throw
    mv.visitTypeInsn(NEW, "java/lang/Exception");
    mv.visitInsn(DUP);
    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Exception", "<init>", "()V", false);
    mv.visitInsn(ATHROW);

    // to
    mv.visitLabel(to);
    mv.visitJumpInsn(GOTO, finallyHandler);

    // catch IllegalArgumentException
    mv.visitLabel(illegalArgExHandler);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/IllegalArgumentException", "printStackTrace", "()V", false);
    mv.visitJumpInsn(GOTO, finallyHandler);

    // catch Exception
    mv.visitLabel(anyExHandler);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false);
    mv.visitJumpInsn(GOTO, finallyHandler);

    // finally
    mv.visitLabel(finallyHandler);
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    mv.visitLdcInsn("finally");
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

    mv.visitInsn(RETURN);
    mv.visitMaxs(2, 1);
    mv.visitEnd();
    cw.visitEnd();

    return cw.toByteArray();
  }

  public static void main(String... args) throws Exception {
    String className = "TryCatch";
    byte[] bytes = generate(className);
    ExampleUtil.execMain(className, bytes);
    ExampleUtil.write(className, bytes);
  }
}