読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Javaクラス解析機を作る

Ruby Java クラス解析機

ACC_ABSTRACTとか調べているうちに、Javaクラスの解析機を作りたくなってしまいました。

  • 検索したらいっぱい見つかりそう。
  • つーか、javapでいいじゃん!

とか思いつつ、でもそんなのかんけーねぇ!

ということで、すみやかに着手。The JavaTM Virtual Machine Specification Second Editionshira - Byte Code (ByteCode) Formatを参考にしつつ、クラスファイルの先頭から以下のデータを読むところまで作ってみました。

名称 サイズ 説明
magic 4byte クラスファイルを識別するためのマジックナンバー。0xCAFEBABEが入る。
minor_version 2byte クラスのマイナーバージョン
major_version 2byte クラスのメジャーバージョン
constant_pool_count 2byte constant_pool (クラス名メソッド名などの定数が格納されるテーブル)の数
constant_pool 可変 クラス名メソッド名などの定数が格納されるテーブル。constant_pool_count-1個のconstant_poolが並ぶ。「文字列」や「Class情報」など種類がいくつかあって、それぞれデータ形式/サイズが異なる。
access_flags 2byte クラス/インターフェイスのアクセス権限情報
this_class 2byte このクラスファイルで定義されているクラスを示すconstant_poolのid

ここまでのコード

ここまでの解析コードは以下。

module Java
  # Java クラス
  class JavaClass

    CONSTANT_Class = 7
    CONSTANT_Fieldref = 9
    CONSTANT_Methodref = 10
    CONSTANT_InterfaceMethodref = 11
    CONSTANT_String = 8
    CONSTANT_Integer = 3
    CONSTANT_Float = 4
    CONSTANT_Long = 5
    CONSTANT_Double = 6
    CONSTANT_NameAndType = 12
    CONSTANT_Utf8 = 1

    ACC_PUBLIC    = 0x0001
    ACC_FINAL     = 0x0010
    ACC_SUPER     = 0x0020
    ACC_INTERFACE = 0x0200
    ACC_ABSTRACT  = 0x0400

    def initialize( class_data_io )
      @io = class_data_io
      @constant_pool = []
      read_magic
      read_version
      read_constant_pool
      read_access_flags
      read_class
    end

    # クラス名を取得
    def class_name
      cp = @constant_pool[@this_class_id]
      raise "illegal class data." if cp == nil || cp[:tag] != CONSTANT_Class
      get_string_from_constants( cp[:name_index] )
    end

    # クラスバージョンを文字列で取得する
    def version
      @major_version.to_s << "." << @minor_version.to_s
    end

    # クラスの文字列表現を得る
    def to_s
    <<STR
// version #{version}
class #{class_name} {}
STR
    end

private
    # magicを読む
    def read_magic
      magic = read( 4 )
      raise "illegal class data." if magic != 0xCAFEBABE
    end
    # バージョンを読む
    def read_version
      @minor_version = read( 2 )
      @major_version = read( 2 )
    end
    # constant_pool を読む
    def read_constant_pool
      constant_pool_count = read(2)
      1.upto(constant_pool_count-1) {|i|
        @constant_pool[i] = read_cp
      }
    end
    def read_cp
      tag = read( 1 )
      cp = {:tag=>tag}
      case tag
      when CONSTANT_Class
        cp[:name_index] = read(2)
      when CONSTANT_Fieldref, CONSTANT_Methodref, CONSTANT_InterfaceMethodref
        cp[:class_index] = read(2)
        cp[:name_and_type_index] = read(2)
      when CONSTANT_String
        cp[:string_index] = read(2)
      when CONSTANT_Integer
        cp[:bytes] = read(4)
      when CONSTANT_Float
        cp[:bytes] = read(4)
        # TODO float化
      when CONSTANT_Long
        cp[:high_bytes] = read(4)
        cp[:low_bytes] = read(4)
      when CONSTANT_Double
        cp[:high_bytes] = read(4)
        cp[:low_bytes] = read(4)
        # TODO double化
      when CONSTANT_NameAndType
        cp[:name_index] = read(2)
        cp[:descriptor_index] = read(2)
      when CONSTANT_Utf8
        length = read(2)
        cp[:bytes] = @io.read(length)
      else
        raise "unkown constant_pool_tag. tag =" << tag.to_s
      end
      return cp
    end
    # access_flagsを読む
    def read_access_flags
      @access_flags = read( 2 )
    end
    # クラス情報を読む
    def read_class
      @this_class_id = read( 2 )
      @super_class_id = read( 2 )
      interfaces_count = read( 2 )
      @interface_ids = []
      interfaces_count.times{|i|
        @interface_ids << read( 2 )
      }
    end

    # constant_poolからidに対応する文字列を取得する。
    def get_string_from_constants( id )
      cp = @constant_pool[id]
      raise "illegal constant pool id. id=" + id.to_s \
        if cp == nil || cp[:tag] != CONSTANT_Utf8
      return cp[:bytes]
    end

    # 指定したバイト数だけデータを読み込んで返す
    def read( size )
      res = 0
      size.times{|i| res = res << 8 | @io.getc }
      return res
    end

  end

  attr_reader :major_version, :minor_version
end

動かしてみる

HelloWorldクラスを用意して、

public class HelloWorld {
    public static void main ( String[] args ) {
        System.out.println( "Hello World!" );
    }
}

解析!

open( "./HelloWorld.class", "r+b" ) {|io|
  jc = Java::JavaClass.new io
  puts jc.to_s
}

実行結果です。

// version 49.0
class HelloWorld {}