netail.net
自作フリーソフトや,ゲームに関する雑記を公開してます.
日記はソフトウェア工学の論文ネタが中心です.
最近のお知らせ (古いものはこちら)
2009-12-27 [長年日記] ▲
_ [研究] Java ソースコード解析 対 バイトコード解析 ▲
メソッド呼び出し関係がほしくて久しぶりに Java のバイトコード解析を試してみたので,ソースコード解析と比べた場合の違いを整理してみました.
ソースコードとバイトコードの解析の違いを特徴づけるコード例は,以下のようなものがあります.
String name = target.getClient().getName();
このソースコード断片を含むファイルを解析してメソッド呼び出し関係を調べようという場合,まず target
変数の宣言を参照してクラス名を解決し,そのクラスのコードから, getClient()
メソッドのシグネチャを取得し,その戻り値の型あるいはその親クラスに getName()
メソッドが宣言されているかどうかを調べる必要があります.一方,コンパイル後のバイトコード上では,メソッド呼び出しの情報はすべて invokevirtual
などの命令に格納されていますので,別ファイルをいちいち参照しなくとも,コンパイル後のクラスファイル単体で,呼び出し先のメソッドの情報を獲得することができます.
当たり前のことと言われればそれまでですが,ソースコードとバイトコードの解析の大きな違いの1つは,ソースコード上での「他のソースファイルへの参照」が,バイトコード上では Java コンパイラによって解決済みの情報となっているという点です.
ソースコードとバイトコードの解析の違いには,以下のようなものが挙げられます.
- コメント
ソースコード上にのみ存在する情報です.バイトコード解析では,参照することができません.
- 制御構文情報
ソースコードに対して構文木を構築することで,制御構文の入れ子関係などにアクセス可能です.バイトコード上では,いわゆる
IF
とGOTO
からなる簡単な構造に変換されています.制御フローグラフを構築するにはバイトコードのほうが簡単ですが,break
やcontinue
などの出現が,ループの終了条件判定などと区別できない可能性があります. - 厳密なソースコード位置
バイトコードがどのソースコードに対応するかについては,実行可能な命令についてのファイル名と行番号の対応関係しか保存されません.1行に複数の文が並んでいるものを区別したい場合や,宣言(実行可能な命令を伴わない文)の位置情報がほしい場合には,ソースコードの構文解析が必要です.
- メソッド呼び出し
バイトコード上では,1つのクラス単体で,呼び出し対象のメソッドのシグネチャが取得可能です.一方,ソースコードの場合,別ファイルの情報を参照しなくてはなりません.大規模なプログラムを解析するとき,下手に実装するとメモリが不足しがちになるほか,必要なファイルが足りない場合への対応方法も考えておく必要があります.
- 動的束縛の解決
どちらの解析手法の場合も,動的束縛の解決可能性を調査するには,プログラムに含まれるすべてのクラスについて,メソッドシグネチャと階層情報を集める必要があります.バイトコード解析のほうが,JARファイルから列挙すればよいので,実装が容易です.
- 型名の解決
ソースコード上では,参照可能なファイル集合の中から,型名を解決する必要があります.Java の場合,
import
文でのワイルドカードの存在や,内部クラスの取り扱いのおかげで,自作するのはかなり大変です.一方,バイトコード上では,コンパイラが解決した完全限定名を利用することができるので,各クラスを個別に解析していくことができます.ただし,ソースコード解析のほうも,正確な結果が得られなくてもかまわない場合には,1クラスだけから可能な限り型推論する手法などの適用を検討する価値があると思います. - 不正なファイルへの対処
ソースコードの場合,Java のバージョンによって文法が変更されているので,それによって生じるコンパイルエラーへの対処が必要ですし,また,誤ってコンパイルエラーのあるファイルが投入される可能性もあります.バイトコードの場合は,異なるJDKバージョン用にコンパイルされているファイルや,デバッグ情報が付与されていないものに対処する必要があります.
- 言語仕様とコンパイラ実装への依存性
バイトコード解析の結果は,言語仕様上規定されていないような式の評価順序について,コンパイラが勝手に選んだ順序を反映していることがあります.また,コンパイル時点で適用された最適化の結果を反映することがあります.
ソースコード解析とバイトコード解析のどちらかが一方的に有利ということはありません.しかし,ソースコード解析のほうが実装の手間が大きく,また構文解析などの処理コストも無視できないので,個人的な好みでいうと,型情報などは事前にライブラリからバイトコード解析で抽出しておき,対象プログラムのソースコード解析は必要最小限に抑えるという方法を採用したいところです.