あやみつさんの覚書き

メモや覚書きなど

Visitorを使った特定のメソッドへのコードの挿入

言語はJavaで、ASM 4.0のお話。
以前はClassNodeの中身をのぞいて書き換え、ということをしていたがVisitorを使ってみる。
今回は"void targetMethod()"というメソッド内の"f()"という処理の後に"System.out.println();"を挿入するという想定。

おおまかな流れ

//元ソース
public class SampleClass {
	
	void targetMethod() {
		this.f();
	}
	
	void f() {
	}
	
}
// org.objectweb.asm.Opcodes内のstaticなフィールドを参照できる前提
// Opcodes: ASM4やGETSTATICやALOADなどが定義されてるInterface

public byte[] transform(byte[] bytes) {
	ClassReader cr = new ClassReader(bytes);
	ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
	ClassVisitor cv = new ClassVisitor(ASM4, cw) {
		@Override
		public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
			MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
			
			// 目的のメソッドを探す
			// 今回は名前、引数、返り値から特定
			// void targetMethod() -> targetMethod()V
			if (name.equals("targetMethod") && (desc.equals("()V")) {
				// 引数に事前にsuperで得たMethodVisitorを渡す(重要)
				// 目的のメソッドのMethodVisitorを置き換え
				// このときに無名クラスを使ってメソッドをオーバーライド、メソッドの挿入処理をする
				// 挿入のみならず、置き換えなどができる。また無名クラスじゃなくてもよい
				mv = new MethodVisitor(ASM4, mv) {
					@Override
					public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
						// 処理を消したいわけじゃないので通常の処理をおいておく
						super.visitMethodInsn(opcode, owner, name, desc, itf);
						
						
						// 目的の処理を探す
						// void f() -> f()V
						if (name.equals("f") && desc.equals("()V")) {
							// System.out.println();を挿入する
							// super使いながらvisitをして処理を書いていく
							super.visitFieldInsn(GETSTATIC, "java/lang/System", "out"/ "Ljava/io/PrintStream;");// System.out
							super.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "()V", false);// .println()
						}
						
					}
				};
			}
			
			return mv;
		}
	};
	
	cr.accept(cw, 0);
	return cw.toByteArray();
}

これで書き換えができて以下のようになるはず。

// 書き換え後
public class SampleClass {
	
	void targetMethod() {
		this.f();
		System.out.println();
	}
	
	void f() {
	}
	
}

visitでNodeを追加するイメージでやるといいかもしれない。

参考URL: