プログラムのエラーのツアーと、静的コード分析の有用性のデモを続けます。
これは、まだダウンロードできない
PVS-Studioのバージョンに関する最後の投稿です。 1週間以内に、新しい一連の一般的なルールを使用して最初のベータ版を既に試すことができると考えています。
2つのプロジェクトを考えてみましょう。 1つ目は
Fennec Media Projectです。 これは、高解像度でオーディオとビデオを再生することに焦点を当てたユニバーサルメディアプレーヤーです。 ソースコードのセットには多くのプラグインとコーデックが含まれていますが、分析されるのはプレーヤーのみです。 最新バージョン1.2 Alphaのソースコードは、
ここから入手でき
ます 。
2番目のプロジェクトは
qutIMです。 これは、オープンソースのクロスプラットフォームのインスタントメッセージングクライアントです。 コードは2010年11月の初めに分析されました。 ソースコードセットは、開発者の1人から提供されましたが、公式サイトからソースコードをダウンロードすることもできます。 ところで、この開発者はここにいます
-gorthauer87 。
フェネックメディアプロジェクト。 通常のエラー数を含む小さな通常のプロジェクト。 これが最初の間違いです。 または、カウント方法に応じて、最初の2つのエラー。 一般に、変数「b」の代わりに2つの場所で、変数「a」が使用されます。
int fennec_tag_item_compare(struct fennec_audiotag_item * a、
struct fennec_audiotag_item * b)
{
int v;
if(a-> tsize && a-> tsize)
v = abs(str_cmp(a-> tdata、a-> tdata));
他に
vは1です。
return v;
}
条件「a-> tsize && a-> tsize」は明らかに疑わしいため、PVS-Studioはこのコードを指しています。
診断メッセージとコード内のエラーの場所:
V501 '&&'演算子の左右に同じ部分式があります:a-> tsize && a-> tsize media library.c 1076 |
そして今、すべてのプログラマーの心を大切にしている-余分なセミコロン。 最初のスニペットは次のとおりです。
int settings_default(void)
{
...
for(i = 0; i <16; i ++);
for(j = 0; j <32; j ++)
{
settings.conversion.equalizer_bands.boost [i] [j] = 0.0;
settings.conversion.equalizer_bands.preamp [i] = 0.0;
}
}
PVS-Studioメッセージと場所コード:
V529奇数セミコロン ';' 「for」演算子の後。 settings.c 483 |
2番目のスニペット:
int trans_rest(transcoder_settings * trans)
{
...
for(i = 0; i <16; i ++);
{
trans-> eq.eq.preamp [i] = 0.0;
for(j = 0; j <32; j ++)
{
trans-> eq.eq.boost [i] [j] = 0.0;
}
}
}
PVS-Studioメッセージと場所コード:
V529奇数セミコロン ';' 「for」演算子の後。 settings.c 913 |
「;」が付いた3番目と4番目のフラグメントがあります。 しかし、私はここでそれを与えません。 すべてが同じタイプで面白くない。
さらにまったく間違いではありませんが、ほぼ間違いありません。 _beginthreadex関数の代わりに、CreateThreadが使用されます。 FennecにはCreateThreadの呼び出しがいくつかありますが、1つの例を示します。
t_sys_thread_handle sys_thread_call(t_sys_thread_function cfunc)
{
符号なしlong tpr = 0;
符号なしlong tid = 0;
リターン(t_sys_thread_handle)
CreateThread(0、0、cfunc、&tpr、0、&tid);
}
PVS-Studioの警告と場所のコード:
V513 CreateThread / ExitThread関数の代わりに_beginthreadex / _endthreadex関数を使用します。 system.c 331 |
CreateThread / ExitThreadの代わりに_beginthreadex / _endthreadexを使用する理由については深く掘り下げません。 私は非常に簡潔に書きますが、この問題の詳細な議論は
ここ 、
ここ 、
ここで読むことができ
ます 。
聖文(MSDN)は次のように述べています。
Cランタイムライブラリ(CRT)を呼び出す実行可能ファイル内のスレッドは、CreateThreadおよび
ExitThreadではなく、
_beginthreadexおよび
_endthreadex関数をスレッド管理に使用する必要があります。 これには、マルチスレッドバージョンのCRTを使用する必要があります。 CreateThreadを使用して作成されたスレッドがCRTを呼び出すと、CRTはメモリ不足の状態でプロセスを終了する場合があります。
一般に、安全であり、常に_beginthreadex / _endthreadexを呼び出すことをお勧めします。 ちなみに、これはジェフリーリヒターが第6章「Windows for Professionals:64ビットバージョンのWindowsの仕様を考慮した効果的なWin32アプリケーションの作成」/ Perで推奨しているとおりです。 英語から -第4版
memset関数のいくつかの失敗した使用がありました。 ところで、最近まで、私はmemset、memcmp、memcpyの使用に関連する不安は過去のものだと思っていました。 同様に、彼らは以前これを書いていましたが、今では誰もが危険について知っており、これらの関数で正確であり、sizeof()を使用し、STLのコンテナを使用しています。 そして今、すべてがピンクと柔らかいです。 違います。 過去1か月間、これらの機能に多くの間違いがありました。 それで、これらの間違いはすべて咲き、臭いがします。
フェネックに戻る。 最初のmemset:
#define uinput_size 1024
typedef wchar_t letter;
文字uinput_text [uinput_size];
文字列basewindows_getuserinput(定数文字列タイトル、
const string cap、const string dtxt)
{
memset(uinput_text、0、uinput_size);
...
}
PVS-Studioの診断とロケーションコード:
V512「memset」関数を呼び出すと、バッファーのオーバーフローまたはアンダーフローが発生します。 ベースwindows.c 151 |
一見したところ、「memset(uinput_text、0、uinput_size);」ですべてが問題ありません。 そして、多分それは、「手紙」のタイプが「チャー」であった当時、さらに良かったです。 しかし、現在は「wchar_t」であり、その結果、バッファーの半分のみがクリアされます。
2番目に失敗したmemset:
typedef wchar_t letter;
レター名[30];
int Conv_EqualizerProc(HWND hwnd、UINT uMsg、
WPARAM wParam、LPARAM lParam)
{
...
memset(eqp.name、0、30);
...
}
本当に魔法の数字は悪です。 「sizeof(eqp.name)」と書くのは難しくないようです。 しかし、私たちはしつこく書かないで、何度も何度も自分の足を撃ち続けます:)。
PVS-Studioの診断とロケーションコード:
V512「memset」関数を呼び出すと、バッファーのオーバーフローまたはアンダーフローが発生します。 ベースwindows.c 2892 |
さて、別の場所にそのようなバグがあります:
V512「memset」関数を呼び出すと、バッファーのオーバーフローまたはアンダーフローが発生します。 transcode settings.c 588 |
おそらく、いくつかのプログラムでは、ファイルを開く/保存するためのダイアログが奇妙なもので動作すること、または利用可能な拡張子のフィールドにナンセンスがあることに気づいたことがあります。 今、あなたはこの足がどこから成長するかを見つけます。
Windows APIには、文字列へのポインターが二重ゼロで終わる必要がある構造があります。 最も使用されるのは、OPENFILENAME構造体のlpstrFilterメンバーです。 このパラメーターは、実際には「\ 0」で区切られた一連の行を指します。 そして、線が終わって、最後に2つのゼロが必要であることを見つけるために。
それは非常に簡単に忘れられます。 コードスニペット:
int JoiningProc(HWND hwnd、UINT uMsg、
WPARAM wParam、LPARAM lParam)
{
...
OPENFILENAME lofn;
memset(&lofn、0、sizeof(lofn));
...
lofn.lpstrFilter = uni( "すべてのファイル(*。*)\ 0 *。*");
...
}
PVS-Studioメッセージと場所コード:
V540メンバー「lpstrFilter」は、2つの0文字で終了する文字列を指す必要があります。 ベースwindows.c 5309 |
ダイアログが正常に機能するかどうかは、「All Files(*。*)\ 0 *。*」という行の後のメモリ内の内容によって異なります。 正しいものに従って、「すべてのファイル(*。*)\ 0 *。* \ 0」と書く必要があります。 ゼロを明示的に指定しましたが、コンパイラはゼロをもう1つ追加します。
他のダイアログと同様の不幸。
int callback_presets_dialog(HWND hwnd、UINT msg、
WPARAM wParam、LPARAM lParam)
{
...
//保存
OPENFILENAME lofn;
memset(&lofn、0、sizeof(lofn));
...
lofn.lpstrFilter = uni( "イコライザープリセット(* .feq)\ 0 * .feq");
...
...
//ロード
...
lofn.lpstrFilter = uni( "イコライザープリセット(* .feq)\ 0 * .feq");
...
}
int localsf_show_save_playlist(void)
{
OPENFILENAME lofn;
memset(&lofn、0、sizeof(lofn));
...
lofn.lpstrFilter = uni( "テキストファイル(* .txt)\ 0 * .txt \ 0M3Uファイル\ 0 * .m3u");
...
}
PVS-Studioの診断とコード内の場所:
V540メンバー「lpstrFilter」は、2つの0文字で終了する文字列を指す必要があります。 ベースwindows.c 986 |
V540メンバー「lpstrFilter」は、2つの0文字で終了する文字列を指す必要があります。 ベースwindows.c 1039 |
V540メンバー「lpstrFilter」は、2つの0文字で終了する文字列を指す必要があります。 shared functions.c 360 |
今、疑わしい機能。 非常に疑わしい。 しかし、ここには本当に間違いがあるか、単純に不十分にしか書かれていません。
unsigned long ml_cache_getcurrent_item(void)
{
if(!mode_ml)
return skin.shared-> audio.output.playlist.getcurrentindex();
他に
return skin.shared-> audio.output.playlist.getcurrentindex();
}
PVS-Studioの診断とコード内の場所:
V523「then」ステートメントは「else」ステートメントと同等です。 メディアライブラリwindow.c 430 |
Fennecに付属のさまざまな拡張モジュールを分析しませんでした。 しかし、悲しい場所は他にもあります。 いくつかの例を挙げます。 Codec ACCプロジェクトからのコードスニペット。
void MP4RtpHintTrack :: GetPayload(...)
{
...
if(pSlash!= NULL){
pSlash ++;
if(pSlash!= '\ 0'){
長さ= strlen(pRtpMap)-(pSlash-pRtpMap);
* ppEncodingParams =(char *)MP4Calloc(長さ+ 1);
strncpy(* ppEncodingParams、pSlash、length);
}
}
PVS-Studio診断メッセージから次のとおりです。
V528「char」タイプへのポインターが「\ 0」値と比較されるのは奇妙です。 おそらく意味:* pSlash!= '\ 0'。 rtphint.cpp 346 |
ここでポインタを逆参照するのを忘れました。 ポインターと0の無意味な比較を行っていることがわかります。「if(* pSlash!= '\ 0')。」
Decoder Mpeg Audioプロジェクトのコードスニペット:
void * tag_write_setframe(char * tmem、
const char * tid、const string dstr)
{
...
if(lset)
{
fhead [11] = '\ 0';
fhead [12] = '\ 0';
fhead [13] = '\ 0';
fhead [13] = '\ 0';
}
...
}
PVS-Studioの診断メッセージとコード内の場所:
V525同様のブロックのコレクションを含むコード。 行716、717、718、719の項目「11」、「12」、「13」、「13」を確認します。id3editor.c 716 |
ここにコピーペーストの悪があります:)。
全体として、フェネックメディアプロジェクトでは、PVS-Studioの汎用分析が非常に優れていることが判明しました。 分析は、低い偽陽性率で実行されました。 合計で、PVS-Studioは31個のコードスニペットを示しました。 さらに、19箇所で、コードを実際に修正する必要があります。
それでは、qutIMプロジェクトに移りましょう。
PVS-Studioはこれらのプロジェクトで敗北しました。 プロジェクトが非常に大きい(約20万株)にもかかわらず、PVS-Studioアナライザーはプロジェクトのエラーを検出できませんでした。 確かにそうですが。 それらはどこにでもあり、常に:)です。 また、場合によってはqutIMが落ちてしまうことがあるため、qutIM開発者はこれに異議を唱えません。
「ミスのチーム」に1ポイントをカウントする必要があります。
これはどういう意味ですか? これは次を意味します。
1)qutIMプロジェクトは非常に高品質のプロジェクトです。 エラーも含まれていますが、それらは非常にまれであり、静的分析には高すぎます(少なくともPVS-Studioの場合)。
2)PVS-Studioには、さらに高度な診断を開発するための長い道のりがあります。 これで、何を目指して努力するかがより明確になりました。 目標は、qutIMで少なくともいくつかの実際のバグを見つけることです。
PVS-StudioはqutIMプロジェクト用に何かを作成しましたか? 発行。 しかし、誤検知はほとんどありません。 少なくともいくつかの関心のうち、次のものだけを区別することができます。
A)CreateThread関数が使用されます。
B)奇妙な機能が見つかりました。 それからqutIMの作者の一人は、これらは忘れられたスタブだと言いました。 奇妙なことは、1つはsave()、もう1つはcancel()という名前ですが、内容は同じです:
void XSettingsWindow :: save()
{
QWidget * c = p-> StackedWidget-> currentWidget();
while(p-> modifiedWidgets.count()){
SettingsWidget * widget = p-> modifiedWidgets.takeFirst();
ウィジェット->保存();
if(ウィジェット!= c)
widget-> deleteLater();
}
p-> buttonBox-> close();
}
void XSettingsWindow :: cancel()
{
QWidget * c = p-> StackedWidget-> currentWidget();
while(p-> modifiedWidgets.count()){
SettingsWidget * widget = p-> modifiedWidgets.takeFirst();
ウィジェット->保存();
if(ウィジェット!= c)
widget-> deleteLater();
}
p-> buttonBox-> close();
}
PVS-Studio診断:
V524「キャンセル」機能が「保存」機能と完全に同等であることは奇妙です(xsettingswindow.cpp、256行目)。 xsettingswindow.cpp 268 |
おもしろくて、すぐにPVS-Studio 4.00 Betaを試してみてください。 もちろん、PVS-Studioはまだ一般的なエラーをほとんど見つけませんが、これはほんの始まりにすぎません。 さらに、コーディング段階で1つのエラーを修正するだけで、顧客、テスター、プログラマーの負担を軽減できます。
PSもう一度、「
プログラマーからいものを
集める 」というトピックの議論に参加してくれた皆に感謝し、例を共有しました。 最終的には多くがPVS-Studioに組み込まれます。 よろしくお願いします!