Javaのバイトコードで遊ぶ (メソッド呼び出し)

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

INVOKEDYNAMIC以外のINVOKE系の命令を使ってみます。

INVOKESTATICはstaticメソッドを呼び出します。引数を順にスタックに積んでから呼び出します。 呼び出し後は引数がスタックから取り除かれ、戻り値がある場合はスタックに積まれています。

// System.getProperty("java.home")
mv.visitLdcInsn("java.home");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "getProperty",
    "(Ljava/lang/String;)Ljava/lang/String;", false);

INVOKESPECIALはコンストラクタやプライベートメソッドスーパークラスインスタンスメソッドを呼び出します。 以下はコンストラクタ呼び出しの例です。

// new ArrayList();
mv.visitTypeInsn(NEW, "java/util/ArrayList");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V",
    false);

INVOKESPECIALを呼ぶ前にNEWを使ってインスタンスを生成し、DUPインスタンスのポインタ(という表現が適切か不明)をスタック上で複製しています。 コンストラクタインスタンスメソッドインスタンス自体を最初の引数としてスタックに積む必要があります。 複製しておかないとコンストラクタ呼び出しでスタックから取り除かれ、その後の処理でインスタンスを使えないためです。

INVOKEVIRTUALはインスタンスメソッドを呼び出します。 最初にインスタンス自体をスタックに積んだ後、引数を順に積んでから、INVOKEVIRTUALで呼び出します。

// ArrayList l1;
// l1.add(0, "ABC");
mv.visitVarInsn(ALOAD, l1);
mv.visitInsn(ICONST_0);
mv.visitLdcInsn("ABC");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/ArrayList", "add",
    "(ILjava/lang/Object;)V", false);

INVOKEINTERFACEはインターフェースのメソッドを呼び出します。

// List l2;
// l2.add(1, "XYZ");
mv.visitVarInsn(ALOAD, l2);
mv.visitInsn(ICONST_1);
mv.visitLdcInsn("XYZ");
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add",
    "(ILjava/lang/Object;)V", true);

インターフェースのメソッドを呼び出す場合に第5引数にtrueを指定します。 Java8からインターフェースがstaticメソッドを持てるようになりました。 INVOKESTATICで第5引数がtrueの例です。

// l2.sort(Comparator.reverseOrder());
mv.visitVarInsn(ALOAD, l2);
mv.visitMethodInsn(INVOKESTATIC, "java/util/Comparator", "reverseOrder",
    "()Ljava/util/Comparator;", true);
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "sort",
    "(Ljava/util/Comparator;)V", true);

最後にコードの全体を載せておきます。

package example;

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

public class Invoke implements Opcodes {
  private static byte[] generate(String className) {
    ClassWriter cw = new ClassWriter(0);
    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();

    // System.out.println(System.getProperty("java.home"));
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
    mv.visitLdcInsn("java.home");
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "getProperty",
        "(Ljava/lang/String;)Ljava/lang/String;", false);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
        "(Ljava/lang/Object;)V", false);

    // ArrayList l1 = new ArrayList();
    int l1 = 1;
    mv.visitTypeInsn(NEW, "java/util/ArrayList");
    mv.visitInsn(DUP);
    mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V",
        false);
    mv.visitVarInsn(ASTORE, l1);
    // l1.add(0, "ABC");
    mv.visitVarInsn(ALOAD, l1);
    mv.visitInsn(ICONST_0);
    mv.visitLdcInsn("ABC");
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/ArrayList", "add",
        "(ILjava/lang/Object;)V", false);

    // List l2 = l1;
    int l2 = 2;
    mv.visitVarInsn(ALOAD, l1);
    mv.visitVarInsn(ASTORE, l2);
    // l2.add(1, "XYZ");
    mv.visitVarInsn(ALOAD, l2);
    mv.visitInsn(ICONST_1);
    mv.visitLdcInsn("XYZ");
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add",
        "(ILjava/lang/Object;)V", true);

    // System.out.println(l2);
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
    mv.visitVarInsn(ALOAD, l2);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
        "(Ljava/lang/Object;)V", false);

    // l2.sort(Comparator.reverseOrder());
    mv.visitVarInsn(ALOAD, l2);
    mv.visitMethodInsn(INVOKESTATIC, "java/util/Comparator", "reverseOrder",
        "()Ljava/util/Comparator;", true);
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "sort",
        "(Ljava/util/Comparator;)V", true);

    // System.out.println(l2);
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
    mv.visitVarInsn(ALOAD, l2);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
        "(Ljava/lang/Object;)V", false);

    mv.visitInsn(RETURN);
    mv.visitMaxs(3, 3);
    mv.visitEnd();
    cw.visitEnd();

    return cw.toByteArray();
  }

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