C/C++言語の静的解析ツール Splintの紹介です。


C/C++言語の静的解析ツール、使っていますか?仕事で使う場合は有償のもの(QACやpgreliefなど)を使うことがおおいと思います。しかし休みの日に家でコードを書いたりするときにも個人で静的解析ツールを使いたい場合もあると思います。そこで今回は、フリーの静的解析ツール Splintについて説明します。 静的解析ツールを使うことによってコードのバグを減らして品質を上げることができます。また、休みの日に孤独にコードを書いているときでも、 レビューの代わりとして使って自身のコーディング能力をアップさせることもできます。

Splint のインストール

Splintでは「インストール用のバイナリはあまり更新されないから、ソースコードからビルドすることをオススメするよ!」というようなことが書いてあります。 が、自分でビルドするのは手間なので、今回はインストール用のバイナリを素直に使う方法を説明します。

Mac OS X の場合

Homebrew を使えば簡単です。

1
brew install splint

Linux の場合

Linuxでもパッケージマネージャが対応していれば簡単です。Ubuntuの場合は下記でOKです。

1
sudo apt-get install splint

Windows にインストールする場合

Windows向けのインストーラも提供されています。インストール自体は簡単です。他のOSと違って、インストール後に3つの環境変数の設定が必要なのでそれを忘れないでください。

まず、splint_win32に .msi と .zipが置いてありますので、どちらかをダウンロードして、インストールします。 .msiでGUIなインストーラを使用してもいいですし、.zipを解凍して任意のとこに置いてもいいですね。

次に下記の3つの環境変数を設定します。設定の方法は特に特別な手順は必要ありません。

  • LARCH_PATH
  • LCLIMPORTDIR
  • include

普通に、マイコンピューターのアイコンを右クリックして「詳細設定」タブをクリックして「環境変数」から設定すればいいです。 もちろん、コマンドプロンプトから設定してもいいです。

環境変数 LARCH_PATH の設定

LARCH_PATHには、Splintをインストールしたフォルダの直下にあるlibというフォルダのパスを設定してください。 コマンドプロンプトで行うなら下記のようにすればOKです。 ここではSplintをC:\Programs\ の下にインストールしたという前提です。

1
set LARCH_PATH=C:\Programs\splint\lib\

環境変数 LCLIMPORTDIR の設定

LCLIMPORTDIRには、Splintをインストールしたフォルダの直下にあるimportsというフォルダのパスを設定してください。 コマンドプロンプトで行うなら下記のようにすればOKです。

1
set LCLIMPORTDIR=C:\Programs\splint\imports\

環境変数 include の設定

最後にincludeの設定です。これにはstdlib.hなどの標準ヘッダファイルのあるフォルダのパスを指定してください。

この変数が設定されていないと、下記のように標準ライブラリのヘッダファイルが見つからないというエラーが出ますので注意してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/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 ファイルを渡すだけです。

1
splint -I./ -I../my_include +weak +posix-lib hoge.c

ちなみに実際の実行結果は下記のようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ /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を使うべきでしょう。

1
2
3
../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)

どのようなフラグがあるかは、Appendix Bを参照してください。

Makefile の書き方の例

Splintの利用例として、Makefile をどのように書けばいいかを説明します。

インクルードパスの設定

前述した通り、Splintを使うときにはヘッダファイルのあるパスを指定しなければいけません。 すでにMakefileでビルド環境が構築している場合は簡単に設定できます。 例えば下記のような、.cファイルから.oファイルをコンパイルするためのパタンルールがあったとします。 CFLAGS変数にヘッダのパス設定-Iも含まれているとします。

1
2
3

%.o : %.c
    $(CC) $(CFLAGS) -c $< -o $*.o

このCFLAGSから-Iオプションだけ取り出すには $(filter -I%,\$(CFLAGS))とすればOKです。下記のようにすればOKです。

1
2
3
4
5
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です。

1
2
3
4
5
6
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にしてみました。

1
2
3
4
5
6
7
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 が中断されてしまいます。 make にて途中でエラーが出ても全コマンドを実行する方法にその対処法を書いていますのでご参照ください。

最終的には下記のようにすればOKです。

1
2
3
4
5
6
7
8
9
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

gnu-make-framework-zenMakefile 全体を置いていますので、興味があればみてみてくださいませ。

まとめ

フリーの静的解析ツール Splintのインストールと設定の仕方から Makefile の設定方法までを説明しました。 かなり細かいことまで指摘してくれるツールであり、またフリーですので、いちど試してみてはいかがでしょうか? おもいもよらぬ指摘がでたりするかもしれませんよ!