このセクションでは、フロー解析でレポートされたバグを解析する方法について説明します。
このセクションの内容
フロー解析の使用
このセクションでは、フロー解析を実行してバグを検出する方法について説明します。たとえば、未初期化メモリの使用、NULL ポインターの間接参照、ゼロによる除算、メモリ リーク、リソース リークなどです。
フロー解析の実行
フロー解析は、実行パスのシミュレートなどの解析技術を使用して、実行時にしか明らかにならない潜在的な欠陥を検出する新しいタイプの解析テクノロジーです。検出される欠陥には、未初期化メモリ、null ポインターの間接参照、ゼロによる除算、メモリ リークおよびリソース リークなどがあります。
フロー解析は複雑なパスを特定してトレースするため、人間によるテストや検査では発見が難しく、文法ベースの静的解析や単体テストでも発見されないことが多いバグを検出できます。コードを実行せずにバグを検出するフロー解析の機能は、特にレガシー コード ベースや組込みコード、つまりそのようなエラーの実行時検出が効果的ではなかったり不可能だったりするコードに有効です。
フロー解析を実行するための基本的な操作手順は次のとおりです。
- 任意のフロー解析設定を実行するテスト コンフィギュレーションを選択または作成します。
- あらかじめ用意されているテスト コンフィギュレーションの詳細については 「ビルトイン テスト コンフィギュレーション」を参照してください。
- テスト コンフィギュレーションの構成と共有に関連する全般的な情報については、「テストコンフィギュレーションとルールの設定」を参照してください。C/C++test 固有のフロー解析オプションについては、「[静的] タブ - 静的解析の方法を定義する」を参照してください。
- 選択したテスト コンフィギュレーションを使ってテストを開始します。
- GUI からのテストの詳細については「GUI からのテスト」を参照してください。
- コマンドラインからのテストの詳細については 「コマンドライン インターフェイスからのテスト」を参照してください。
- テスト結果をレビューして修正を行います。
- 詳細については「フロー解析の結果の参照」 を参照してください。
- (オプション) 必要に応じてフロー解析の設定を調整します。
- 詳細については「フロー解析のカスタマイズ」 を参照してください。
インクリメンタル解析モードでのフロー解析の実行
デフォルトでは、フロー解析は実行対象のスコープの完全解析を実行します。大規模なコード ベースの場合、この解析にはかなりの時間がかかることがあります。
フロー解析を使った解析を実行する最も一般的な方法は、毎日わずかに変化する 1 つのコード ベースに対して夜間テストを実行することです。インクリメンタル解析モードの目的は、この一般的なシナリオでの解析にかかる時間を短縮することです。インクリメンタル解析モードのフロー解析では、1 回目の実行中に重要な解析データが記憶され、2 回目以降のテストで再利用されます。再解析は、変更されたコードおよび変更されたコードに密接に関係するコードにだけ実行されます。
インクリメンタル解析を実行するときは次の点に注意してください。
- インクリメンタル解析モードで初めてフロー解析を実行すると、インクリメンタル解析モードではない場合に較べて解析に時間がかかることがあります。解析が遅くなる理由は、完全な解析を実行するのに加えて、今後のテストで再利用するためのデータも収集しているからです。
- データを格納するためのディスク容量が必要です。
インクリメンタル解析モードはテスト コンフィギュレーションの [静的] タブで設定できます。設定の詳細については 「フロー解析オプションの設定」を参照してください。
解析データのスワップを有効にしたフロー解析の実行
解析データのスワップ モードはデフォルトで有効化されています。スワップ モードでは、解析データをハードディスクに書き込みます。解析データのスワップは、インクリメンタル解析と同じ永続性記憶装置を使用し、インクリメンタル解析と似たプロセスで実行されます。大きなプロジェクトで解析を実行する場合、解析するソース コードの意味論的モデルを表す解析データが、フロー解析が使用できるすべてのメモリを消費する可能性があります。この現象が発生した場合、フロー解析は現在必要ない解析データをメモリから削除し、後でディスクから再び読み込みます。一般的に、 Xmx JVM オプションを使って JVM ヒープのサイズを大きくして C++test を実行することを推奨します。これはスワップを最小限にするためであり、結果としてパフォーマンスが向上します。充分なメモリがある場合、解析データのスワップは無効化しても構いません。スワップを無効化すると、コード解析の速度が上がる場合があります。解析データのスワップを無効化する方法については、 「[パフォーマンス] タブ」の [ディスクへの解析データのスワップを有効化] の説明を参照してください。
ビルトイン フロー解析テスト コンフィギュレーション
Flow Analysis Fast
セキュリティ ルールおよびカスタマイズが必要なルールを除き、すべてのフロー解析ルールが含まれます。解析の深さとして「最も浅い」を使用するため、 Standard や Aggressive よりも迅速に実行します (下の説明を参照)。適度な量の問題を発見し、違反の数が爆発的に増加するのを防止します。新しいプロジェクトでフロー解析ルールを推進するために Fast を 使用することを推奨します。より厳密な解析が必要な場合は、Standard に切り替えることを検討してください。
Flow Analysis Standard
セキュリティ ルールおよびカスタマイズが必要なルールを除き、すべてのフロー解析ルールが含まれます。Standard は Fast よりも深い解析を実行するため Fast よりも多くのバグを発見する可能性があります。また、Standard は Fast よりも長い実行時間を必要とします。より多くのバグを発見することの方が優先順位が高い場合、Standard に切り替えることを推奨します。解析パラメーターを再定義したりルールを再構成する必要がある場合、Standard を元にしてユーザー定義テスト コンフィギュレーションを作成できます。
Flow Analysis Aggressive
あらゆる疑わしいコードについて違反を検出したい場合、Aggressive の使用を推奨します。Aggressive は、たとえ「偽陽性」がレポートされるかもしれない場合でさえも、問題が疑われる場合に必ず違反をレポートします。Aggressive を使用すると、より多くのバグがレポートされますが、「実際には違反ではないケース」のレポートも増える可能性があります。
フロー解析の解析スコープ
フロー解析は、解析対照として選択したファイルのスコープ内で、関数、クラス、名前空間、コンパイル単位の境界を超えて解析を実行します。関数の呼び出しが存在し、その関数が別のファイルで定義されている場合、そのファイルが解析スコープに含まれている限り、フロー解析は関数内のパスを追跡できます。そのため、解析の精度はスコープに大きく依存します。
解析スコープ定義する (どのファイルを 1 セッションで解析するかを選択する) ための一般的なガイドラインは次のとおりです。
- 独立した複数のプロジェクトを同じ解析スコープに選択しないでください。完全修飾名を持つ型あるいは異なるシグニチャを持つ関数が複数のプロジェクトで定義されている場合があります。複数回定義されている関数の呼び出しがある場合、正しい関数まで呼び出しを追跡できないことがあります。
- コンパイル単位 A がコンパイル単位 B に依存していて、コンパイル単位 A を解析したい場合は、A の解析時に必要な情報を B からフロー解析が取得できるよう、通常 A と B の両方を解析スコープに含めることを推奨します。同じパターンは、依存関係があるプロジェクトにも適用されます。もちろん、解析対象のファイルが増えれば増えるほど、解析時間は長くなります。フロー解析は 1 つのファイルだけを解析することもできますが、そのような制限された解析では、多くの場合に解析精度が低下します。
フロー解析の結果の参照
このセクションでは、フロー解析でレポートされたバグを解析する方法について説明します。
テスト結果へのアクセス
フロー解析の解析結果は、通常の静的解析違反と共に [品質タスク] ビューに表示されます。ただし、その内容と形式は通常の静的解析違反とかなり異なります。フロー解析の解析結果は、ルール カテゴリと重要度で分類されたタスク リストとしてレポートされます。解析結果を重要度で表示するには、[品質タスク] ビューのプルダウン メニューをクリックし、[表示] > [詳細] をクリックします。
IDE から実行したテストの結果および IDE にインポートされた結果 (「GUI へのテスト結果のインポート」 を参照) は [品質タスク] ビューの「静的解析違反の修正」カテゴリにレポートされます。
コマンドライン インターフェイスからテストした場合、ルール違反はレポートの「静的解析」セクションにレポートされます。
レポートされたバグの詳細情報
レポートされたバグについての詳細情報を参照するには、 [品質タスク] ビューで静的解析違反を右クリックし、ショートカット メニューの [ルール ドキュメントの参照] をクリックします。黄色の「注意」マークが付いたノードを右クリックします。
違反からのテスト コンフィギュレーションの参照
[品質タスク] ビューの違反から、その違反を検出したテスト コンフィギュレーションを表示できます。違反を右クリックし、ショートカット メニューの [テスト コンフィギュレーションの参照] をクリックします。
テストのカスタマイズを担当し、不適切なルールをすぐに無効化したいグループ アーキテクトにとって、違反からテスト コンフィギュレーションにすぐにアクセスできるのはとても便利です。また、サーバー ベースの実行からテスト結果をインポートする開発者も、違反を検出したテスト コンフィギュレーションをレビューしなければならない場合があります。
フロー パスとは
フロー解析の違反は、[品質タスク] ビューで階層的なフロー パスによって表現されます。このパスは検出された問題につながるコードを正確に表します。フロー パス中の各要素は実行時に実行された各コード行です。フロー パス中に関数呼び出しがある場合、その関数呼び出しを表す要素の下のサブノードは関数内の実行フローを表します。フロー パスの最後の要素は常にバグが出現したポイントです。最終ポイントでなぜバグが出現しているのかを説明するために、完全なパスが表示されます。
フロー パスの要素はアイコンでマークされ、検出された違反につながるフローを解析するのに役立つ情報がツールチップとして表示されます。ツールチップを表示するには、要素の上にマウス ポインターを置きます。
レポートされた実行パスに関連するコード間を移動するには、[品質タスク] ビューの ツールバーの [次のタスク] または [前のタスク] ボタンをクリックします。
パスには省略 (...) が含まれることがあります。これは、 違反の原因から違反のポイントにつながる、異なる複数のパスがあることを表します。
重要な要素の特定
発見された違反に対して重要なパス要素は、濃色のボールのアイコンでマークされます。重要なパス要素の上にカーソルを置くと、その要素が重要な理由がツールチップに表示されます。たとえば、 「NULL の代入箇所」、「重要なデータ フロー」、「重要なデータ フローを含む (重要な要素を持つメソッド呼び出しノードに対する出力)」などです。重要な要素には、違反の原因と違反のポイントが該当するほか、違反につながるデータを持つ変数リストを変更する要素が該当します。たとえば、次のサンプル コードで変数 "a" が null ポインターを持つ場合:
1: b = a; 2: c = k;
行 1 は、後で変数 "b" で違反が発見される場合に重要です。そのため、重要な要素としてマークされます。
行 2 は、不正なデータを持つ変数に関係していないため、重要な要素としてマークされません。
違反の原因と違反のポイント
違反自体は、2 つのマークされたポイントを持つ実行パスとして表現されます。
- 違反の原因違反の「ソース」です。通常、ここが「不正なデータ」の発生ポイントです。たとえば null ポインターの間接参照のルールの場合、違反の原因は null ポインターの代入ポイントです。
- 違反ポイントこれは、「不正なデータ」が使用されるポイントであり、通常、プログラムのバグを発生させます。たとえば null ポインターの間接参照の ルールの場合、null ポインターを持つ変数が間接参照されるポイントです。
レポートされた違反 ( 黄色の注意アイコンがあるノード) を右クリックして [違反の原因を表示] または [違反ポイントを表示] を選択するだけで、違反の原因と違反のポイントに簡単にアクセスできます。
エディターで該当行がハイライト表示されます。
違反メッセージが参照しているコードをハイライト表示する
違反メッセージをダブルクリックするだけで、フロー解析違反メッセージが参照しているソース コードを簡単にハイライト表示できます。フロー パスが水色にハイライト表示された状態で、選択した違反に対応するソース コードが表示されます。例外がスローされる行はピンク色で表示されます。違反にとって重要なすべての行、つまり違反の原因、違反のポイント、および重要なデータ フローは、濃い色でハイライト表示されます。レポートされた実行パスに関連するコード間を移動するには、[品質タスク] ビューの ツールバーの [次のタスク] または [前のタスク] ボタンをクリックします。
ヒント
- チェック対象から除外するルールは無効にしたり抑制したりできます。詳細については、「[静的] タブ - 静的解析の方法を定義する」 を参照してください。
- C++test に付属のフロー解析ルールについては、[Parasoft] メニューの [ ヘルプ] をクリックして 『Parasoft C++test 静的解析ルール ガイド』ブックのフロー解析カテゴリるるーの説明を参照してください。
パス トレースの簡略表示と詳細表示
簡略表示と詳細表示の違い
デフォルトでは、解析結果は簡略パス トレースで表示されます。簡略表示では、欠陥のあるパス中の重要な実行可能ステートメントだけが表示されます。このため、レポートされるさまざまな問題の概要をすぐに把握することができます。
詳細なパスを表示することもできます。詳細表示にした場合、 検出されたバグが出現しているとフロー解析が見なした実行パスのすべての要素が展開可能なタスク ノードに表示されます。言い換えると、フロー解析はこのバグに至る完全な実行パスを表示します。ユーザーは、発見された問題についての完全な情報を得ると共に、フロー解析が行った推測を得ることができます。ときには、フロー解析がこの問題をレポートした理由を知るために、詳細情報が必要不可欠なこともあります。
ほとんどの場合、レポートされた問題を理解するために詳細情報は必要ないかもしれません。そのような場合は簡略表示の方が便利です。簡略表示では、 検出されたバグが出現しているとフロー解析が見なした実行パスの中から、最も重要なものだけがタスク ノードに表示されます。フロー解析は、次の実行パス要素を重要な要素として扱い、簡略表示でレポートします。
- 違反の原因 (通常は不正データの発生ポイント)
- 違反ポイント (バグが出現しているポイント)
- 例外をスローしている要素 (制御フローに著しく影響するため)
- 重要なデータ フロー要素 (ある変数から別の変数への不正なデータ フローのポイント。可能性としては、不正なデータを変数に代入している、不正なデータをパラメーターとしてメソッドに渡している、不正なデータをメソッドが返している、といった処理があります。この情報は、違反の原因を突き止めるために非常に重要です。)
次の図は「詳細表示」の例です。
次の図は同じ違反の「簡略表示」の例です。
ソース コード エディターで詳細な実行パスを確認する
[品質タスク] ビューを簡略表示にして最重要な要素だけを表示している場合でも、違反を選択すると、すべてのパス要素がハイライト表示され、対応するコードが IDE のソース コード エディターに表示されます。したがって、簡略表示によって問題を直ちに見極めた後、違反の発生原因について詳しい情報を得たければ、ソース コード エディターで詳細な実行パスを確認することができます。
詳細表示に設定する
特定の違反の詳細情報を表示するには、次の操作を行います。
- 違反のタスク ノードを右クリックし、ショートカット メニューの [完全な違反のパスを表示] をクリックします。
常に詳細表示にするには、次の設定を行います。
- [Parasoft] メニューの [設定] をクリックします。
- [品質タスク] を選択します。
- [Flow Analysis 違反の完全なパスを表示する] チェックボックスをオンにします。
フロー解析のカスタマイズ
このセクションでは、フロー解析をカスタマイズする方法について説明します。検出するバグを指定する方法、ルール パラメーター、解析オプション、および特定のルールでチェックするリソースを指定する方法について説明します。
検出するバグの指定
フロー解析が検出するバグの種類は、通常の静的解析とほぼ同様に、テスト コンフィギュレーションで有効化されたルールによって決定されます。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-SWITCH ルールを実行した場合、[原因が特定できない場合は違反をレポートしない] オプションが無効な場合にだけ、複数の原因を持つ違反がレポートされます。
#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() といった関数です。フロー解析はアプリケーションの実行フローを解析するため、アプリケーションを直ちに停止して実行フローをブレークする終了関数を把握することは、フロー解析にとって非常に重要です。終了関数を把握していない場合、フロー解析がアプリケーションについて誤った推測を行う可能性があります。
フロー解析は、標準ライブラリで定義された終了関数を認識します。しかし、非標準ライブラリに独自の終了関数が定義されることも多いため、それだけでは十分ではない場合もあります。独自の終了関数をアプリケーションで使用している場合、このタブのリストにその終了関数を追加します。終了関数をリストに追加しない場合、終了関数による実行パスについてフロー解析が誤った結果をレポートする (本当は違反ではないものを違反としてレポートする) 可能性があります。
サポート対象 API が表示されたリストを使用して、各種 API の終了関数を有効化/無効化したり、終了関数を含む独自の API を定義します。特定のライブラリの終了関数についての情報を追加するには、[追加] をクリックし、ライブラリの名前を入力してエンター キーを押します。新しいエントリが終了 API テーブルに挿入されます。次に、[編集] ボタンをクリックしてテーブルに入力します。テーブルには以下の列があります。
- 有効: 解析中にビルトイン終了関数またはカスタム終了関数を考慮するかどうかを指定します。•
- 完全修飾型名または名前空間: 特定の終了関数のエンティティを指定します。このフィールドに何も指定しない場合、[関数名] で指定された名前のグローバル関数が終了関数と見なされます。
- 例: たとえば、終了関数が名前間空間 myNameSpace の myClass で宣言されている場合、このフィールドの値は myNameSpace::myClass になります。あるいは、終了関数が型において宣言されてなく、名前空間 myNameSpace にだけ属している場合、このフィールドの値は myNameSpace になります。•
- 関数名: 終了関数の名前を指定します。
- + サブクラスの定義: サブクラス中の終了関数の定義も、終了関数として見なすかどうかを指定します。この設定は、完全修飾型名が指定されている場合にだけ意味を持ち、インスタンス関数と非インスタンス関数の両方に適用されます。
[マルチスレッド] タブ
[マルチスレッド] タブでは、既知のマルチスレッド関数を有効化/無効化するだけでなく、スレッド間の同期化のための関数を定義することができます。このタブで定義した情報は、BD.TRS (スレッドと同期化) カテゴリのルールに影響します。BD.TRS カテゴリのルールは、[マルチスレッド] タブで定義および有効化されているすべての関数をチェックします。
サポートされる API のチェックボックスをオンにして、さまざまな API の同期化関数を有効化するだけでなく、同期化関数を含むユーザー独自の API からの関数を有効化することもできます。特定のライブラリの同期化関数についての情報を追加するには、[追加] をクリックし、ライブラリの名前を入力してエンター キーを押します。新しいエントリがマルチスレッド API テーブルに挿入されます。次に、エントリをクリックして [編集] ボタンをクリックします。表示されたダイアログで、特定の同期化関数を定義します。このダイアログでは次の種類の関数を定義することができます。
- ロック関数 (たとえば mutex の取得)
- ロック解除関数 (たとえば mutex の解放)
- スリープ関数
これらの関数を定義する方法は、リソースの場合と非常に似ています。「BD.RES ルールでチェックするリソースを指定する」を参照してください。
[リソース] タブ
[リソース] タブでは、BD.RES (リソース) カテゴリのルールを使ってチェックするリソースを定義することができます。BD.RES カテゴリのルールは、[リソース] タブで定義および有効化されているすべてのリソースについて、リソースが正しく使用されているかをチェックします。
リソースを定義する方法の詳細については、以下のセクションを参照してください。
BD.RES ルールでチェックするリソースを指定する
テスト コンフィギュレーションの [静的] > [フロー解析オプション] > [リソース] タブでは、BD.RES (リソース) カテゴリのルールを使ってチェックするリソースを定義することができます。BD.RES カテゴリのルールは、[リソース] タブで定義および有効化されているすべてのリソースについて、リソースが正しく使用されているかをチェックします。
さまざまな種類のリソースのチェックを有効化/無効化することができます。カスタム リソースを管理するには [追加]、[削除]、[編集] ボタンをクリックします。
特定のライブラリのリソースの情報を追加するには、次の操作を行います。
- [追加] ボタンをクリックします。
- [リソースのタイプ] に名前を入力します。
- Enter キーを押します。新しいエントリが追加されます。
- 適切な場合、[アプリケーション終了時の違反をレポートしない] オプションをオフにします。
- [編集] ボタンをクリックし、表示されたダイアログで、リソースのアロケーター、クローザー、チェッカー、セーフ関数を定義します。それぞれのパネルのテーブルの 1 行が 1 つの関数に相当し、明確に 1 つの関数 (ワイルドカードが使用された場合は 1 つの関数ファミリー) を識別し、リソースを割り当て/解放する方法を判断するのに必要な情報が含まれている必要があります。詳細については以下のセクションを参照してください。
リソース アロケーターの設定
[リソース アロケーター ] テーブルには、リソースを生成する関数の記述を入力できます。テーブルには以下の列があります。
- 有効: 解析中にアロケーターを考慮するかどうかを指定します。
- 完全修飾型名または名前空間 (ワイルドカード): 関数が宣言された型または名前空間の完全修飾名です。任意の型 (または名前空間) で宣言された関数、また型や名前空間の外で宣言されたグローバル関数を指定するには '*' を使用します。
- 関数名 (ワイルドカード): アロケーター関数の名前を指定します。任意の数の任意の文字を表すには '*' を使用します。
- リソース パラメーター: 関数が リソースを割り当てる1つ以上のパラメーターを指定します。パラメーターの位置 (1 から開始) を指定するか、アスタリスク (*) を指定して、すべてのパラメーターにリソースを割り当てます。
- + サブクラスの定義: サブクラス中の (指定された名前の) アロケーター関数の定義も、アロケーター関数として見なすかどうかを指定します。この設定は、インスタンス関数と非インスタンス関数の両方に適用されます。
- "this" オブジェクトがリソース: 関数が呼び出されたオブジェクトのリソースを割り当てることを示します。
- リソース オブジェクトを返す: 関数が割り当てられたリソースを返すことを示します。
- エラー時の戻り値の制約: 割り当てエラー時にリソース アロケーターが汎整数値を返す場合、戻り値の制約を指定します。条件は次の書式で指定する必要があります。<比較演算子><整数値> たとえば、エラー時に関数がゼロ以外の値を返す場合、フィールドに "!=0" (引用符は入力しない) 入力します。エラー時のリターン コードが -1 の場合、"==-1" と入力します。"!=" および "==" のほかに、次の演算子を使用してエラー条件を指定できます: ">"、">="、"<"、および "<="
一般的に、アロケーター関数はエラー コードを返してリソース割り当ての失敗を表します。アロケーター関数 はリソースへのポインターを返し、通常 NULL ポインターはリソース割り当ての失敗を表します。リソース リークを探すときに、フロー解析はリソースの割り当てが成功したか失敗したかを把握できる必要があります。これは、割り当てが実際に発生したパス上で発見されないクローザー関数への呼び出しだけをフロー解析がレポートするのに役立ちます。リソース アロケーター関数がリソースへのポインターを返し、このポインターが NULL でない場合、フロー解析はリソースが正常に割り当てられたと推測します。
リソース クローザーの設定
[リソース クローザー] テーブルには、リソースをクローズする関数の記述を入力できます。テーブルには以下の列があります。
- 有効: 解析中にクローザーを考慮するかどうかを指定します。
- 完全修飾型名または名前空間 (ワイルドカード): 関数が宣言された型または名前空間の完全修飾名です。任意の型 (または名前空間) で宣言された関数、また型や名前空間の外で宣言されたグローバル関数を指定するには '*' を使用します。
- 関数名 (ワイルドカード): クローザー関数の名前を指定します。任意の数の任意の文字を表すには '*' を使用します。
- + サブクラスの定義: サブクラス中の (指定された名前の) クローザー関数の定義も、クローザー関数として見なすかどうかを指定します。この設定は、インスタンス関数と非インスタンス関数の両方に適用されます。
- "this" オブジェクトがリソース: 関数が呼び出されたオブジェクトのリソースをクローズすることを示します。
- リソース パラメーター: メソッドが リソースをクローズする 1つ以上のパラメーターを指定します。パラメーターの位置 (1 から開始) を指定するか、アスタリスク (*) を指定して、すべてのパラメーターにリソースを割り当てます。
リソース チェッカーの設定
[リソース チェッカー] テーブルには、リソースが開かれているかどうかをチェックする関数の記述を入力できます。テーブルには以下の列があります。
- 有効: 解析中にチェッカーを考慮するかどうかを指定します。
- 完全修飾型名または名前空間 (ワイルドカード): 関数が宣言された型または名前空間の完全修飾名です。任意の型 (または名前空間) で宣言された関数、また型や名前空間の外で宣言されたグローバル関数を指定するには '*' を使用します。
- 関数名 (ワイルドカード): チェッカー関数の名前を指定します。任意の数の任意の文字を表すには '*' を使用します。
- + サブクラスの定義: サブクラス中の (指定された名前の) チェッカーの定義も、チェッカーとして見なすかどうかを指定します。この設定は、インスタンス関数と非インスタンス関数の両方に適用されます。
- "this" オブジェクトがリソース: 関数が呼び出されたオブジェクトのリソースをチェックすることを示します。
- リソース パラメーター: 関数が リソースをチェックする 1つ以上のパラメーターを指定します。パラメーターの位置 (1 から開始) を指定するか、アスタリスク (*) を指定して、すべてのパラメーターにリソースを割り当てます。
- リソースがオープンされているときの戻り値: リソースが開かれている場合にチェッカー関数が返す値を指定します。true または false を指定できます。
true
またはfalse
を指定できます。
セーフ関数の設定
[セーフ関数] テーブルには、クローズ済みのリソースで呼び出しても安全な関数 (BD-RES-FREE ルールの違反をレポートしない関数) の記述を入力できます。テーブルには以下の列があります。
- 有効: 解析中にセーフ関数を考慮するかどうかを指定します。
- 完全修飾型名または名前空間 (ワイルドカード): 関数が宣言された型または名前空間の完全修飾名です。任意の型 (または名前空間) で宣言された関数、また型や名前空間の外で宣言されたグローバル関数を指定するには '*' を使用します。
- 関数名 (ワイルドカード): セーフ関数の名前を指定します。任意の数の任意の文字を表すには '*' を使用します。
- + サブクラスの定義: サブクラス中の (指定された名前の) 定義も、安全と見なすかどうかを指定します。この設定は、インスタンス関数と非インスタンス関数の両方に適用されます。
ビルトインでサポートされるリソース
- デフォルトでは、ビルトインでサポートされるリソースが [リソース] タブに表示されます。ビルトインでサポートされるリソースは次のとおりです。ディレクトリ(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)
カスタム リソースの定義
カスタム リソースを定義する例として、次の非標準のリソースを使った単純な 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" オブジェクトがリソース: オフ
- リソース オブジェクトを返す: オン
- パラメーターの使用: 空白のままにする
- リソースを表すパラメーターの位置 (1 から開始): 空白のままにする
- 割り当て解除関数を定義します。
- 完全修飾型名または名前空間: 空白のままにする
- メソッド名: myDealloc
- + サブクラスの定義: オフ
- '"this" オブジェクトがリソース: オフ
- リソースを表すパラメーターの位置 (1 から開始): 1
別の例を考えてみましょう。ここでは 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
を定義します。- 完全修飾型名または名前空間: 空白のままにする
- + サブクラスの定義: オフ
- '"this" オブジェクトがリソース: オフ
- リソース オブジェクトを返す: オフ
- エラー時の戻り値の制約: ==-1
- リソースを表すパラメーターの位置 (1 から開始): 1
- 割り当て解除関数を定義します。
- 完全修飾型名または名前空間: 空白のままにする
- メソッド名: closeMyResource
- + サブクラスの定義: オフ
- '"this" オブジェクトがリソース: オフ
- リソースを表すパラメーターの位置 (1 から開始): 1
[拡張解析] スコープ タブ
コード解析を実行する際、フロー 解析はテスト対象のソース ファイルおよびヘッダー ファイルに定義された関数定義を処理します。テスト スコープ外のヘッダー ファイルで定義された関数は解析されず、フロー解析はそのセマンティクスを考慮しません。フロー解析がテスト スコープ外で定義された関数定義の情報を必要とする場合、以下のようにオプションを設定できます。
解析対象の外部ファイル: フロー解析で解析する追加のヘッダー ファイルへの絶対パスを指定します。ワイルドカードを使用してパターンを指定します。
解析対象の外部関数: フロー解析で解析する追加の関数を指定します。テーブルに以下の情報を入力します。テーブルに以下の情報を入力します。
- 有効: 解析中に関数を考慮するかどうかを指定します。
- 完全修飾型名または名前空間 (ワイルドカード): 関数が宣言された型または名前空間の完全修飾名です。任意の型 (または名前空間) で宣言された関数、また型や名前空間の外で宣言されたグローバル関数を指定するには '*' を使用します。
- 関数名 (ワイルドカード): 関数の名前を指定します。任意の数の任意の文字を表すには '*' を使用します。
- パラメーター数: 関数のパラメーターの数を指定します。任意の数のパラメーターを指定するには、'-1' を使用します。
- + サブクラスの定義: サブクラス中の (指定された名前の) 定義も含めるかどうかを指定します。この設定は、インスタンス関数と非インスタンス関数の両方に適用されます。
常に解析される関数の指定
[常に解析すべき関数] オプションは、実行パスで見つかったら常に解析する関数を定義します。これにより、特定のパスを解析したとき、通常は入らない関数を必ず解析するようにできます。
[有効] チェックボックスをオンにして、以下を指定します。
- 完全修飾型名または名前空間 (ワイルドカード): 関数が宣言された型または名前空間の完全修飾名です。
- 関数名 (ワイルドカード): 関数の名前を指定します。
- + サブクラスの定義: サブクラス中の定関数も考慮するかどうかを指定します。
[コンパイラ固有の設定] タブ
"errno" 値の内部表現: 規格では、errno は変更可能な int 型の lvalue であると定義されています。errno がマクロであるか、外部リンケージを持つと宣言された識別子であるかは未指定です。処理系は、グローバル変数 "errno" または "__errno" を使用するか、呼び出し先関数の異なる "(*errno_function())" というパターンを使用できます。このオプションを使用すると、正規表現を使用してこれらの変数および関数の名前を指定できます。
- 関数名のパターン: "errno" 値が使用される場合に呼び出される関数の名前です。正規表現で名前を指定します。
- 変数名のパターン: "errno" 値が使用される場合に呼び出される変数の名前です。正規表現で名前を指定します。
ヘッダー <ctype.h> の関数への呼び出しの内部表現: 規格には、<ctype.h> ヘッダーに定義するべき関数がいくつか指定されています。一部の処理系 (C モードの GNU GCC など) は、内部配列の要素とフラグを比較してテストするコードに展開されるマクロとして、これらの関数を定義します。これは、グローバル配列または関数によって返されるポインターである可能性があります。このオプションを使用すると、正規表現を使用してこれらの変数および関数の名前を指定できます。
- 関数名のパターン: <ctype.h> ヘッダーのいずれかの関数の代わりに内部的に呼び出される関数の名前です。正規表現で名前を指定します。正規表現で名前を指定します。
- 変数名のパターン: <ctype.h> ヘッダーのいずれかの関数を呼び出した後に内部的に使用される変数の名前です。正規表現で名前を指定します。正規表現で名前を指定します。