C/C++言語の静的解析ツール、使っていますか?仕事で使う場合は有償のもの(QACやpgreliefなど)を使うことがおおいと思います。しかし休みの日に家でコードを書いたりするときにも個人で静的解析ツールを使いたい場合もあると思います。そこで今回は、フリーの静的解析ツール Splint について説明します。
静的解析ツールを使うことによってコードのバグを減らして品質を上げることができます。また、休みの日に孤独にコードを書いているときでも、
レビューの代わりとして使って自身のコーディング能力をアップさせる
こともできます。
Splint のインストール
Splint 公式サイト では「インストール用のバイナリはあまり更新されないから、ソースコードからビルドすることをオススメするよ!」というようなことが書いてあります。が、自分でビルドするのは手間なので、今回はインストール用のバイナリを素直に使う方法を説明します。
Mac OS X の場合
Homebrew を使えば簡単です。
$ brew install splint
Linux の場合
Linux でもパッケージマネージャが対応していれば簡単です。Ubuntuの場合は下記でOKです。
$ sudo apt-get install splint
Windows にインストールする場合
Windows 向けのインストーラも提供されています。インストール自体は簡単です。他のOSと違って、インストール後に3つの環境変数の設定が必要なのでそれを忘れないでください。
まず、下記のサイトに .msi と .zip が置いてありますので、どちらかをダウンロードして、インストールします。.msi でGUIなインストーラを使用してもいいですし、.zipを解凍して任意のとこに置いてもいいですね。
次に下記の3つの環境変数を設定します。設定の方法は特に特別な手順は必要ありません。
普通に、マイコンピューターのアイコンを右クリックして「詳細設定」タブをクリックして「環境変数」から設定すればいいです。もちろん、コマンドプロンプトから設定してもいいですし、MSYS2のターミナル上でやってもいいです。
環境変数 LARCH_PATH の設定
「LARCH_PATH」には、Splintをインストールしたフォルダの直下にある「lib」というフォルダのパスを設定してください。
コマンドプロンプトで行うなら下記のようにすればOKです。ここではSplintを C:\Programs\ の下にインストールしたという前提です。
set LARCH_PATH=C:\Progarams\splint\lib\
MSYS2 の場合は下記のようになりますね。
LARCH_PATH=/C/Progarams/splintlib/ export LARCH_PATH
環境変数 LCLIMPORTDIR の設定
「LCLIMPORTDIR」には、Splintをインストールしたフォルダの直下にある「imports」というフォルダのパスを設定してください。
コマンドプロンプトで行うなら下記のようにすればOKです。
set LCLIMPORTDIR=C:\Progarams\splint\imports\
MSYS2 の場合は下記のようになりますね。
LCLIMPORTDIR=/C/Progarams/imports/ export LCLIMPORTDIR
環境変数 include の設定
最後に「include」の設定です。これにはstdlib.hなどの標準ヘッダファイルのあるフォルダのパスを指定してください。
MSYS2上で開発している場合は、下記のようにすればOKです。
inclukde=/usr/include/ export include
この変数が設定されていないと、下記のように標準ライブラリのヘッダファイルが見つからないというエラーが出ますので注意してください。
/c/Programs/splint/bin/splint.exe +skip-sys-headers +posixlib -I../src/modules/embunit -I../src/targets/sample_embunit ../src/targets/sample_embunit/person.c || : Splint 3.1.2 --- 25 Aug 2010 ../src/targets/sample_embunit/person.c(1,19): Cannot find include file stdio.h on search path: ../src/modules/embunit;../src/targets/sample_embunit;C:/include;C:/local/include Preprocessing error. (Use -preproc to inhibit warning) ../src/targets/sample_embunit/person.c(2,20): Cannot find include file stdlib.h on search path: ../src/modules/embunit;../src/targets/sample_embunit;C:/include;C:/local/include ../src/targets/sample_embunit/person.c(3,20): Cannot find include file string.h on search path: ../src/modules/embunit;../src/targets/sample_embunit;C:/include;C:/local/include Preprocessing error for file: ../src/targets/sample_embunit/person.c *** Cannot continue.
使い方
使い方は簡単です。下記のような感じで .c ファイルを渡すだけです。
$ splint -I./ -I../my_include +weak +posix-lib hoge.c
ちなみに実際の実行結果は下記のようになります。
$ /c/Programs/splint/bin/splint.exe +skip-sys-headers +posixlib -I../src/modules/embunit -I../src/targets/sample_embunit ../src/targets/sample_embunit/AllTests.c Splint 3.1.2 --- 25 Aug 2010 ../src/targets/sample_embunit/AllTests.c: (in function main) ../src/targets/sample_embunit/AllTests.c(9,22): New fresh storage (type TestRef) passed as implicitly temp (not released): CounterTest_tests() A memory leak has been detected. Storage allocated locally is not released before the last reference to it is lost. (Use -mustfreefresh to inhibit warning) ../src/targets/sample_embunit/AllTests.c(10,22): New fresh storage (type TestRef) passed as implicitly temp (not released): PersonTest_tests() ../src/targets/sample_embunit/AllTests.c(6,15): Parameter argc not used A function parameter is not used in the body of the function. If the argument is needed for type compatibility or future plans, use /*@unused@*/ in the argument declaration. (Use -paramuse to inhibit warning) ../src/targets/sample_embunit/AllTests.c(6,27): Parameter argv not used Finished checking --- 4 code warnings
引数のオプションについて、ひとまず使うために必要なことを下記に説明します。
ヘッダファイルの探索パスの設定
splint がヘッダファイルを探し出せるように、パスを教えてあげる必要があります。stdlib.hなどの標準ライブラリは、前述した環境変数 include で指定されたところを探しに行きます。
自分で作ったヘッダファイルは、別途教えてあげる必要があります。
方法は、gccなどの通常のコンパイラと同じです。「-I」を使います。例えばカレントディレクトリと../my_includeというディレクトリにヘッダファイルがある場合は、
「-I./ -I../my_include」とすればOKです。通常のコンパイラと一緒ですね。ですのでコンパイルしている時のオプションをそのまま渡せば基本的にOKです。
ただし1点だけ注意が必要です。コンパイラの場合は-I とパスの間にスペースがあっても構いません。例えば「-I ./ -I ../my_include」でもOKです。ですが splint はダメです。必ず -I の直後にはスペースをつけずに、すぐにパスを書くようにしてください。
標準ライブラリなどの設定
どのライブラリを使うかの設定が必要です。デフォルトではC言語のライブラリ(ANSI)が設定されています。もし、C言語の標準でないPOSIX関数を使っている場合は「posix-lib」を指定してください。よくわからなければPOSIXにしておけば大丈夫でしょう。
ここで注意点があります。ライブラリを指定するときはハイフン(-)でなくプラス(+)を使用してください。つまり「splint -posix-lib」でなく「splint +posix-lib」としてください。基本的にSplintではハイフン(-)は無効化、プラス(+)は有効化を示します。
フラグの設定
どのような指摘を有効にするか、または無効にするかを細かく指定できます。ただし、通常のLinuxコマンドとは作法が異なるので注意してください。
普通のコマンドは、「-hoge」のようにハイフン(-)でオプションんを指定しますが、Splintはプラス(+)とハイフン(-)を使い分けなければいけません。
プラス(+)は有効か、ハイフン(-)は無効化の意味となります。例えば「hoge」という指摘を有効にしたい場合は「-hoge」でなく「+hoge」とします。「-hoge」は無効にするという意味になってしまいます。
例として、バッファーオーバーフローにつながる可能性のある関数を使用している場合に指摘する bufferoverflowhigh を有効にする場合、
「splint +bufferoverflowhigh」のように指定します。すると下記のような指摘が出力されます。
ちなみにこれは sprintf を使っているから怒られていますね。snprintf を使うべきでしょう。
../src/targets/sample_embunit/person.c:89:4: Buffer overflow possible with sprintf. Recommend using snprintf instead: sprintf Use of function that may lead to buffer overflow. (Use -bufferoverflowhigh to inhibit warning)
どのようなフラグがあるかは、下記のサイトを参照してください。
公式サイト(英語)
http://splint.org/manual/html/appB.html
日本語訳サイト
https://ja.osdn.net/projects/splint-jp/wiki/appB
Makefile の書き方の例
Splintの利用例として、Makefile をどのように書けばいいかを説明します。
インクルードパスの設定
前述した通り、Splintを使うときにはヘッダファイルのあるパスを指定しなければいけません。すでにMakefileでビルド環境が構築している場合は簡単に設定できます。
例えば下記のような、.cファイルから.oファイルをコンパイルするためのパタンルールがあったとします。CFLAGS変数にヘッダのパス設定「-I」も含まれているとします。
%.o : %.c $(CC) $(CFLAGS) -c $< -o $*.o
このCFLAGSから「-I」オプションだけ取り出すには $(filter -I%,$(CFLAGS)) とすればOKです。下記のようにすればOKです。
SPLINT_FLAGS = $(filter -I%,$(CFLAGS)) %.o : %.c $(SPLINT) $(filter -I%,$(CFLAGS)) $< $(CC) $(CFLAGS) -c $< -o $*.o
ここで注意点が1つあります。
コンパイラは「-I ../my_include」のように -I とパスの間にスペースがあってもOKですが、SplintではNGです。
-I のあとにスペースを入れないでください。「-I../my_include」のようにしてください。
さいごに、システムのヘッダファイルを解析しないようにします。通常システムに問題はないはずですし、指摘があってもおいそれと変更できませんしね。
オプションは +skip-sys-headers をつければOKです。
SPLINT_FLAGS = $(filter -I%,$(CFLAGS)) SPLINT_FLAGS += +skip-sys-headers %.o : %.c $(SPLINT) $(SPLINT_FLAGS) $< $(CC) $(CFLAGS) -c $< -o $*.o
ライブラリの設定
プログラムがつかうライブラリを指定してあげる必要があります。 ANSIかPOSIXかUnixのどれかを選択します。ANSIなら +ansi-lib、POSIXなら +posix-lib、Unixなら +unix-libのオプションをわたします。μITRONのようなLinuxコマンドを使わない環境ならANSI、LinuxなどはPOSIXとすればよいかと思います。ここではPOSIXにしてみました。
SPLINT_FLAGS = $(filter -I%,$(CFLAGS)) SPLINT_FLAGS += +skip-sys-headers SPLINT_FLAGS += +posix-lib %.o : %.c $(SPLINT) $(SPLINT_FLAGS) $< $(CC) $(CFLAGS) -c $< -o $*.o
終了ステータスの処理
Splintは一つでも指摘があるとエラーステータスを返します。そのため、そこでmake が中断されてしまいます。
下記の記事にその対処法を書いていますのでご参照ください。
最終的には下記のようにすればOKです。
SPLINT_FLAGS = $(filter -I%,$(CFLAGS)) SPLINT_FLAGS += +skip-sys-headers SPLINT_FLAGS += +posix-lib IGNORE_EXIT_STATUS = || : %.o : %.c $(SPLINT) $(SPLINT_FLAGS) $< $(IGNORE_EXIT_STATUS) $(CC) $(CFLAGS) -c $< -o $*.o
下記の GitHub のリポジトリに Makefile 全体を置いていますので、興味があればみてみてくださいませ。
まとめ
フリーの静的解析ツール Splint のインストールと設定の仕方からMakefileにいれこむ方法までを説明しました。
かなり細かいことまで指摘してくれるツールであり、またフリーですので、いちど試してみてはいかがでしょうか?
おもいもよらぬ指摘がでたりするかもしれませんよ!
コメント