言語は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() {
}
}
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);
if (name.equals("targetMethod") && (desc.equals("()V")) {
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);
if (name.equals("f") && desc.equals("()V")) {
super.visitFieldInsn(GETSTATIC, "java/lang/System", "out"/ "Ljava/io/PrintStream;");
super.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "()V", false);
}
}
};
}
return mv;
}
};
cr.accept(cw, 0);
return cw.toByteArray();
}
これで書き換えができて以下のようになるはず。
public class SampleClass {
void targetMethod() {
this.f();
System.out.println();
}
void f() {
}
}
visitでNodeを追加するイメージでやるといいかもしれない。
参考URL: