言語はJavaで、ASM 4.0のお話。
以前はClassNodeの中身をのぞいて書き換え、ということをしていたがVisitorを使ってみる。
今回は"void targetMethod()"というメソッド内の"f()"という処理の後に"System.out.println();"を挿入するという想定。
おおまかな流れ
- byte配列が与えられる
- byte配列を用いてClassReaderのインスタンス生成
- ClassWriterのインスタンス生成
- ClassWriterを引数にClassVisitorのインスタンスを生成
- ClassVisitorのインスタンス生成時に無名クラスを用いてvisitMethodのメソッドをオーバーライド
- 目標のメソッドをみつけて書き換え
- ClassReader#acceptに、ClassVisitorに入れていたClassWriterのインスタンスを渡す
- ClassWriter#toByteArrayでbyte配列にする
//元ソース 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: