このセクションの内容 :
フロー解析の使用と静的解析によるバグの検出
このセクションでは、フロー解析を実行してバグを検出する方法について説明します。たとえば、未初期化メモリの使用、NULL ポインターの間接参照、ゼロによる除算、メモリ リーク、リソース リークなどです。
重要
フロー解析を使用するにはオプションの BugDetective ライセンスが必要です。
フロー解析の実行
フロー解析は新しい静的解析テクノロジーです。フロー解析は、アプリケーションの実行パスをシミュレートして、実行時のバグ ( 未初期化メモリの使用、NULL ポインターの間接参照、ゼロによる除算、メモリ リーク、リソース リーク ) につながるパスを自動的に特定します。
フロー解析は複雑なパスを特定してトレースするため、人間によるテストや検査では発見が難しく、静的解析や単体テストでも発見されないことが多いバグをフロー解析は検出できます。コードを実行せずにバグを検出するフロー解析の機能は、特にレガシー コード ベースや組込みコード、つまりそのようなエラーの実行時検出が効果的ではなかったり不可能だったりするコードに有効です。
フロー解析の動作と利点の詳細については 「 データフロー解析による静的解析」 を参照してください。
ヘッダーのテスト
C++test は、テスト対象のソース ファイルがヘッダーをインクルードしていない限り、ヘッダーを直接テストしません。
詳細については 「ヘッダー ファイルを解析するには ? どのファイルが解析されるのか ?」 を参照してください。
テンプレート関数のテスト
C++test は、インスタンス化された関数テンプレートとインスタンス化されたクラス テンプレートのメンバーに対して静的解析と単体テストを実行できます。
詳細については 「C++test でのテンプレート関数のサポート」 を参照してください。
フロー解析を実行するための基本的な操作手順は次のとおりです。
- フロー解析の解析を実行するテスト コンフィギュレーションを選択します。
- C++test のビルトイン テスト コンフィギュレーションについては「ビルトイン テスト コンフィギュレーション」 を参照してください。
- テスト コンフィギュレーションの設定と共有に関連する全般的な情報については「テストコンフィギュレーションとルールの設定」を参照してください。 C++test 特有のフロー解析オプションについては 「[静的] タブ - 静的解析の方法を定義する」 を参照してください。
- 選択したテスト コンフィギュレーションを使ってテストを開始します。
- GUI からテストする方法については「GUI からのテスト」を参照してください。
- コマンドラインからテストする方法については 「コマンドライン インターフェイスからのテスト」 を参照してください。
- 結果をレビューします。
- 「フロー解析の結果の参照」 を参照。
- 必要に応じてフロー解析の設定をカスタマイズします。
- 詳細については 「フロー解析のカスタマイズ」 を参照してください。
cpptestcli によるコマンドライン モードでのフロー解析の実行
定期的にコマンドラインでフロー解析の解析を実行するには、チームにとって重要なルールを選択したフロー解析テスト コンフィギュレーションを使ってプロジェクトを解析します。
たとえば:
cpptestcli -data /path/to/workspace -resource "ProjectToTest" -config team://DataFlowAnalysis -publish
コマンドラインからのテストの詳細については 「 コマンドライン インターフェイスからのテスト」 を参照してください。
インクリメンタル解析モードでのフロー解析の実行
デフォルトでは、フロー解析は実行対象のスコープの完全解析を実行します。大規模なコード ベースの場合、この解析にはかなりの時間がかかることがあります。
フロー解析を実行する最も一般的な方法は、毎日わずかに変化する 1 つのコード ベースに対して夜間テストを実行することです。インクリメンタル解析モードの目的は、この一般的なフロー解析の解析にかかる時間を短縮することです。 インクリメンタル解析モードのフロー解析では、1 回目の実行中に重要な解析データが記憶され、2 回目以降のテストで再利用されます。再解析は、変更されたコードおよび変更されたコードに密接に関係するコードにだけ実行されます。
インクリメンタル解析を実行するときは次の点に注意してください。
- インクリメンタル解析モードで初めてフロー解析を実行すると、インクリメンタル解析モードではない場合に較べて解析に時間がかかることがあります。解析が遅くなる理由は、完全な解析を実行するのに加えて、今後のテストで再利用するためのデータも収集しているからです。
- データを格納するためのディスク容量が必要です。
インクリメンタル解析モードはテスト コンフィギュレーションの [静的] タブで設定できます。設定の詳細については 「フロー解析オプションの設定」を参照してください。
解析データのスワップを有効にしたフロー解析の実行
解析データのスワップ モードはデフォルトで有効化されています。スワップ モードでは、解析データをハードディスクに書き込みます。解析データのスワップは、インクリメンタル解析と同じ永続性記憶装置を使用し、インクリメンタル解析と似たプロセスで実行されます。大きなプロジェクトで解析を実行する場合、解析するソース コードの意味論的モデルを表す解析データが、フロー解析が使用できるすべてのメモリを消費する可能性があります。この現象が発生した場合、フロー解析は現在必要ない解析データをメモリから削除し、後でディスクから再び読み込みます。
一般的に、 Xmx JVM オプションを使って JVM ヒープのサイズを大きくして C++test を実行することを推奨します。これはスワップを最小限にするためであり、結果としてパフォーマンスが向上します。充分なメモリがある場合、解析データのスワップは無効化しても構いません。スワップを無効化すると、コード解析の速度が上がる場合があります。解析データのスワップを無効化する方法については、 「[パフォーマンス] タブ」の [ディスクへの解析データのスワップを有効化] の説明を参照してください。
フロー解析ビルトイン テスト コンフィギュレーション
Flow Analysis Fast
Fast には、セキュリティ ルールおよびカスタマイズが必要なルールを除き、すべてのフロー解析ルールが含まれます。Fast は解析の深さとして「最も浅い」を使用するため、 Standard や Aggressive (下記を参照) よりも早く実行します。Fast は適度な量の問題を発見し、違反の数が爆発的に増えるのを防ぎます。新しいプロジェクトでフロー解析ルールを推進するために Fast を 使用することを推奨します。より厳密な解析が必要な場合は、Standard に切り替えることを検討してください。
Flow Analysis Standard
Standard には、カスタマイズが必要なルールを除き、すべてのフロー解析ルールが含まれています。Standard は Fast よりも深い解析を実行するためFast よりも多くのバグを発見する可能性があります。また、Standard は Fast よりも長い実行時間を必要とします。より多くのバグを発見することの方が優先順位が高い場合、Standard に切り替えることを推奨します。解析パラメーターを再定義したりルールを再構成する必要がある場合、Standard を元にしてユーザー定義テスト コンフィギュレーションを作成できます。
Flow Analysis Aggressive
あらゆる疑わしいコードについて違反を検出したい場合、Aggressive の使用を推奨します。Aggressive は、たとえ「偽陽性」がレポートされるかもしれない場合でさえも、問題が疑われる場合に必ず違反をレポートします。Aggressive を使用すると、より多くのバグがレポートされますが、「実際には違反ではないケース」のレポートも増える可能性があります。
フロー解析のスコープ
フロー解析は、解析するために選択したファイルのスコープ内で、関数、クラス、名前空間、コンパイル単位の境界を超えて解析を実行します。 関数の呼び出しが存在し、その関数が別のファイルで定義されている場合、そのファイルが解析スコープに含まれている限り、フロー解析は関数内のパスを追跡できます。そのため、解析の精度はスコープに大きく依存します。
解析スコープ定義する (どのファイルを 1 セッションで解析するかを選択する) ための一般的なガイドラインは次のとおりです。
- 独立した複数のプロジェクトを同じ解析スコープに選択しないでください。完全修飾名を持つ型あるいは異なるシグニチャを持つ関数が複数のプロジェクトで定義されている場合があります。複数回定義されている関数の呼び出しがある場合、正しい関数まで呼び出しを追跡できないことがあります。
- コンパイル単位 A がコンパイル単位 B に依存していて、コンパイル単位 A を解析したい場合は、A の解析時に必要な情報を B からフロー解析が取得できるよう、通常 A と B の両方を解析スコープに含めることを推奨します。同じパターンは、依存関係があるプロジェクトにも適用されます。もちろん、解析対象のファイルが増えれば増えるほど、解析時間は長くなります。フロー解析は 1 つのファイルだけを解析することもできますが、そのような制限された解析では、多くの場合に解析精度が低下します。
フロー解析の結果の参照
このセクションでは、フロー解析でレポートされたバグを解析する方法について説明します。
解析結果へのアクセス
フロー解析の結果は、通常の静的解析違反と共に [品質タスク] ビューに表示されます。たださし、その内容と形式は通常の静的解析違反とかなり異なります。フロー解析の結果は、ルール カテゴリと重要度で分類されたタスク リストとしてレポートされます。解析結果を重要度で表示するには、[品質タスク] ビューのプルダウン メニューをクリックし、[表示] > [詳細] をクリックします。
GUI からテストを実行した場合、ルールの違反は [品質タスク] ビューの「静的解析違反の修正」カテゴリにレポートされます。
コマンドライン インターフェイスから実行した場合、ルールの違反はレポートの「静的解析」セクションにレポートされます。解析結果を Team Server に送信した場合は、『Parasoft Test ユーザーズ ガイド』の「ワークフローと使用法」>「GUI へのテスト結果のインポート」 にあるように解析結果を GUI にインポートできます。インポートした解析結果は [品質タスク] ビューの「静的解析違反の修正」カテゴリに表示されます。
レポートされたバグの詳細情報
レポートされたバグについての詳細情報を参照するには、[品質タスク] ビューでバグ メッセージを右クリックし、ショートカット メニューの [ルール ドキュメントの参照] をクリックします。右クリックするのは、黄色の注意アイコンがあるノードです。
違反からのテスト コンフィギュレーションの参照
[ 品質タスク] ビューの違反から、その違反を検出したテスト コンフィギュレーションを表示できます。違反を右クリックし、ショートカット メニューの [テスト コンフィギュレーションの参照] をクリックします。
テストのカスタマイズを担当し、不適切なルールをすぐに無効化したいグループ アーキテクトにとって、違反からテスト コンフィギュレーションにすぐにアクセスできるのはとても便利です。また、サーバー ベースの実行からテスト結果をインポートする開発者も、違反を検出したテスト コンフィギュレーションをレビューしなければならない場合があります。
フロー パスとは
フロー解析の違反は、[品質タスク] ビューで階層的なフロー パスによって表現されます。このパスは検出された問題につながるコードを正確に表します。フロー パス中の各要素は実行時に実行された各コード行です。フロー パス中に関数呼び出しがある場合、その関数呼び出しを表す要素の下のサブノードは関数内の実行フローを表します。フロー パスの最後の要素は常にバグが出現したポイントです。最終ポイントでなぜバグが出現しているのかを説明するために、完全なパスが表示されます。
フロー パスの要素はアイコンでマークされ、このアイコンは例外処理の動作を説明するのに役立ちます。パス中に、throw 文かまたはそのパスで例外をスローする関数呼び出しがある場合、その throw 文または関数呼び出しのパス要素は、赤い球体のアイコンでマークされます。赤い球体のアイコンは、フローが正常に進まないことを表します。
フロー パスの要素上にマウス ポインターを置くと、違反に関連する変数がツールチップとして表示されます。たとえば NULL ポインターの間接参照の違反の場合、フロー パスのどのポイントでどの変数が null 値を持っているかがツールチップとして表示されます。
レポートされた実行パスに関連するコード間を移動するには、[品質タスク] ビューの ツールバーの [次のタスク] または [前のタスク] ボタンをクリックします。
ソース コードの変更はフロー パスにどう影響するか ?
フロー解析の実行後にソース コードを変更した場合、次の処理が行われます。
- フロー解析の違反は元のフロー パスのソース コードを使用し続けます。このため、確実に有効な違反パスが表示されます。
- フロー パス中の要素が古い場合 ( つまり解析されたソース コードに現行のソース コードが一致しない場合 )、その要素 (および違反ノード) には特別なアイコンとツールチップが表示されます。また、関連するソース コードにアクセスできないため、要素をダブルクリックしてソース コードを表示する機能は使用不可になります。
パスには省略 (...) が含まれることがあります。これは、 違反の原因から違反のポイントにつながる、異なる複数のパスがあることを表します。
パス トレースの簡略表示と詳細表示
デフォルトでは、解析結果は簡略パス トレースで表示されます。簡略表示では、欠陥のあるパス中の重要な実行可能ステートメントだけが表示されます。このため、レポートされるさまざまな問題の概要をすぐに把握することができます。詳細については 「パス トレースの簡略表示と詳細表示」 を参照してください。
重要な要素の特定
発見された違反に対して重要なパス要素は、濃色のボールのアイコンでマークされます。重要なパス要素の上にカーソルを置くと、その要素が重要な理由がツールチップに表示されます。たとえば、 「NULL の代入箇所」、「重要なデータ フロー」、「重要なデータ フローを含む (重要な要素を持つメソッド呼び出しノードに対する出力)」 などです。
重要な要素には、違反の原因と違反のポイントが該当するほか、違反につながるデータを持つ変数リストを変更する要素が該当します。
たとえば、次のサンプル コードで変数 "a" が null ポインターを持つ場合:
1: b = a; 2: c = k;
行 1 は、後で変数 "b" で違反が発見される場合に重要です。そのため、重要な要素としてマークされます。
行 2 は、不正なデータを持つ変数に関係していないため、重要な要素としてマークされません。
違反の原因と違反のポイント
違反自体は、2 つのマークされたポイントを持つ実行パスとして表現されます。
- 違反の原因 通常、ここが「不正なデータ」の発生ポイントです。たとえば null ポインターの間接参照のルールの場合、違反の原因は null ポインターの代入ポイントです。
- 違反ポイント これは、プログラム中のバグに通常つながる「不正なデータ」のポイントです。たとえば null ポインターの間接参照の ルールの場合、null ポインターを持つ変数が間接参照されるポイントです。
レポートされた違反 ( 黄色の注意アイコンがあるノード) を右クリックして [違反の原因を表示] または [違反ポイントを表示] を選択するだけで、違反の原因と違反のポイントに簡単にアクセスできます。たとえば、「null 値の間接参照」 ルールの違反では [違反の原因を表示 (Null の代入ポイント)] および [違反ポイントを表示 (NullDereferencing ポイント)] コマンドが表示されます。
違反メッセージが参照しているコードをハイライト表示する
違反メッセージをダブルクリックするだけで、フロー解析違反メッセージが参照しているソース コードを簡単にハイライト表示できます。フロー パスが水色にハイライト表示された状態で、選択した違反に対応するソース コードが表示されます。違反にとって重要なすべての行、つまり違反の原因、違反のポイント、および重要なデータ フローは、濃い色でハイライト表示されます。
レポートされた実行パスに関連するコード間を移動するには、[品質タスク] ビューのツールバーの [次のタスク] または [前のタスク] ボタンをクリックします。
ヒント
ヒント
- チェック対象から除外するルールは無効にしたり抑制したりできます。詳細については「[静的] タブ - 静的解析の方法を定義する」 を参照してください。
- C++test に付属の静的解析ルールについては、[Parasoft] メニューの [ヘルプ] をクリックして『Parasoft C++test 静的解析ルール ガイド』ブックを参照してください。
パス トレースの簡略表示と詳細表示
簡略表示と詳細表示の違い
デフォルトでは、解析結果のパス トレースは「簡略表示」で表示されます。簡略表示では、欠陥のあるパス中の重要な実行可能ステートメントだけがビューに表示されます。このため、レポートされるさまざまな問題の概要をすぐに把握することができます。
問題についてより詳しい内容を知りたい場合は、「詳細表示」にして完全なパスを表示できます。詳細表示にした場合、 検出されたバグが出現しているとフロー解析が見なした実行パスのすべての要素がタスク ノードに表示されます。言い換えると、フロー解析はこのバグに至る完全な実行パスを表示します。ユーザーは、発見された問題についての完全な情報を得ると共に、フロー解析が行った推測を得ることができます。ときには、フロー解析がこの問題をレポートした理由を知るために、詳細情報が必要不可欠なこともあります。
ほとんどの場合、レポートされた問題を理解するために詳細情報は必要ないかもしれません。そのような場合は簡略表示の方が便利です。簡略表示では、 検出されたバグが出現しているとフロー解析が見なした実行パスの中から、最も重要なものだけがタスク ノードに表示されます。フロー解析は、次の実行パス要素を重要な要素として扱い、簡略表示でレポートします。
- 違反の原因 (通常は不正データの発生ポイント)
- 違反ポイント (バグが出現しているポイント)
- 例外をスローしている要素 (制御フローに著しく影響するため)
- 重要なデータ フロー要素 (ある変数から別の変数への不正なデータ フローのポイント。可能性としては、不正なデータを変数に代入している、不正なデータをパラメーターとしてメソッドに渡している、不正なデータをメソッドが返している、といった処理があります。この情この情報は、違反の原因を突き止めるために非常に重要です。)
次の図は「簡略表示」の例です。
次の図は「詳細表示」の例です。
ソース コード エディターで詳細な実行パスを確認する
[品質タスク] ビューを簡略表示にして最重要な要素だけを表示している場合でも、違反を選択すると、すべてのパス要素がハイライト表示され、対応するコードが IDE のソース コード エディターに表示されます。したがって、簡略表示によって問題を直ちに見極めた後、違反の発生原因について詳しい情報を得たければ、ソース コード エディターで詳細な実行パスを確認することができます。
詳細表示に設定する
特定の違反の詳細情報を表示するには、次の操作を行います。
- 違反のタスク ノードを右クリックし、ショートカット メニューの [完全な違反のパスを表示] をクリックします。
常に詳細表示にするには、次の設定を行います。
- [Parasoft] メニューの [設定] をクリックします。
- 左側のリストから [品質タスク] を選択します。
- [フロー解析の違反の完全なパスを表示する] チェックボックスをオンにします。
フロー解析のカスタマイズ
このセクションでは、フロー解析をカスタマイズする方法について説明します。検出するバグを指定する方法、ルール パラメーター、解析オプション、および特定のルールでチェックするリソースを指定する方法について説明します。
検出するバグの指定
フロー解析が検出するバグの種類は、通常の静的解析とほぼ同様に、テスト コンフィギュレーションで有効化されたルールによって決定されます。1 ルールあたりレポートされるタスクの最大数など、基本的な静的解析の設定は通常の静的解析とフロー解析のデータフロー解析の両方に適用されます (基本的な静的解析の設定については「[静的] タブ - 静的解析の方法を定義する」を参照してください)。
たとえば、フロー解析ルールを有効化/無効化するには、テスト コンフィギュレーションの [静的] > [ルール ツリー] タブでルールのチェックボックスのオン/オフを切り替えます。
ルール パラメーターの指定
一部のルールではパラメーターを指定することができます。つまり、プロジェクトのニーズに合わせてユーザーがルールをカスタマイズできます。そのため、フロー解析は特定の API の使用に関連する違反でも検出できます。
ルール パラメーターの詳細については 「ルール パラメーターのカスタマイズ」を参照してください。
未検証の違反をレポートする
共通のルール パラメーターの 1 つが [未検証の違反をレポートする] です。
このパラメーターがどのように動作するかを説明するために、まず違反を発見してレポートするプロセスを見てみましょう。まずフロー解析は違反の原因 (不正なデータまたは疑わしいデータが発生しているポイント) から開始し、違反のポイント (不正なデータまたは疑わしいデータが、危険または疑わしい操作で使用されているポイント) で終了するパスを探します。ルールに違反するそのようなパスを発見すると、フロー解析は通常、違反パスの最上位レベルの関数 (他の関数を呼び出す可能性がある、違反パスの階層表現での最上位レベルの関数) からパスが到達可能かどうかを検証することで、違反を検証しようとします。
たとえば次のコードは、完全に 1 つの 関数内に違反のパスがある単純なケースです (違反のパスから他の関数は呼び出されません)。
typedef struct SomeStruct { int hashCode; } SomeStruct; extern SomeStruct* getObject(); void check(int initialized) { SomeStruct* obj; if (initialized) { obj = getObject(); } else { obj = 0; } int hashCode = 0; // some other actions if (initialized) { hashCode = obj->hashCode; } // some other actions }
フロー解析がこのコードを解析すると、次の違反がまず発見されます。
.C program.c (12): obj = 0; // Null value carrier: obj . program.c (14): int hashCode = 0; // Null value carrier: obj . program.c (16): if (initialized) { // Null value carrier: obj .P program.c (17): hashCode = obj->hashCode; // Null value carrier: obj
次の段階は、違反のパスが関数の先頭から到達可能かどうかをチェックして、違反を検証することです。この段階でフロー解析は "initialized" が false の場合にだけ "obj" に null 値を代入できることを判断します。ただしこの場合、間接参照が発生している行は到達できません。これは、違反の最上位レベルの関数の先頭からパスが到達可能かを検証することで、違反のパスの検証がどのように「偽陽性」を排除するのに役立つかの一例です。一方、最上位レベルの関数の先頭からパスが到達可能であったなら、違反のパスの検証をパスするでしょう。そして詳細パス モードでは、「パスが到達可能ではない可能性がある」とフロー解析が判断した理由を説明するために、最上位レベルの関数の先頭から開始して、完全なパスと共に違反が表示されます。
複雑なケースでは、解析の深さの設定によって、フロー解析の違反パス検証の処理が終了しない場合があります (解析の深さの制限は、解析速度を適切なレベルに保つために使用されます)。そうなったとき、違反は未検証のままになり、対応する関数から到達可能かどうかがフロー解析には分かりません。この場合、違反がレポートされるかどうかを [未検証の違反をレポートする]パラメーターが決定します。このオプションを有効にすると、フロー解析が発見する欠陥の数が増えますが、「偽陽性」の数も増えることがあります。
フロー解析オプションの設定
テスト コンフィギュレーションでは、基本的な静的解析の設定に加えて、解析の深さ、マルチスレッド API のサポート、違反のレポートの冗長性といったフロー解析固有のオプションを設定できます。[静的] タブの [フロー解析オプション] タブで設定します。
[フロー解析オプション] タブは次のタブから構成されます。
- [パフォーマンス] タブ
- [冗長性] タブ
- [ターミネータ] タブ
- [マルチスレッド] タブ
- [リソース] タブ
[パフォーマンス] タブ
- インクリメンタル解析 インクリメンタル解析のオプションを設定します。この機能の詳細については 「インクリメンタル解析モードでのフロー解析の実行」を参照してください。
- インクリメンタル解析を有効化 インクリメンタル解析を使用するかどうかを指定します。
- インクリメンタル キャッシュを圧縮する間隔 [日] インクリメンタル キャッシュを圧縮する間隔を日数で指定します。インクリメンタル解析は速度を上げるために最適化されます。フロー解析は、常にキャッシュ サイズを小さくして不要なデータを削除しようとします。しかし、ソース コードの変更によって使用されなくなったデータがキャッシュに残る場合があります。このオプションを指定すると、指定した日数ごとにキャッシュが圧縮され、すべての古いデータが消去されます。前回の圧縮から経過した時間がこのオプションの日数より長い場合、フロー解析のインクリメンタル解析を実行した直後にキャッシュの圧縮が実行されます。
- キャッシュの消去 現在のワークスペースについて、インクリメンタル解析のキャッシュを消去します。キャッシュを消去した後に初めてワークスペースに対してインクリメンタル解析を実行する場合、コード ベースの完全な解析が必要です。
- 解析の深さ このオプションは解析の深さを決定します。解析が深くなればなるほど、より多くの違反が発見されます。ただし、解析が深くなるとパフォーマンスが低下し、メモリの消費量もやや増加します。次のオプションがあります。
- 最も浅く (最も高速) ソース コード中の最も明らかな問題だけを発見します。発見する対象は、問題が発生しているコードの箇所に「問題の起点」が近い場合だけに制限されます。発見される違反の実行パスは通常、1 つの関数のいくつかの行にわたります。4 以上の関数呼び出しにわたることは まずありません。
- 浅く (高速) [最も浅く] オプションと同様、ソース コード中の最も明らかな問題だけを発見します。ただし、[浅く] オプションは [最も浅く] オプションよりも多くの問題を検出し、より長いパスを検証することができます。
- 標準 何十もの要素を含む実行パスと共に数多くの問題を発見します。[標準] オプションは、[浅く] オプションよりも深い解析を実行するのに加えて、より複雑な問題を探します。たとえば、不正なフローのために1 つの関数で発生する問題や、異なる箇所の異なる関数間で不適切なやり取りがあるために解析対象プロジェクトで発生する問題などです。多くの場合、[標準] オプションが発見する違反は、解析対象ソース コード中の重要なバグを明らかにし、そのコード行は何十行にも及びます。
- 深く (低速) [標準] オプションで定義されているのと同じ複雑さと性質の問題を、より数多く検出することができます。 ただし、[標準] オプションよりも低速になります。
- 徹底的 (より低速) より複雑な問題を発見します。コードベースを徹底的に解析するため、時間を必要とします。しかし、アプリケーション中の異なる場所にある何百行というコードにまで違反パスがわたるような、非常に複雑な問題を数多く発見します。このオプションは夜間のテスト実行での使用を推奨します。
- ディスクへの解析データのスワップを有効化:デフォルトで有効。解析に必要なデータをハード ディスクに書き込みます。解析データのスワップは、インクリメンタル解析と同じ永続性記憶装置を使用し、インクリメンタル解析と似たプロセスで実行されます。大量のメモリを必要としない小さなサイズのプロジェクトに対してフロー解析を実行する場合、または 64-bit システムのように十分なメモリがある場合、このオプションを無効化できます。スワップを無効化すると、解析のパフォーマンスが向上する場合があります。スワップ機能の説明については、「解析データのスワップを有効にしたフロー解析の実行」 を参照してください。
[冗長性] タブ
[冗長性] タブでは次のオプションを設定できます。
- 原因が特定できない場合は違反をレポートしない 原因を表示できない場合に違反をレポートするかどうかを指定します。
一部のフロー解析ルールでは、フロー解析はあるポイントに至るすべての可能なパスをチェックし、すべてのパスについて特定の条件が満たされることを検証する必要があります。そのような場合、1 つの違反が複数のパスに関連付けられます (単純なケースでは、1 つの違反が 1 つのパスによって表されます)。そのような違反のすべてのパスは、違反のすべてのパスに共通する違反ポイントで終わります。ただし、別のパスがコード中の別のポイントで開始する可能性もあります。各パスの開始点が違反の原因 (コード中のポイント。それよりも後の違反ポイントのコードで特定の条件の違反を規定する) です。「複数パスの違反」でパスによって原因が異なる場合、フロー解析は違反のポイントだけを表示します (違反の原因は表示しません)。
違反の原因から違反ポイントまでの完全なパスが表示される通常のケースと比較すると、違反ポイントだけの違反は、理解しにくい場合があります。そのため、違反の原因を表示できない場合に違反を非表示にするこのオプションが用意されています。
詳細については下記の「例 - [原因が特定できない場合は違反をレポートしない] の影響」を参照してください。 - インライン アセンブリ コードを通るパスの違反をレポートしない 違反のパスがインライン アセンブリ コードを通っている場合、その違反をレポートしないように設定します。
- 類似する違反のレポート レベル 「違反ポイントを共有している違反」または「違反ポイントと原因の両方を共有している違反」について、レポートするレベルを選択します。
- すべての類似する違反を表示 特定されたすべてのフロー解析違反をレポートします。
- 同じ原因と違反ポイントの違反を 1 つだけ表示 重複するフロー解析の違反をレポートしないようにします。重複する違反とは、( たとえフロー パスが異なっていても ) 同じ原因と同じ違反ポイントを共有する違反のことです。
- 疑わしいポイントごとに違反を 1 つだけ表示 1 つの疑わしいポイントにつき、( 1 つのルールに対する) 1 つの違反だけをレポートします。このオプションを選択すると、フロー解析のパフォーマンスが若干速くなります。
例 - [原因が特定できない場合は違反をレポートしない] の影響
次のサンプル コードに対して BD-PB-DEREF ルールを実行した場合、[原因が特定できない場合は違反をレポートしない] オプションが無効な場合にだけ、複数の原因を持つ違反がレポートされます。
#include "stdio.h" enum Figures { SPHERE, CIRCLE, CUBE, SQUARE, HIMESPHERE }; static void guessFigure(int round, int volumetric) { int figure; if (round && volumetric) { figure = SPHERE; /* CAUSE 1 */ } else if (round && !volumetric) { figure = CIRCLE; /* CAUSE 2 */ } else if (!round && volumetric) { figure = CUBE; /* CAUSE 3 */ } else { figure = SQUARE; /* CAUSE 4 */ } switch (figure) { case SQUARE: printf("This is a sphere"); break; case HIMESPHERE: printf("This is a hemispere"); break; case CIRCLE: printf("This is a circle"); break; case CUBE: printf("This is a cube"); break; default: printf("This is a square"); break; } }
[ターミネータ] タブ
アプリケーションの実行を終了させるターミネータ関数を定義できます。
C/C++ プログラミングでは、回復が不可能な致命的なエラーが発生した場合に、関数を使ってアプリケーションの実行を終了させることがあります。たとえば標準ライブラリの abort() や exit() といった関数です。フロー解析はアプリケーションの実行フローを解析するため、アプリケーションを直ちに停止して実行フローをブレークするターミネータ関数を把握することは、フロー解析にとって非常に重要です。ターミネータ関数を把握していない場合、フロー解析がアプリケーションについて誤った推測を行う可能性があります。
フロー解析は、標準ライブラリで定義されたターミネータ関数を認識します。独自のターミネータ関数をアプリケーションで使用している場合、[ターミネータ] タブのリストにそのターミネータ関数を追加します。ターミネータ関数をリストに追加しない場合、ターミネータ関数による実行パスについてフロー解析が誤った結果をレポートする (本当は違反ではないものを違反としてレポートする) 可能性があります。
次の指定を行って、ターミネータ関数を定義します。
- 有効 チェックボックスがオンのターミネータ関数をフロー解析は解析中に把握します。
- 完全修飾型名または名前空間 特定のターミネータのエンティティを指定します。このフィールドに何も指定しない場合、[関数名] で指定された名前のグローバル関数がターミネータ関数と見なされます。
- たとえば、ターミネータ関数が名前間空間 myNameSpace の myClass で宣言されている場合、このフィールドの値は myNameSpace::myClass になります。あるいは、ターミネータ関数が型において宣言されてなく、名前空間 myNameSpace にだけ属している場合、このフィールドの値は myNameSpace になります。
- 関数名 ターミネータ関数の名前を指定します。
- + サブクラスの定義 サブクラス中のターミネータ関数の定義も、ターミネータ関数として見なすかどうかを指定します。 この設定は、完全修飾型名が指定されている場合にだけ意味を持ち、インスタンス関数と非インスタンス関数の両方に適用されます。
[マルチスレッド] タブ
[マルチスレッド] タブでは、既知のマルチスレッド関数を有効化/無効化するだけでなく、スレッド間の同期化のための関数を定義することができます。このタブで定義した情報は、BD.TRS (スレッドと同期化) カテゴリのルールに影響します。BD.TRS カテゴリのルールは、[マルチスレッド] タブで定義および有効化されているすべての関数をチェックします。
サポートされる API のチェックボックスをオンにして、さまざまな API の同期化関数を有効化するだけでなく、同期化関数を含むユーザー独自の API からの関数を有効化することもできます。特定のライブラリの同期化関数についての情報を追加するには、[追加] ボタンをクリックし、ライブラリの名前を入力して Enter キーを押します。新しいエントリが API のリストに追加されます。次に、エントリをクリックして [編集] ボタンをクリックします。表示されたダイアログで、特定の同期化関数を定義します。このダイアログでは次の種類の関数を定義することができます。
- ロック関数 (たとえば mutex の取得)
- ロック解除関数 (たとえば mutex の解放)
- sleep 関数
これらの関数を定義する方法は、リソースの場合と非常に似ています。「 BD.RES ルールでチェックするリソースを指定する」を参照してください。
[リソース] タブ
[リソース] タブでは、BD.RES (リソース) カテゴリのルールを使ってチェックするリソースを定義することができます。BD.RES カテゴリのルールは、[リソース] タブで定義および有効化されているすべてのリソースについて、リソースが正しく使用されているかをチェックします。
リソースを定義する方法の詳細については、以下のセクションを参照してください。
BD.RES ルールでチェックするリソースを指定する
テスト コンフィギュレーションの [静的] > [フロー解析オプション] > [リソース] タブでは、BD.RES (リソース) カテゴリのルールでどのリソースをチェックするかを定義することができます。BD.RES カテゴリのルールは、[リソース] タブで定義および有効化されているすべてのリソースについて、リソースが正しく使用されているかをチェックします。
さまざまな種類のリソースのチェックを有効化/無効化することができます。カスタム リソースを管理するには [追加]、[削除]、[編集] ボタンをクリックします。
特定のライブラリのリソースについての情報を追加するには、次の操作を行います。
- [追加] ボタンをクリックします。
- [リソースのタイプ] に名前を入力します。
- Enter キーを押します。新しいエントリが追加されます。
- 適切な場合、[アプリケーション終了時の違反をレポートしない] オプションをオフにします。
- エントリをクリックして [編集] ボタンをクリックします。表示されたダイアログで、リソースの割り当て/割り当て解除の方法を定義します。詳細については以下のセクションを参照してください。
アロケーターとクローザーに共通のパラメーター指定
各行は 1 つのアロケーター/クローザーの関数に対応します。明確に 1 つの関数 (ワイルドカードを使用した場合は一連の関数) を特定するのに十分な情報と、リソースの割り当て/解放の方法を定義します 。
- 有効 このチェックボックスがオンのリソースが BD.RES ルールでチェックされます。
- 完全修飾型名または名前空間 (ワイルドカード) すべての関数について、型の完全修飾名または関数が宣言されている名前空間を指定する必要があります。一般的に名前の文字列の一部としてアスタリスク (*) を使用できます。アスタリスク (*) は任意の数の任意のシンボルを表します。
- 関数名 (ワイルドカード) アロケーター/クローザーの関数の名前を指定します。アスタリスク (*)を使って、任意の数の任意のシンボルを表すことができます。
- + サブクラスの定義 サブクラス中の関数の定義も、アロケーター/クローザーとして見なすかどうかを指定します。 この設定は、完全修飾型名が指定されている場合にだけ意味を持ち、インスタンス関数と非インスタンス関数の両方に適用されます。
リソース アロケーター
リソースを生成する関数についての情報を定義します。
- "this" オブジェクトがリソース このチェックボックスをオンにすると、関数が呼び出されるオブジェクトのリソースが割り当てられます。
- リソース オブジェクトを返す 割り当てられたリソースを返すには、このチェックボックスをオンにします。
- パラメーターの使用 リソースを割り当てる1つ以上のパラメーターを指定します。パラメーターの位置 (1 から開始) を指定するか、アスタリスク (*) を指定して、すべてのパラメーターにリソースを割り当てます。
一般的に、アロケーター関数はエラー コードを返してリソース割り当ての失敗を表します。アロケーター関数 はリソースへのポインターを返し、通常 NULL ポインターはリソース割り当ての失敗を表します。リソース リークを探すときに、フロー解析はリソースの割り当てが成功したか失敗したかを把握できる必要があります。これは、割り当てが実際に発生したパス上で発見されないクローザー関数への呼び出しだけをフロー解析がレポートするのに役立ちます。リソース アロケーター関数がリソースへのポインターを返し、このポインターが NULL でない場合、フロー解析はリソースが正常に割り当てられたと推測します。
リソース アロケーターが整数値を返す場合、[エラー時の戻り値制約] フィールドに条件を入力して、割り当てが失敗した場合の戻り値の制約を指定できます。条件は次の書式で指定する必要があります。
<比較演算子><整数値>
たとえば、0 ではない値をアロケーター関数が返す場合、このフィールドに「!=0」を入力します。 同様に、エラーに対するリターン コードが -1 の場合、「==-1」と入力します。!= と == に加えて、次の演算子を使ってエラー条件を指定できます。
>, >=, <, <=
リソース クローザー
リソースをクローズする関数についての情報を定義します。
- "this" オブジェクトがリソース このチェックボックスをオンにすると、関数が呼び出されるオブジェクトのリソースがクローズされます。
- パラメーターの使用 リソースをクローズする 1つ以上のパラメーターを指定します。パラメーターの位置 (1 から開始) を指定するか、アスタリスク (*) を指定して、すべてのパラメーターにリソースを割り当てます。
ビルトインでサポートされるリソース
デフォルトでは、ビルトインでサポートされるリソースが [リソース] タブに表示されます。ビルトインでサポートされるリソースは次のとおりです。
- ディレクトリ (dirent.h)
- 動的ライブラリ (dlfcn.h)
- ファイル (stdio.h)
glob の結果 (glob.h)
次への呼び出しによって割り当てられる 'glob_t' 構造に対応。int glob(const char *pattern, int flags, int (*errfunc) (const char *epath, int eerrno), glob_t *pglob);
hostent 構造 (hostent.h)
次への呼び出しによって割り当てられる hostent 構造に対応。struct hostent *getipnodebyname(const char *name, int af, int flags, int error_num); struct hostent *getipnodebyaddr(const void *addr, size_t len, int af, int error_num);
- メモリ (標準 C)
標準 C のメモリ割り当て関数によって管理されるメモリ - メモリ (標準 C++)
標準 C++ のメモリ管理演算子 new/delete によって管理されるメモリ - メモリ (Windows API)
Windows API によって管理されるメモリ - メッセージ カタログ (nl_types.h)
- パイプ (stdio.h)
- PThreads (pthread.h)
- 正規表現 (regex.h)
- ソケット (socket.h, Winsock2.h)
ソケット アドレス構造 (netdb.h)
次への呼び出しによって割り当てられる 'addrinfo' 構造に対応。int getaddrinfo(const char *node, const char *service, const struct addrinfo hints, struct addrinfo **res);
- Windows API次の種類のリソースを含む。
- カーネル、ユーザー、および GDI API (Windows.h)
- プリント スプーラー API (Winspool.h)
- バックアップ機能 (Sisbkup.h)
- 待機チェーン トラバーサル (Wct.h)
- ネットワーク管理 (Lm.h)
カスタム リソースの定義
例 #1
カスタム リソースを定義する例として、非標準のリソースを使って、次の単純な C コードについて考えてみましょう。
/* returns NULL on allocation failure */ void* myAlloc(void); void myDealloc(void*); static void createMyResource_Leak() { void* resource = myAlloc(); } /* 'res' is not closed on the path where it is not NULL */ static void createMyResource_NoLeak() { void* resource = myAlloc(); if (!resource) { return; /* no leak here, if res is NULL, it means allocation failed */ } /* use the resource */ myDealloc(resource); }
フロー解析を使ってこの非標準のリソースのリークを発見するために、リソースを割り当て/解放するメソッドを定義します。次の操作を行います。
- [リソース] タブで [追加] ボタンをクリックし、新しいリソースの名前を定義します。この名前は、このリソースに関連する違反がレポートされるときに使用されます。
- [アプリケーション終了時のリークをレポートしない] オプションをオフにします。
- [編集] ボタンをクリックしてこのリソースの操作方法を指定します。
- アロケーターとして
myAlloc
を定義します。- 完全修飾型名または名前空間 - 空白のままにする
- メソッド名 - myAlloc
- + サブクラスの定義 - オフ
- '"this" オブジェクトがリソース - オフ
- リソース オブジェクトを返す - オン
- パラメーターの使用 - 空白のままにする
- クローザーとして closeMyResource を定義します。
- 完全修飾型名または名前空間 - 空白のままにする
- メソッド名 - closeMyResource
- + サブクラスの定義 - オフ
- '"this" オブジェクトがリソース - オフ
- リソース オブジェクトを返す - オフ
- パラメーターの使用 - 1
例 #2
別の例を考えてみましょう。ここでは openMyResource 関数がパラメーターとしてリソース ハンドルへのポインターを受け取り、新規に割り当てられたリソースを初期化します。割り当てが失敗する場合、エラー コード -1 が返ります。
int openMyResource(int* pHandle); void closeMyResource(int handle); static void openMyResource_Leak() { int handle; openMyResource(&handle); } // 'res' is not closed static void openMyResource_NoLeak() { int handle; int status = openMyResource(&handle); if (status == -1) { return; // no leak here, status == -1 indicates allocation failure } // use the resource closeMyResource(handle); }
このタイプのリソースに対応するようフロー解析を設定します。次の操作を行います。
- [リソース] タブで [追加] ボタンをクリックし、新しいリソースの名前を定義します。このリソースに関連する違反がレポートされるときに、この名前が使用されます。
- [アプリケーション終了時のリークをレポートしない] オプションをオフにします。
- [編集] ボタンをクリックしてこのリソースの操作方法を指定します。
- アロケーターとして openMyResource を定義します。
- 完全修飾型名または名前空間: 空白のままにする
- メソッド名: openMyResource
- + サブクラスの定義: オフ
- '"this" オブジェクトがリソース: オフ
- リソース オブジェクトを返す: オフ
- エラー時の戻り値制約: ==-1
- リソースを表すパラメーターの位置: 1
- クローザーを指定します。
- 完全修飾型名または名前空間: 空白のままにする
- メソッド名: closeMyResource
- + サブクラスの定義: オフ
- '"this" オブジェクトがリソース: オフ
- リソース オブジェクトを返す: オフ
- パラメーターの使用: 1
- リソースを表すパラメーターの位置: 1