CscopeとSilentBobによるソースコードの解析
両ツールは、シンボル定義の検索、特定の関数が使われている箇所や関数間の呼び出し関係の確認、コードベース全体からの文字列やパターンの検索に活用できる。また、ソースファイル群に対して手作業でgrepをかけるよりも、目的とする検索を迅速に行えるため、時間の節約にもなる。
Cscopeを使用する
Cscopeはよく知られたユーティリティで、最近のディストリビューションにはたいてい含まれている。もともとCscopeはC言語のコードで使うことしか想定されていなかったが、実際にはC++やJavaのような言語でも十分に使える。CscopeはncursesベースのGUIを備えているが、EmacsやVimといった主要なエディタなど、フロントエンドとして使える他のアプリケーションと連携するためのコマンドラインインタフェースもサポートしている。
Cscopeを起動すると、カレントディレクトリ内にあるソースファイルのスキャンが行われ、収集された情報がCscopeの内部データベースに格納される。-R
オプションを指定することで、サブディレクトリを再帰的にスキャンすることもできる。このデータベースに対するクエリをCscopeのGUIを使わずに別のアプリケーション(以降の記述を参照)から発行する場合には、-b
オプションを指定する。大規模なプロジェクトやシステム関連のプロジェクトでCscopeを使う場合は、こちらのガイドを参照し、Cscopeを最適化して大量のファイルを高速処理するための詳しい手順を確認すること。
デフォルトでは、データベースの生成が終わると(生成済みデータベースを利用する際は、-d
オプションで指定)、自動的にCscopeのGUIフロントエンドが起動される。このGUIフロントエンドのインタフェースは、2つのパネルからなる。下側のパネルに検索クエリを入力すると、検索結果が上側のパネルに表示される。Tabキーを押すとパネルが切り換わり、Ctrl-Dキーでプログラムが終了する。
下部パネルでは、カーソルキーを使って検索フィールド間の移動を行う。以下に、下部パネルで可能な操作を示す。
- 指定したシンボルの検索
- グローバル定義の検索(ヒット時には直ちにエディタが起動される)
- 指定した関数から呼び出される関数の検索
- 指定した関数を呼び出す関数の検索
- 全ソースファイルを対象としたテキスト文字列の検索
- 文字列の置換
- egrepパターンの検索
- エディタによる指定ファイルのオープン
- 指定ファイルをインクルード(#include)しているファイルの検索
検索を実行するたびに、その結果が検索番号、ファイル名、関数名(該当する場合のみ)、行番号、該当するコード行とともに表示される。検索結果の1つを、カーソルキーとEnterキー、または検索番号に対応する数字キーを使って選択すると、システムのデフォルトエディタ(EDITOR環境変数で指定されているもの)が起動し、該当する検索結果の行にカーソルが置かれた状態でそのファイルが開かれる(サポート対象外のエディタではこのとおりに動作しないことがあるが、EmacsやVimであれば問題ない)。
フロントエンドとして(X)Emacsを使用する
CscopeをEmacsと連携させて使うのは難しくない。cscope-
で始まるコマンドやCscopeメニューがまだEmacsで使えるようになっていない場合は、xcscope.elをインストールすることでEmacsをCscopeと連携させることができる。xcscope.elは、Cscopeのソースtarballのcontrib/xscopeサブディレクトリに入っている。まず、cscope-indexerというスクリプトを$PATHに指定された適当なディレクトリに、またxcscopeファイルをEmacsから参照できるディレクトリ(EmacsでC-h v load-path
と入力してload-path変数を確認するとよい)にそれぞれコピーする。次に、(require 'xcscope)
という行を~/.emacsまたは~/.emacs.d/init.elに追加する。
xcscope.elファイルの冒頭にあるコメントが、マニュアル代わりになっている。
このパッケージにより、Cscopeの検索コマンドがすべてEmacsのCscopeサブメニューに追加され、ソースファイル編集中にEmacs風のキーバインドによって使えるようになる。例えば、シンボルを検索する場合は、メニューから「Cscope」→「Symbol」を選択するか、M-x cscope-find-this-symbol
と入力するか、C-c s s
とキー入力するかのいずれかを行ってからシンボル名を入力する(シンボル名の入力がない場合はカーソルの置かれている単語が検索語になる)。
検索結果は、ファイルごとにグループ化されて<関数名>[<行番号>] <行>という形で*cscope*バッファに表示される。ヒットした検索結果の1つにカーソルを移動させてSpaceキーを押すと、検索にヒットした行にカーソルが置かれた状態で該当ファイルが別のバッファで開かれる。このときSpaceキーの代わりにEnterキーを押すと(あるいは関連する検索結果をクリックしてもよい)、*cscope*バッファがこのバッファに切り換わる。また、n
キーを押すと次の検索結果に、p
キーを押すと1つ前の検索結果に移動する(*cscope*バッファがアクティブでない場合は、それぞれC-c s n
とC-c s p
がこれらに対応)。同様に、N
キーで次のファイルが、P
キーで前のファイルが選択される(それぞれC-C s N
、C-c s P
でも可)。
VimからCscopeを使う
EmacsよりVimが好みだという人にもCscopeは対応している。まず何よりも現在使っているVimが--enable-cscope
オプションを有効にしてコンパイルされている必要があるが、Linuxのバイナリディストリビューションの大半ではそのようになっている。ただし、Gentooユーザの場合は、cscope
USEフラグを有効にしておく必要がある。以降では、Vimの6.xまたは7.xが使われていると仮定して話を進める。Vimのリファレンスマニュアルには、Cscopeインタフェースの利用法に関する項目が含まれている。これにあたる/usr/share/vim/vim&version/doc/if_cscop.txtというファイルが見つかるはずだ。また、CscopeをVimで使うための簡単なチュートリアルも存在する。
Vimでは、:cscope find search type search string
という形式でCscopeの検索コマンドを実行する(cscope find
の部分は:cs f
で代用可)。search type(検索タイプ)には、以下のものがある。
-
symbol
ors
— すべてのシンボル参照を検索 -
global
org
— グローバル定義を検索 -
calls
orc
— 指定した関数の呼び出し箇所を検索 -
called
ord
— 指定した関数が呼び出す関数を検索 -
text
ort
— テキスト検索を実行 -
file
orf
— ファイルを開く -
include
ori
— 指定ファイルをインクルードしているファイルを検索
検索結果は、Vimウィンドウの一番下にメニュー形式で表示される。検索結果の番号を入力してEnterキーを押すと、該当する検索結果の部分にジャンプできる。また、:cscope
の部分を:scscope
(または:scs
)にしてコマンドを実行すると、Vimウィンドウが上下に2分割され、新しいウィンドウに検索結果が表示される。
Vimでは、Cscopeクエリによる検索結果へのジャンプが、任意のタグへのジャンプによく似ている。つまり、Ctrl-T
キーを使って検索実行前の表示画面に戻り、:tnext
や:tprevious
を使って検索結果の画面を切り換えることができるのだ。
カーソルの置かれている語を検索したい場合は、cscope_maps.vimプラグインをインストールする必要がある(同ファイルを$HOME/.vim/pluginディレクトリに追加するだけ)。詳しい説明は、同ファイルにコメントとして記述されている。このcscope_mapsプラグインを利用するには、:cscope
と入力する代わりにCtrl-\
キーを、:scscope
の代わりにCtrl-Space
キーを押す。すると、カーソルの置かれている語が検索される(例:「initialize」という語にカーソルを置いた状態でCtrl-\ s
とすると「initialize」へのシンボル参照の検索が実行される)。
SilentBob
ソースコード解析用のもう1つの新しいツールが、SilentBobである。現状ではC/C++、Perl、Pythonに対応しているが、プラグイン・フレームワークの採用により、新たな言語やほかの機能にも容易に対応できる。
ソースのtarballとdebパッケージは、SilentBobのWebサイトにあるダウンロード・セクションから入手できる。インストール終了後、ソースコードのあるディレクトリで以下のようにSilentBobの実行を3回行う。
bob --make-ctags bob --cfiles bob -L cfiles --call-tags
すると、普通のタグテーブルtags
、C/C++のファイルリストcfiles
、呼び出しタグテーブルcall_tags
という3つのファイルができる。呼び出しタグテーブルには、関数呼び出しのタグが含まれている。これは、ある関数のすべての呼び出しを探し出す場合に、呼び出しタグテーブルの利用(:set tags=./call_tags
)をVimに指示して、タグ検索と同じコマンド群(:tag function-name
、検索結果の切り換えには:tnext
および:tprevious
)が使えるようにするためである。これらのインデックスファイルは、SilentBobが呼び出し木や逆向き呼び出し木(backward call tree)を構築する際に使われる。
ただし、PerlやPythonの場合は、タグテーブルとファイルリストだけがサポートされている。
bob <--perl | --python> --make-ctags bob <--perl | --python> --files
2番目のコマンドでは、perl_files
またはpython_files
というリストファイルが作成される。
タグテーブルができると、次のコマンドによって呼び出し木の表示ができるようになる。
bob [--depth N] function
--depth
オプションを指定すると、表示する呼び出し木の深さを制限できる。functionから直接呼び出される関数だけを知りたい場合は、--depth 0
とすればよい。それ以外の場合は、functionから、指定した深さ以下の個数の関数を経由して呼び出される関数群が表示される。
なお、このオプションは、PythonやPerlのファイルに対してSilentBobが生成したタグテーブルにも使うことができる。ただし、Exuberant Ctagsによって生成されたテーブルには対応していない。
呼び出しタグテーブルは、次のような逆向き呼び出し木の生成に使われる。
bob [--depth N] -u function
このコマンドにより、functionを、指定した深さに応じた個数の関数を経由して呼び出す関数群が表示される。この場合、--depth 1
とすると、functionを直接呼び出す関数だけを確認できる。
SilentBobでは、生成されたファイルcfilesを使ってC/C++コード内のテキストを検索することもできる。ただし、チェックの対象となるのは演算子であり、文字列やコメントは無視される。
bob list of files --cgrep pieces of text(カンマ区切り)
生成されたファイルリストを使用するには、-L ./cfiles
を指定する。また、上記のカンマ区切りテキスト群(pieces of text)は、演算子ごとに指定する必要がある。そのため、変数Tのテスト箇所を探し出す場合には、次のように指定する。
bob -L ./cfiles --cgrep if,T
SilentBobにはtags
ユーティリティも用意されており、C/C++ファイルのタグ定義をコンソール内で確認することができる。ソースコードから関心のあるコード断片 ― 関数定義、グローバル変数宣言など ― を抽出したい場合は、tags tag1 tag2 ... tagN
の実行により、必要なコード断片が取得できるようになっている。
タグ生成にSilentBobを使う理由 |
---|
SilentBobは構文解析を利用してソースファイルを解析する。そのため、正規表現を利用してマッチする行をファイルから探し出すExuberant Ctagsのようなユーティリティよりも高速な解析が行える。試しにLinuxカーネル(バージョン2.6.19)を解析させたところ、Exuberant Ctagsではタグテーブルの生成に90秒かかったが、SilentBobでは10秒しかかからなかった(いずれも2.6MHzのCerelonマシンで実行)。また、SilentBobは、マルチスレッド処理による最適化にも対応している。 もう1つの違いは、タグテーブルの形式である。Exuberant Ctagsで作成されたテーブルでは、正規表現を用いてファイル内の該当行の位置を特定する。そのため、ファイルの編集によって定義の位置が変わってもタグテーブルを再構築する必要がない。このように、頻繁にタグテーブルを生成しなおす必要がないというのがExuberant Ctagsの利点である。その反面、巨大なファイルの閲覧時には、行番号を利用してタグ定義にジャンプする方式よりもずっと時間がかかる。そのため、SilentBobでは、タグテーブルに行番号が使われている。ただし、大幅な編集を加えるたびにタグテーブルの更新が必要になるのは上述の通りである。 |