bcelを使ってバイトコードを読む

そろそろバイトコードを読むお年頃になったので年初めにBCELを使って少し読んでみました。
BECLってのはジャカルタバイトコード操作ライブラリ。こんなものも揃っているなんて便利な世の中です。
DOMAIN ERROR

まずここから一式をDLしてサンプルを真似しながら解析するクラスを作る。あとは「java listclass -code TestBcel」てなかんじでさくっと実行。実行できたら気合いで読む!

まずは初歩

Stringを返すだけのGetterの場合

ソース

    public String getA () {
        return "Gara";
    }

バイトコード

public String getA()
Code(max_stack = 1, max_locals = 1, code_length = 3)
0:    ldc               "Gara" (16)      「Gara」をコンスタントプールに積む
2:    areturn                            オブジェクトを返値としてreturnする

ふむふむ。文字の生成して返却すると。そのまんまだね

変数を使ってみる。
    public String getA () {
        String aa = "Gara";
        return aa;
    }
public String getA()
Code(max_stack = 1, max_locals = 2, code_length = 5)
0:    ldc               "Gara" (16)      「Gara」をコンスタントプールに積む
2:    astore_1                           オブジェクトを1番目のローカル変数に格納。
3:    aload_1                            1番目のローカル変数からオブジェクトを取り出してスタックに積む
4:    areturn                            オブジェクトを返値としてreturnする

なるほど確かにさっきのに比べてローカル変数がちゃんと使われている。

Objectを返す場合
    public Date getB () {
        return new Date();
    }
public java.util.Date getB()
Code(max_stack = 2, max_locals = 1, code_length = 8)
0:    new               <java.util.Date> (22)            オブジェクトを生成して
3:    dup                                                トップスタックアイテムの値をコピーして更にスタックの上に積む((次にinvokeすると消えちゃうからコピーが必要))
4:    invokespecial     java.util.Date.<init> ()V (24)   インスタンス初期化メソッドの呼び出し
7:    areturn                                            オブジェクト返却

なるほど。バイトコードを見るとクラスがNEWされてからコンストラクタが呼ばれるまでに差が有るのが見える。*1

ちょっとがんばる

読めるようになってきたんで調子乗ってみた
    public void doAction() throws Exception {
        String a = "Pentasa";              1
        String b = "Gara";                 2
        
        if (b.equals(a)) {                 3
            System.out.println(b);         4
        } else {                           5
            a = null;                      6 
        }
        
        if (a == null) {                   7
            throw new Exception();         8
        }
    }
public void doAction()
                throws java.lang.Exception
Code(max_stack = 2, max_locals = 3, code_length = 39)
0:    ldc               "Pentasa" (31)        1さっきと同じでコンスタントプール
2:    astore_1                                1ローカル変数に格納
3:    ldc               "Gara" (16)           2
5:    astore_2                                2
6:    aload_2                                 3メソッド呼び出す前にスタックから取り出し
7:    aload_1                                 3
8:    invokevirtual     java.lang.String.equals (Ljava/lang/Object;)Z (33)
                                              3メソッド呼び出し
11:   ifeq              #24                   3スタックの値が0なら#24に飛ぶ((おおおおお!IFってこんなになってたのか!))
14:   getstatic         java.lang.System.out Ljava/io/PrintStream; (39)
                                              4Staticなクラスを取得する
17:   aload_2                                 4変数呼び出し
18:   invokevirtual     java.io.PrintStream.println (Ljava/lang/String;)V (45)
                                              4画面に出力
21:   goto              #26                   5ELSE句なのでGOTOで#26まで飛ぶ
24:   aconst_null                             6スタックにNULLを積む
25:   astore_1                                6
26:   aload_1                                 7
27:   ifnonnull         #38                   7コードは「a == null」だが最適化されたのかNotNULLで飛んでいる
30:   new               <java.lang.Exception> (29)
                                              8オブジェクトNewして
33:   dup                                     8コピーして
34:   invokespecial     java.lang.Exception.<init> ()V (51)
                                              8コンストラクタ呼び出し
37:   athrow                                  8スローする
38:   return                                  終わり

なるほど!IFってこんな風に内部はgotoで実現されているのか。

さらに調子に乗ってシンクロしてみた
    public void doAction2() throws Exception {
        String b = "Gara";                  1
        synchronized (b) {                  2
            b = "SIN";                      3
        }                                   4
        b = "END";                          5
    }
public void doAction2()
                throws java.lang.Exception
Code(max_stack = 2, max_locals = 3, code_length = 19)
0:    ldc               "Gara" (16)         1
2:    astore_1                              1
3:    aload_1                               1
4:    dup                                   2
5:    astore_2                              2
6:    monitorenter                          2ロック取得!!!
7:    ldc               "SIN" (54)          3
9:    astore_1                              3
10:   aload_2                               3
11:   monitorexit                           4ロック解放!!!
12:   goto              #18                 4正常終了時
15:   aload_2                               4エクセプションハンドラー部分
16:   monitorexit                           4例外時に解放
17:   athrow                                4
18:   ldc               "END" (56)          5
20:   astore_1
21:   return

Exception handler(s) =
From    To      Handler Type
7       12      15      <Any exception>(0)
15      17      15      <Any exception>(0)

シンクロの場合暗黙的に例外時の処理が追加されている。最初なんで追加されているのかわからなかった。

その他これを知ってると大体読める

  • sipush :byte,shortをスタックに積む
  • invokevirtual :メソッド呼び出し
  • invokeinterface :インターフェース呼び出し
  • invokespecial :親呼び出し(コンストラクタ呼び出しもこれ)
  • invokestatic :Staticメソッド呼び出し

詳細はhttp://ja.wikipedia.org/wiki/Java%E4%BB%AE%E6%83%B3%E3%83%9E%E3%82%B7%E3%83%B3

感想

BCELを今日知った(恥)僕でもすぐにさくさく読めたのでみんなもっとバイトコードを読むといいとおもう。ちょっとした発見とかいろいろあったのでこれはお勧めです。

*1:Singltonのダブルチェックがアンチパターンなのが実感できた。http://www.ibm.com/developerworks/java/library/j-dcl.html