
今日は、プログラマの知識や経験のレベルに関係なく、ソースコード分析ツールが役立つ理由についてのトピックに専念したいと思います。 そして、このような分析の利点は、すべてのプログラマーに知られているツールの例-WinMergeによって実証されます。
アプリケーションコードのエラーが早く検出されるほど、修正コストは安くなります。 私たちは、最も安価で簡単なエラーがコードを書くの過程で排除することができると結論付けています。 エラーがない場合はいっそのこと、書くことにします。 ここでは唯一の私は間違いを作りたいので、ちょうど彼の手をぴしゃりとコードが正しく書き込まれています。 しかし、それは不可能であるため。 アプローチはまだ動作しません「エラーなしで記述する必要があります」。
急いでいない非常に熟練したプログラマーでさえ、単純なタイプミスからアルゴリズムの論理エラーで終わるまで、ミスを犯します。 ここでは、多数の法則が機能します。 ここでは、特定の「if」ステートメントごとにエラーを作成することは不可能と思われます。 そして彼は200回の比較を書きましたが、一度、彼は間違っていました。 これは、Andrey UrazovによるCodeFest 2010カンファレンスでのレポート「Quality-Oriented Programming」で興味深いことに語られてい
ます (
音声記録を参照 )。 手短に言えば、私は彼の次の考えを述べたい。 開発者の経験がどれほどであっても、コードにはエラーが表示されます。 これらの間違いを止めることはできません。 しかし、それらの多くで正常に通常行われているよりもはるかに早い段階で戦うことができます。
通常、エラーに対する防衛の非常に最初のレベルは、新たに書かれたコードのためのユニットテストを作成することです。 テストは、テストするコードの前に記述される場合があります。 ただし、ユニットテストには多くの欠点がありますが、これらはプログラマには既に知られているため、詳細に検討しません。 多くの予備的なデータ準備を必要とする関数の単体テストを作成するのは必ずしも簡単ではありません。 プロジェクトの要件が急速に変化している場合はユニットテストが負担になってきました。 テストの作成と保守には多くの時間がかかります。 すべてのブランチをテストするのは必ずしも簡単ではありません。 また、単体テストが存在せず、計画されていないモノリシックプロジェクトをギフトとして取得できます。 単体テストの大きな利点を否定することなく、これは良い防御ラインですが、大幅に強化することができるし、そうするべきだと思います。
静的コード解析 - プログラマは、多くの場合であっても、以前の防御レベルを無視します。 多くは、コンパイラによって発行される診断警告の範囲から逸脱することなく可能コード分析を使用しています。 一方、コーディング段階で論理エラーと単純なタイプミスのかなりの割合を識別するツールのクラスがあります。 これらのツールは、いくつかのコーディングパターンの知識に基づいて高レベルのコード検証を実行し、ヒューリスティックアルゴリズムを使用し、柔軟な構成システムを備えています。
静的解析では、当然のことながら、あまりにも、欠点があります。 彼は単に多くの種類のエラーを検出することはできません。 アナライザーは誤検知を与え、コードにこのような変更を加えることを強制し、このコードを気に入って安全と評価します。
しかし、大きな利点があります。 分析は、その使用頻度に関係なく、プログラムのすべてのブランチを対象としています。 分析は実行ステージに依存しません。 不完全なコードでもチェックする機会があります。 あなたが継承、多くのコードを確認することができます。 静的検証は高速であり、動的検証ツールとは異なり、拡張性に優れています。
ソースコードの静的分析について多くの言葉が言われました。 さあ、練習に集中しましょう。 私はC ++で書かれたアプリケーションを1つ取る、といくつかのバグを発見しようとするでしょう。
小さくて有名なものを選びたかった。 私は多くのツールを使用せず、[スタート]メニューのプログラムのリストをめくるので、私の選択はWinMergeに限定されていました。 WinMergeは
source 、小規模(約186,000行)で利用可能です。 プログラムは十分な品質です。 私はそれを完璧に使用し、ソースコードの25%がコメントで占められていること(良い兆候)に基づいて話しています。 一般的には、最適な選択。
最新の利用可能なバージョン2.13.20(10/20/2010日付)がダウンロードされました。 分析には、開発中の汎用アナライザーのプロトタイプを使用しました。 私はこのもう少しで住むます。
今静的コード分析PVS-スタジオの一部は、ルールの二組を含みます。 一つは、検出するように設計されて
64ビットの欠陥を 、そして他方がするように設計された
OpenMPプログラムをチェック 。 現在、一般的なルールのセットを開発しています。 ベータ版もまだ利用できませんが、何かがすでに機能しているので、少なくとも少しのエラーとの本当の戦争が本当に欲しいです。 私たちは、その自己宣伝について書きしないでください、自由のための新しいルールのセットを作るために計画しています。 PVS-Studio 4.00の一部として、1〜2か月後に新しいツールを公開します。
そこで、ここに30分以内にWinMerge-2.13.20のソースコードで見つけた興味深い点をいくつか示します(15分検証、15分で結果を表示)。 他にも疑わしい場所がありますが、本当に間違いがあるかどうかを判断するのは努力です。 今では、1つのプロジェクトでできるだけ多くの欠陥を見つけるのに問題はありません。 静的解析がいかに役立つか、大まかな研究でさえ多くのエラーを迅速に特定できることを優雅に示したいと思います。
最初の例。 アナライザーは、「V530-関数 'Foo'の戻り値を使用する必要があります」といういくつかのエラーを指摘しました。 一般的に、これらの警告は、関数の誤った使用に関連して表示されます。 コードを考えてみましょう:
/ **
* @brief指定したアイテムの両側のファイル名を取得します。
アイテムが特別なアイテムである場合*空の文字列を返します@Note。
* /
void CDirView :: GetItemFileNames(int sel、
String&strLeft、String&strRight)const
{
UINT_PTR diffpos = GetItemKey(sel);
もし(diffpos ==(UINT_PTR)SPECIAL_ITEM_POS)
{
strLeft.empty();
strRight.empty();
}
他に
{
...
}
}
この関数は、特定の状況で、2行の空白行を返す必要があります。 ただし、過失により、std :: string :: empty()関数はstd :: string :: clear()の代わりに呼び出されます。 これは、偶然にも、見かけほどまれな間違いではありません。 他の多くのプロジェクトで彼女に会った。 それを含むことは、別のWinMerge関数にあります。
/ **
* @briefバリアントの値をクリアします(デフォルトにリセット)。
* /
void VariantValue ::クリア()
{
m_vtype = VT_NULL;
m_bvalue = falseは、
m_ivalue = 0;
m_fvalue = 0;
m_svalue.empty();
m_tvalue = 0;
}
ここでも、予想される行のクリアは発生しません。
ただし、「V501-'||'の左右に同じ部分式がある」という警告 演算子 ":
BUFFERTYPE m_nBufferType [2];
...
//名前のないバッファを処理します
if((m_nBufferType [nBuffer] == BUFFER_UNNAMED)||
(m_nBufferType [nBuffer] == BUFFER_UNNAMED))
nSaveErrorCode = SAVE_NO_FILENAME。
近くのコードを見ると、類推すると次のようになります。
(m_nBufferType [0] == BUFFER_UNNAMED)||
(m_nBufferType [1] == BUFFER_UNNAMED)
そして、そうでない場合には、まだ間違いのいくつかの種類があります。
さまざまな緊急事態が発生した場合、WinMergeはエラーの報告を試みますが、多くの場合失敗します。 これは、アナライザをコーディングする方法の良い例は、プログラムのほとんど使用されない部分に任意のタイプのエラーを検出することができる方法です。 コードにはいくつかのエラーがあり、PVS-Studioは次のように報告します。「V510-'Format'関数は、クラス型変数を 'N'実引数として受け取ることを期待されていません」。 コード例:
String GetSysError(int nerr);
...
CString msg;
msg.Format(
_T(「レジストリキーHKCU /%sを開けませんでした:\ n \ t%d:%s」)、
f_RegDir、retVal、GetSysError(retVal));
一見、すべてが正常です。 それはちょうど«文字列»のタイプです«のstd :: wstringの»のような他のものではありません。 したがって、最良の場合は意味不明なものを出力し、最悪の場合はアクセス違反エラーが発生します。 代わりに、スタック内の文字列へのポインタをタイプ«のstd :: wstringの»のオブジェクトを配置しています。 この状況については、「
Big Brother Helps You 」という記事で詳しく説明しました。 正しいコードにはc_str()の呼び出しが含まれている必要があります。
msg.Format(
_T(「レジストリキーHKCU /%sを開けませんでした:\ n \ t%d:%s」)、
f_RegDir、retVal、GetSysError(retVal).c_str());
さらに進みましょう。 これは非常に疑わしいコードです。 ここに間違いがあるかどうか、わかりません。 しかし、「if」演算子の2つのブランチに完全に同一のコードが含まれているのは奇妙です。 アナライザーは、診断メッセージ「V532-「then」ステートメントは「else」ステートメントと同等です」でこの状況について警告しました。 ここでは、不審なコードは次のとおりです。
if(最大<INT_MAX)
{
(I =分;私は最大の<;私は++)のために
{
if(eptr> = md-> end_subject ||
IS_NEWLINE(セプター))
休憩;
eptr ++;
while(eptr <md-> end_subject &&
(* eptr&0xc0)== 0x80)
eptr ++;
}
}
他に
{
(I =分;私は最大の<;私は++)のために
{
if(eptr> = md-> end_subject ||
IS_NEWLINE(セプター))
休憩;
eptr ++;
while(eptr <md-> end_subject &&
(* eptr&0xc0)== 0x80)
eptr ++;
}
}
}
ここにあるものを感じます:「これは、まあ、正当な理由です。」
さて、もう1つの例と終了です。 アナライザーは、疑わしいサイクルを検出しました。「V534-「for」演算子内で間違った変数が比較されている可能性があります。 「i」のレビューを検討してください。
//テキストからバイトの翻訳された配列の長さを取得します。
int Text2BinTranslator :: iLengthOfTransToBin(
char * src、int srclen)
{
...
for(k = i; i <srclen; k ++)
{
if(src [k] == '>')
休憩;
}
...
}
このコードは、アクセス違反の傾向があります。 ループは、「>」文字が見つかるか、「srclen」文字の長さの文字列が終了するまで継続する必要があります。 比較のために変数「i」が使用されたのは偶然であり、「k」ではありません。 「>」文字が見つからない場合は、おそらくすべてが終了します。
おわりに
静的解析を忘れないでください。 彼は、良いコードでも面白いものを見つけることができます。 また、準備ができたら、後で私たちの
サイトに戻って無料の汎用アナライザーを試してください。