netail.net
自作フリーソフトや,ゲームに関する雑記を公開してます.
日記はソフトウェア工学の論文ネタが中心です.
最近のお知らせ (古いものはこちら)
2002-12-27 古い日記からの変換データ ▲
_ 論文 ▲
研究会の原稿を先生に渡して,最終チェックをお願いする.他の事務書類は全部預けたので,あとは最悪,実家に帰っても作業可能だ.
これで次の原稿に取り掛かれる :-)
それにしても,「工学博士」っていう表現は使わなくなったのね.全部「博士(工学)」に統一されたらしい.
2008-12-27 ▲
_ [Eclipse][Java][ツール] ブレークポイントごとに変数の内容を出力するプラグイン ▲
Java プログラムの動的解析を簡単に実行する方法として,Eclipse からのプログラム実行方法を色々と実験してます.
その副産物として,デバッグ実行時,ブレークポイントで停止したときの変数の内容をテキストファイルに書き出す Heapdumpプラグイン(仮) というのができたので,とりあえず現状のバイナリのみ置いておきます.
このプラグインをインストールすると,デバッグの構成ダイアログから,「Heapdump for Java」という構成が作れるようになります.この構成でプログラムをデバッグ実行すると,ブレークポイントで停止したとき,スタックフレームに載ってる変数の情報がテキストファイルに出力されます.変数名,型名,値の情報は,Eclipseデバッガの変数欄で格納できる文字列とまったく同じです.テキストファイルで読むほうが,オブジェクトのメンバーなどを細かく調べる作業を少しだけ楽にできるようになるだろう,と作ってみたものです.
オブジェクト参照を再帰的に探索する実装をしていますが,全オブジェクトを出力すると大変なことになってしまうので,実行時の構成では,以下のような設定項目を指定する必要があります.
- Output Directory: ファイル出力先のディレクトリ.ここに,ブレークポイントで停止するごとに,1.txt から順番にテキストファイルが作られます.
- Path of Variable Names and Type Names: 出力したいオブジェクトの変数名あるいは型名の条件を,スラッシュ区切りで指定します.デフォルトの
*
は,スタックフレーム上のすべての変数を意味します.スラッシュ以下は,オブジェクトや配列のメンバーを意味します.たとえば
this/int
なら,this に含まれた int 型のメンバー全員を出力します.また,x/Field*/*
なら,xという変数名あるいは型名で参照されたオブジェクトの情報を取得し,その中の「Field」という文字列で始まる変数名あるいは型名のフィールドで参照されているオブジェクトを列挙し,そのメンバー全員を出力します.スラッシュの数で,何階層下まで探索するかが決まります.大量に出力すると,ブレークポイントで停止したタイミングでの出力処理に時間がかかり,一時的に Eclipse の UI 更新が止まって見えます.
- Number of Stackframes: いくつのスタックフレームの内容を出力するかを指定します.
1なら実行中のメソッドから参照できる変数のみ,2ならそのメソッドの呼び出し側スタックの内容も出力する,というようになります.
- Max Number of Variables: いくつの変数の情報を出力したら探索を停止するかを指定します.
1週間くらいで作った即席ツールなので,あんまり洗練されてはいませんが,使ってみたい人はご自由にどうぞ.
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バージョン用にコンパイルされているファイルや,デバッグ情報が付与されていないものに対処する必要があります.
- 言語仕様とコンパイラ実装への依存性
バイトコード解析の結果は,言語仕様上規定されていないような式の評価順序について,コンパイラが勝手に選んだ順序を反映していることがあります.また,コンパイル時点で適用された最適化の結果を反映することがあります.
ソースコード解析とバイトコード解析のどちらかが一方的に有利ということはありません.しかし,ソースコード解析のほうが実装の手間が大きく,また構文解析などの処理コストも無視できないので,個人的な好みでいうと,型情報などは事前にライブラリからバイトコード解析で抽出しておき,対象プログラムのソースコード解析は必要最小限に抑えるという方法を採用したいところです.