Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

ASM3.3利用例 短編集

ASMを使ったコーディングをしていると、日本語情報の少なさに気づきます。断片的ですが自分が検索した事柄について列挙しておきます。
また公式のサンプルコードがow2のダウンロードページにzipで置いてありますので、こちらを参照すると良いでしょう。

Tips

リテラルをスタックに積む

MethodVisitor#visitLdcInsn(Object)メソッドで積める。積める型は限られている。

MethodVisitor mv = // ...

// System.err.println("TEST ASM");
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
mv.visitLdcInsn("TEST ASM");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");

インスタンスを作る

以前のエントリで紹介したように、NEW(参照作成)→DUP(参照複製)→INVOKESPECIAL(コンストラクタ実行)の順番でバイトコードを実行する。

MethodVisitor mv = // ...

// new Object();
mv.visitTypeInsn(NEW, Type.getInternalName(Object.class));
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V");

参照型の配列を作ってスタックに積む

配列の要素数をオペランドスタックに積んでからANEWARRAYを実行。NEW & INVOKESPECIAL のように2段階に分かれることはない。

MethodVisitor mv = // ...

// new Object[0];
mv.visitInsn(ICONST_0);
mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Object.class));

メソッド開始時・終了時に処理を埋め込む

戻り値をラップする場合などメソッドの開始時・終了時に処理を埋め込む場合は、AdviceAdapterのonMethodEnter()メソッドonMethodExit()メソッドを使うとキレイに書ける。

動的にクラスを定義する

まずはClassWriterでバイト列(≒.classファイルの中身)を作成する。

private byte[] createClassBinary() {
	ClassWriter cw = new ClassWriter(0);
	cw.visit(V1_5, ACC_PUBLIC, "pkg/OwnClass", null, "java/lang/Object", null);
	{
		// constructor
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
		mv.visitMaxs(2, 1);
		mv.visitVarInsn(ALOAD, 0); // push `this` to the operand stack
		mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V"); // call the constructor of super class
		mv.visitInsn(RETURN);
		mv.visitEnd();
	}
	{
		// public Object get() { return new Object(); }
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "get", "()Ljava/lang/Object;", null, null);
		mv.visitMaxs(2, 1);
		mv.visitTypeInsn(NEW, Type.getInternalName(Object.class));
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V");
		mv.visitInsn(ARETURN);
		mv.visitEnd();
	}
	cw.visitEnd();
	return cw.toByteArray();
}

作成したバイト列はドキュメントに載っている通りClassLoaderを自作してdefineClassを呼び出すことで、Classインスタンスに変換できる。

private static class OwnClassLoader extends ClassLoader {
	public Class<?> defineClass(String name, byte[] b) {
		return defineClass(name, b, 0, b.length);
	}
}

private Class<?> createClass(byte[] b) {
	return new OwnClassLoader().defineClass("pkg.OwnClass", b);
}

なお動的にインタフェースを定義する方法は公式ドキュメントに載っている。こちらのほうが簡単。