2014年2月3日月曜日

[Cppcheck] cfg ファイル設定

Cppcheck 1.63 がリリースされました。
といっても大分前ですが…
調べたり検証したり別のことやってたりしてたら時間がかかってしまいました。

トラブってました
1.63 を早速インストールしたところ、下記のようなエラーが…
(information) Failed to load std.cfg. Your Cppcheck installation is broken, please re-install. The Cppcheck binary was compiled without CFGDIR set. Either the std.cfg should be available in cfg or the CFGDIR should be onfigured.
cygwin から実行すると動作して、コマンドプロンプトからだと上記エラーが…
どうしたものかと調べていたら、1.63.1 がリリースされました。
こちらをインストールしたところ、問題なく実行できました。

メデタシメデタシ。ですが、転んでもただでは起きません。
.cfg ファイルは今までノータッチでしたので、少し調べてみました。

書式
<?xml version="1.0"?>
  <def>
    <function name="ZeroMemory">
      <noreturn>false</noreturn>
    </function>
  </def>
マニュアルより引用

.cfg ファイルの中身は XML になっています。
<def> ノードの中に定義を書きます。

リークチェック
Cppcheck にはメモリ/リソースのリークチェック機能があります。
ただし、この機能を使うためには確保/解放関数を Cppcheck に教えてあげる必要があります。
定義は .cfg ファイルの <memory> および <resource> ノードに書きます。
<resource>
  <alloc>CreatePen</alloc>
  <dealloc>DeleteObject</dealloc>
</resource>
マニュアルより引用

alloc ノードに確保、dealloc ノードに解放関数を定義します。
allco ノードには init 属性を付けることができ、確保されたメモリ/リソースが初期化されるかどうかを true/false で指定できます。

関数
関数は function ノードに定義します。
function ノードでは <arg nr="1"> のようにすることで、各引数に条件を持たせられます。
(nr の値は引数の順番)

未初期化チェック
<function name="CopyMemory">
  <arg nr="2">
    <not-uninit/>
  </arg>
</function>
マニュアルより引用
not-uninit サブノードを追加することで未初期化変数を引数に渡された場合に警告を出すようにできます。

nullptr チェック
<function name="CopyMemory">
  <arg nr="1">
    <not-null/>
  </arg>
</function>
マニュアルより引用
not-null サブノードを追加することで関数引数の NULL チェックができます。

範囲 チェック
<function name="do_something">
  <arg nr="1">
    <valid>0-1023</valid>
  </arg>
</function>
マニュアルより引用
valid サブノードに有効な値の範囲を記述することで範囲外チェックができます。

書式文字列
<function name="do_something">
  <arg nr="1">
    <formatstr/>
  </arg>
</function>
マニュアルより引用
formatstr を指定することで %d %x などの書式のチェックがされるようになります。

return しない関数
<function name="ZeroMemory">
  <noreturn>false</noreturn>
</function>
マニュアルより引用
noreturn に true を指定することで、exit 関数などのような制御を返さない関数であることを通知できます。
また、false にすると制御を返す関数であることを明示的にし、パス解析を助けます。

その他
他にも not-bool や leak-ignore などマニュアルに書いていないものもあるようです。


cfg の保存
.cfg ファイルは cppcheck のインストールディレクトリ直下の cfg フォルダに保存するか、
任意の場所に保存し、cppcheck のコマンドラインオプションに指定することで有効にできます。
cppcheck src --library=my.cfg

試しに書いてみた
試しに .cfg 書いてみたのですが、思ったように書くことができませんでした。

クラスメンバーにある確保・解放関数を登録しようと考えたのですが、
まずメンバー関数に対しての書き方がよくわからない。
あーだこーだやってみたが全然検出してくれず…埒が明かん!ということでソースコードチェックアウトして確認しました。

結論としては「できません」。
cppcheck はトークンごとに解析をする感じになっていて、例えばメンバ関数の呼び出し
void* p = heap.MyAlloc(10);
は、
[void][*] [p] [=][heap][.][MyAlloc][(][10][)][;]
のように分解され解析されます。
alloc 関数かどうかの判定も1つのトークンで判定されるので、この場合 MyAlloc と書くことになります。

しかし、このとき alloc 関数かどうかの判定に使われるのは「heap」という名前です。
なんでと言われようがそうなっていたので、そうなのです。
当然マッチしません。

マッチしなかった場合、トークン列が [hoge][::] や [hoge][.] などにマッチするか判断します。
マッチした場合、現在のトークンの2つ先のトークンを関数名として使用します。
上の例では、「heap」 の次の次なので 「MyAlloc」。ようやくお出まし。

そして、取得した関数名から関数定義を引いてきて、return ステートメントなどから戻り値の allocType を判定します。
戻り値から判定するときにユーザー定義の alloc も参照しますが、「MyAlloc」が alloc 関数だと判定して欲しいので期待と異なります。

というわけで、メンバー関数呼び出しによるメモリリークの検出はできなさそうでした…残念orz
※ この記事はリビジョン 10720 で書いています。最新の動作と異なる場合があります。

なんにせよ、.cfg を使ったら検出率上げられるかもしれないので、試してみてはいかかがでしょうか?
以上。

1 件のコメント:

  1. Cppcheckのマニュアルを翻訳しております。
    http://d.hatena.ne.jp/fu7mu4/20160204/1454596617

    返信削除