すがブロ

sugamasaoのhatenablogだよ

静的コード解析ツール splint

splint という解析ツールがある

MacOSX だとデフォルトでは入っていないようだけど、port install splint でインストールできる。
これを使う事でバグが混在しやすいコードや、未使用の変数等を解析して教えてくれる。

ためしに使ってみる。

以前書いた正規表現のソースをチェックしてみる。
ソースはこんな内容

  1 /* $Id: regex.c 39 2008-05-19 16:25:42Z sugamasao $ */
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <regex.h>
  5 
  6 int main(void) {
  7     char* str = "<p>hoge</p>";
  8     regex_t reg;
  9     regmatch_t* pmatch;
 10     size_t nmatch;
 11 
 12     /* 正規表現オブジェクトの生成 */
 13     if (regcomp(&reg, ">([[:alpha:]].+)<", REG_EXTENDED) != 0) {
 14         puts("正規表現できません><");
 15         return 1;
 16     }
 17 
 18     /* 正規表現格納エリア確保 */
 19     nmatch = reg.re_nsub+1;
 20     pmatch = malloc(sizeof(regmatch_t) * nmatch);
 21 
 22     /* 正規表現実行 */
 23     if (regexec(&reg, str, nmatch, pmatch, 0) != 0) {
 24         puts("見つからなかったよ><");
 25         return 1;
 26     }
 27 
 28     /* 結果出力 */
 29     {
 30         int i = 0;
 31         int j = 0;
 32         for(i = 0; i < nmatch; i++) {
 33             printf("match pattern[%d] = ", i);
 34             for(j = pmatch[i].rm_so; j < pmatch[i].rm_eo; j++){
 35                 printf("%c", str[j]);
 36             }
 37             puts("");
 38         }
 39     }
 40 
 41     free(pmatch);
 42     return 0;
 43 }

これを、以下のオプションを付けて解析してみる。ちなみに、解析レベルはデフォルトの standard を使用した。最高レベルの strict を使うと気狂いのようなチェックを行ってくれるのであまり嬉しくなかった。

splint -posix-lib regex.c

こうした結果:

Splint 3.1.1 --- 23 May 2008

regex.c: (in function main)
regex.c:14:3: Return value (type int) ignored: puts("正規表�...
Result returned by function call is not used. If this is intended, can cast
result to (void) to eliminate message. (Use -retvalint to inhibit warning)
regex.c:19:14: Access field of abstract type (regex_t): reg.re_nsub
An abstraction barrier is broken. If necessary, use /*@access @*/ to
allow access to an abstract type. (Use -abstract to inhibit warning)
regex.c:23:33: Possibly null storage pmatch passed as non-null param:
regexec (..., pmatch, ...)
A possibly null pointer is passed as a parameter corresponding to a formal
parameter with no /*@null@*/ annotation. If NULL may be used for this
parameter, add a /*@null@*/ annotation to the function parameter declaration.
(Use -nullpass to inhibit warning)
regex.c:20:11: Storage pmatch may become null
regex.c:24:3: Return value (type int) ignored: puts("見つか�...
regex.c:25:12: Fresh storage pmatch not released before return
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)
regex.c:20:2: Fresh storage pmatch created
regex.c:32:14: Operands of < have incompatible types (int, size_t): i < nmatch
To allow arbitrary integral types to match any integral type, use
+matchanyintegral.
regex.c:34:8: Assignment of regoff_t to int: j = pmatch[i].rm_so
regex.c:34:29: Operands of < have incompatible types (int, regoff_t):
j < pmatch[i].rm_eo
regex.c:37:4: Return value (type int) ignored: puts("")

Finished checking --- 9 code warnings

こんな短いソースなのに、9個を指摘事項があるようだ。
14行目:puts の戻り値を使ってないエラー? これは無視しても良い類いだろう
19行目:うーん、本当はアクセスしちゃまずいフィールだってこと? よくわからん。もしアクセスするのがダメだったとして、どうやて最終的な正規表現結果を取れば良いのさ。
23行目:NULLが入る可能性があるってことか? たしかに、malloc 後の NULL チェックはしていない。
24行目:これは14行目と一緒だろう
25行目:どうやらメモリリークしているらしいぞ!!! 後で要検討や!
32行目:int型と size_t 型を比較しているようだ
34行目:型が違う代入がされている & 型が違うのに比較している?
37行目:14行目と同様
というわけで、すぐに直せるところは直してみた。
一部やっつけだけど

  1 /* $Id: regex.c 39 2008-05-19 16:25:42Z sugamasao $ */
  2 #include <stdio.h> 
  3 #include <stdlib.h>
  4 #include <regex.h>
  5 
  6 int main(void) {
  7     char* str = "<p>hoge</p>";
  8     regex_t reg;
  9     regmatch_t* pmatch;
 10     size_t nmatch;
 11 
 12     /* 正規表現オブジェクトの生成 */
 13     if (regcomp(&reg, ">([[:alpha:]].+)<", REG_EXTENDED) != 0) {
 14         printf("正規表現できません><\n");
 15         return 1;
 16     }
 17 
 18     /* 正規表現格納エリア確保 */
 19     nmatch = reg.re_nsub+1;
 20     pmatch = malloc(sizeof(regmatch_t) * nmatch);
 21     if (pmatch == NULL) {
 22         printf("メモリ確保失敗><");
 23         return 1;
 24     }
 25 
 26     /* 正規表現実行 */
 27     if (regexec(&reg, str, nmatch, pmatch, 0) != 0) {
 28         printf("見つからなかったよ><\n");
 29         return 1;
 30     }
 31 
 32     /* 結果出力 */
 33     {
 34         size_t i = 0;
 35         regoff_t j = 0;
 36         for(i = 0; i < nmatch; i++) {
 37             printf("match pattern[%d] = ", (int)i);
 38             for(j = pmatch[i].rm_so; j < pmatch[i].rm_eo; j++){
 39                 printf("%c", str[j]);
 40             }
 41             printf("\n");
 42         }
 43     }
 44 
 45     free(pmatch);
 46     return 0;
 47 }

このあと、またチェックツールを掛けてみる。

A flag is not recognized or used in an incorrect way (Use -badflag to inhibit
warning)
regex.c: (in function main)
regex.c:19:14: Access field of abstract type (regex_t): reg.re_nsub
An abstraction barrier is broken. If necessary, use /*@access @*/ to
allow access to an abstract type. (Use -abstract to inhibit warning)
regex.c:29:12: Fresh storage pmatch not released before return
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)
regex.c:20:2: Fresh storage pmatch created

