静的分析:メディアプレーヤーのエラーとバグのないICQ

プログラムのエラーのツアーと、静的コード分析の有用性のデモを続けます。



これは、まだダウンロードできない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に組み込まれます。 よろしくお願いします!



All Articles