静的解析

概要

Clangコンパイラの静的解析機能を使用することで、C/C++ソースコード中の様々な問題点を実行前に検出する事が可能です。 報告される問題点の中には、NULLポインタ参照の可能性(Dereference of null pointer)や、ゼロ割(Division by zero)などのエラーの他に、使用されない変数への代入(Dead store)など、 実行中に異常動作を引き起こさないものも含まれますが、これらを取り除いておくことは、より堅牢で無駄のないソフトウェアを開発するための手助けになります。

使い方

  1. 解析したいプロジェクトのメニューから [プロジェクトのみ] ‣ [プロジェクトのみでコード解析を実行] を選択します。[1]

../_images/do-static-analysis.png

  1. 解析が終了すると、Static Analysis Results タブに結果が表示されます。
    結果には、ファイル名、バグ概要、バグの種類、関数名、ソース行などが含まれます。
../_images/static-analysis-results.png

また、REPORT の項目に表示されているリンクをクリックすると、ファイル毎の詳細なレポートを参照できます。

../_images/static-analysis-report.png

実行例

実際にサンプルアプリケーションを使って、静的解析を実行してみましょう。 サンプル AP-RZA-demo-analyze または DB51903PF-demo-analyze を起動します。

プロジェクト demo-analyze にて [プロジェクトのみ] ‣ [プロジェクトのみでコード解析を実行] を選択します。

../_images/sample-static-analysis-build.png

実行すると、3つの問題が指摘されます。(指摘された問題の表示順は異なる場合があります。)

../_images/sample-static-analysis-report.png

3つの問題は、1つ1つ種類の違う問題です。それぞれを詳細に見ていきましょう。


  1. Null pointer argument in call to string length function

これは、NULLを渡してはいけない関数に、NULLを渡す可能性があることを指摘しています。 右端にある、REPORTのリンクをクリックすると、ソースを使った解析の詳細が表示されます。

下にスクロールさせていくと、数字のついている部分がありますので、これを辿っていきましょう。

../_images/sample-static-analysis-result1.png
(1)によって、pStrがNULLであるという条件分岐があり、NULLだと仮定して解析を継続します。
(2)はNULLだと仮定した時に、こちら(if分の中)が実行されることを解析しています。
(3)はswitchの処理で、defaultになった場合の処理を解析し、(問題のある)140行目にそのまま実行が移ってしまうことを示しています。
(4)にて、strlenにNULLが入る可能性があることが判明し、問題として報告されました。

引数にNULLを許すかどうかの判定は、関数の宣言に __attribute__((nonnull (引数番号のリスト))) を 設定することで、追加が可能です。


  1. Undefined or garbage value returned to caller

これは、未初期化の変数を戻り値として渡す可能性があることを指摘しています。

先ほどは解析の手順を説明するため、(1)から順に見ましたが、実際に問題を見ていく時には 最後の方から黄色の部分を辿っていくと、どこを修正すべきかが分かりやすくなります。

../_images/sample-static-analysis-result2.png

(15)がreturn文で、ここで使われているresultが不定値であることが指摘されています。 この不定値が発生する条件は、(13)を通ってきた時、つまり op=='=' が成立した時です。 したがって、このif文の処理中に、resultに値を入れる修正を入れる必要があります。


  1. Division By Zero

これは、ゼロで除算してしまう可能性があることを指摘しています。

Clangコンパイラは、ゼロで除算してしまう問題の精度を上げるため、問題となる関数 単体だけでなく、その関数を呼び出す側の関数も解析し、その上でゼロで割る可能性 を指摘します。

まず、問題となる関数(DoOperand())では、引数 op=='/' かつ right が 0の可能性が指摘されています。(21),(19)

../_images/sample-static-analysis-result3-3.png

そして、そのDoOperand()を呼ぶ DoParse() 側で、rval が 0 になる条件、DoOperand()が 呼び出す GetValue() が0を返す可能性がある事を指摘しています。

../_images/sample-static-analysis-result3-1.png

そして、GetValue() は、*pStr が 0 (つまりpStrが空文だった場合)、0を返す可能性があることを 指摘しています。

../_images/sample-static-analysis-result3-2.png

このように、(意図せずに)ゼロで除算してしまう可能性をチェックするために、 呼び出す側の関数や、その呼び出す関数が別に呼び出している関数まで辿っていることが分かります。