Finished checking --- 2 code warnings

まだ二つ残っている。ちょっとフィーリングでやるには難しいので日本語に翻訳する\(^o^)/

旗は認識されたか不正確な方法で使用された(警告を禁止するのに-badflagを使用する)
regex.cではありません: (機能メインの)
regex.c: 19:14: 抽象型(regex_t)の分野にアクセスしてください: reg.re_nsub An抽象
化バリアは起伏が多いです。 If necessary, use /*@access @*/ to
allow access to an abstract type. (警告を禁止する使用要約)
regex.c: 29:12: リターンAメモリリークの前にリリースされなかった新鮮なストレージ
pmatchは検出されました。 それの最後の参照が無くなる前に局所的に割り当てられたス
トレージはリリースされません。 (警告を禁止するのに-mustfreefreshを使用します)
regex.c: 20:2: pmatchが作成した新鮮なストレージ

チェックし終わっています。--- 2 コード警告

ふむ。二つ目の方は、つまり、20行目で確保したメモリを解放する前にロジックを抜ける箇所があるよって事か。
確かに、正規表現実行時のエラーチェックでメモリの解放を行わずに抜けている。試しに、そこにも free を入れてみよう。

 26     /* 正規表現実行 */
 27     if (regexec(&reg, str, nmatch, pmatch, 0) != 0) {
 28         free(pmatch); // <---------ここを追加
 29         printf("見つからなかったよ><\n");
 30         return 1;
 31     }

これで再度実行する。
おぉ、減ったぞ!

Splint 3.1.1 --- 23 May 2008

Command Line: Unrecognized option: -waek
A flag is not recognized or used in an incorrect way (Use -badflag to inhibit
warning)
regex.c: (in function main)
regex.c:19:14: Access field of abstract type (regex_t): reg.re_nsub
An abstraction barrier is broken. If necessary, use /*@access @*/ to
allow access to an abstract type. (Use -abstract to inhibit warning)

Finished checking --- 1 code warning

んーーーーーーーー、これはよくわからないなぁ。抽象型でアクセスしろっつってもなぁ><
わかる人、エロい人がいたら教えて下しあ!

というわけで

このプログラムではほとんど実害は無いけれど、メモリリークの可能性を検出できた。これは目視でやるよりもかなり良いのではないか。
というか、 standard でもチェックが厳しすぎるのかもれないな。
戻り値に対するチェックをしない、とかのオプションはあるものの、それをした事によって何らかのエラーを見逃す可能性もあるし、チェックを弱くするにせよ抑制するにせよ、ちょっと悩ましいところだ。
自分はこう使っているよ! とかのノウハウがあれば教えて下しあ!