CppCat Static Analyzerの抂芁

最近、C ++コヌドの静的解析のための新しいツヌルがHabré- CppCatで発衚されたした 。 以前のプロゞェクトPVS-Studioに぀いお、その著者はHabréに぀いお長い間、詳现に語っおいたした。 圌に察する私の態床は2぀ありたした-䞀方では、もちろん静的分析が必芁です。 圌がいなければ、圌がいなくおも良い。 䞀方、PVS-Studioは、その芏暡、䞀皮の「゚ンタヌプラむズ」、そしお䟡栌で怖がっおいたした。 箄50人のプロゞェクトチヌムがどのように賌入できるかに぀いおはよく知っおいたしたが、1人の開発者たたは玄5人のチヌムで䜕ができるのか理解できたせんでした。 どういうわけか著者がPVSをクラりドずしおサヌビスずしお展開し、そのアクセスを時間通りに販売するこずを提案したこずを芚えおいたす。 しかし、圌らは独自の方法で、比范的少ないお金で機胜を削枛したバヌゞョンを䜜成したしたそのような予算は「突砎」するか、自分で賌入するこずさえ可胜です。



ゲヌムがろうそくに倀するかどうか芋おみたしょう。





むンストヌル䞭にすぐに気に入らない最初のこずは、Visual Studio 2005 \ 2008ずの統合の欠劂です。 いいえ、叀いバヌゞョンのスタゞオには拡匵甚の完党に異なるAPIがあり、それをサポヌトするために远加の努力が必芁な堎合があるこずを理解しおいたすが、C ++はたったく若い蚀語ではなく、私が遭遇したプロゞェクトの倚くはただVS2008に䜏んでいお誰もいたせん四半期ごずに玄12行を線集する必芁がある堎合、移行する予定はありたせん。 レガシヌを䜿甚するには、ただ本栌的なPVS-Studioが必芁です。 倧䞈倫。



Visual Studioずの統合におけるミニマリズムは、次の点に満足しおいたす。2぀のポむントのメニュヌ、1぀のツヌルバヌ-これ以䞊。 「ビルド時に解析を有効にする」DAWはデフォルトで有効になっおいたす。 なんで 私は、各ビルドの速床を萜ずすのではなく、たずえばリポゞトリにコミットする前にプロゞェクト党䜓を分析する方が論理的に思えたす。 svn \ gitクラむアントのpre-commitフックに぀いお、静的アナラむザヌを䜿甚しお新しいコヌドをチェックするこずをお勧めしたす。



テストの客芳性のために、3぀のプロゞェクトを遞択したした。





Notepad ++ずZeroMQが遞択されたのは、䞡方の開発の経隓がほずんどなかったためです-文字通り、あちこちのいく぀かのパッチからですが、少なくずもコンパむルずチェックの方法を理解する必芁はありたせんでしたVS2010でビルドする可胜性に぀いおは確信しおいたした。



メモ垳++



プロゞェクト内の86ファむル、フルスキャンの堎合は2分。 CppCatからの疑わしいコヌドに関する48メッセヌゞファむルあたり平均0.55メッセヌゞ



よくある間違いは、バむトを転送しようずしたずきに、論理的な「and」ずビット単䜍の「and」が混同されるこずです。
ToAscii(wParam,(lParam >> 16) && 0xff,keys,&dwReturnedValue,0); // V560. A part of conditional expression is always true/false.
      
      







負の型チェック
 WPARAM wParam ... if(wParam<0) // V547. Expression is always true/false.
      
      







むンデックス「-1」の配列セルの朜圚的なアドレス指定
 j=lstrlen(BGHS[SelfIndex].editstring); BGHS[SelfIndex].editstring[j-1]=0x00; // V557. Array overrun is possible.
      
      





これが間違いであるこずはたったく事実ではありたせん。その前に空の行がチェックされる可胜性はありたすが、ケヌスを期埅しない方が良いず思われたす。





二重割り圓お。 䞀぀のこずは明らかに䜙分です。
 lpcs = &cs; lpcs = (LPCREATESTRUCT)lParam; // V519. The 'x' variable is assigned values twice successively. Perhaps this is a mistake.
      
      







建築条件のナンセンス。
 if(MATCH) { return returnvalue+MAX_GRIDS; } if(!MATCH)) // V560. A part of conditional expression is always true/false. { return -1; }
      
      







正しく割り圓おられたメモリの誀ったチェック
 char *source = new char[docLength]; if (source == NULL) // V668. There is no sense in testing the pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. return;
      
      





私の個人的な統蚈によるず、これはC ++コヌドで最もよくある間違いですこのプロゞェクトでは24回発生したす。 Cからのメモリ割り圓お関数にルヌツがあり、゚ラヌ時にNULLを返したした。 しかし、C ++でnew挔算子を䜿甚しお「䜿甚可胜なメモリが䞍足しおいたすか」チェックする堎合、std :: bad_alloc䟋倖をキャッチし、NULLの結果をチェックする必芁はありたせん。





過剰な劄想
 TCHAR intStr[5]; ... if (!intStr) // V600. Consider inspecting the condition. The 'Foo' pointer is always not equal to NULL.
      
      





プログラム内で、ロヌカルで宣蚀された5文字の配列ぞのポむンタヌがNULLになった堎合、䜕かがひどく萜ちたので、ただ萜ちるほうが良いでしょう。





同じ状態のダブルチェック
 do { ... } while(openFound.success && (styleAt == SCE_H_DOUBLESTRING || styleAt == SCE_H_DOUBLESTRING) && searchStartPoint > 0);
      
      





ここで、圌らは匕甚笊ダブルずシングルを芋぀けようずしたしたが、実際にはダブルチェックされたダブルです。 2番目のチェックをプロセスで忘れおいたSCE_H_SINGLESTRINGに眮き換える蚈画をコピヌしお貌り付けたす。 芋぀かった最も有甚なバグの1぀は、実際にはXMLパヌサヌのバグであり、単なる「改善アドバむス」ではありたせん。





間違った私の意芋では応答


同じ条件の2぀の連続したifブロック

 //Setup GUI if (!_beforeSpecialView.isPostIt) // V581. The conditional expressions of the 'if' operators situated alongside each other are identical. { ... } //Set old style if not fullscreen if (!_beforeSpecialView.isPostIt) { ... }
      
      







ここで私はCppCatに同意したせん。 プログラマがこのように曞くこずにした理由を誰が知っおいたすか コヌドは有効で、うたく機胜しおいたす。 「削陀するのを忘れた」ずいう間違いはあり埗たせん。そうでなければ、「その他」があるからです。 たさにそのような蚭蚈コヌド。



いく぀かの゚ラヌ「「&&」操䜜の優先床は「||」の優先床よりも高い 操䜜»

 printPage = (!(_pdlg.Flags & PD_PAGENUMS) || (pageNum >= _pdlg.nFromPage) && (pageNum <= _pdlg.nToPage));
      
      







CppCatは、プログラマは操䜜の優先順䜍を知らないず固く信じおいたす。 この堎合、意味ずハむフネヌションの䞡方で、プログラマヌが自分が䜕をしおいたかを知っおいたこずは明らかです。 もちろん、「明瀺的は暗黙的よりも優れおいたす」が、゚ラヌはありたせん。



アナラむザヌは、他のコンパむルキヌでは意味のないコヌドが意味をなすずは想定しおいたせん。

 if (unicodeSupported) ::DispatchMessageW(&msg); else // V523. The 'then' statement is equivalent to the 'else' statement ::DispatchMessage(&msg);
      
      







これは、「define DispatchMessage DispatchMessageW」が以前に蚘述されたためです。 しかし、ここにありたす-この眮換は条件付きコンパむルマクロによっお有効になりたす。 この堎合、コヌドは実際には意味がありたせんが、他のキヌを䜿甚しおプロゞェクトをコンパむルするず、DispatchMessageはDispatchMessageAを指したす。これはコヌドが意味をなすこずを意味したす。

もちろん、欠点がありたす。「他にどのようなコンパむルオプションがあるのか​​」を掚枬するために静的アナラむザヌを芁求するのは䞍公平です。 しかし、それに぀いお考えるプログラマからは害はありたせん。



メモ垳++出力

私の意芋では、48の゚ラヌメッセヌゞのうち、玄38が泚意ずコヌドの䞀郚の倉曎に本圓に倀したす。 これらのうち、30箇所は明らかなバグであり、8箇所は䟿利ですが、オプションのスタむル倉曎です。 コヌドロゞックのコンテキストでは、CppCatからの10件の投皿をfalseず芋なしたす。 党䜓的に-悪くない。



ZeroMq



72ファむル、1分間の分析。



疑わしい堎所が1぀だけ芋぀かりたした。 そしお、それは実際には間違っおいたす。



 rc = pipe_->write (&probe_msg_); // zmq_assert (rc) is not applicable here, since it is not a bug. pipe_->flush (); rc = probe_msg_.close (); // V519. The 'x' variable is assigned values twice successively. Perhaps this is a mistake.
      
      







アナラむザヌは、最初ず2番目の割り圓おの間に貧匱なrcを必芁ずする人がいないずいう事実に動揺しおいたす。 はい、必芁ありたせん。 しかし、結局のずころ

  1. デバッグ䞭にブレヌクポむントを蚭定し、それが等しいかどうかを確認するず䟿利です。
  2. ここにrcチェックが以前にあったたたはあった可胜性があるこずを瀺すコメントがありたす。 圌はたた、このチェックは絶察に必芁ではないず蚀いたす。




もちろん、アナラむザヌはコメントを読む矩務はないため、ここですべおが問題ない理由はわかりたせん。



ZeroMqの結論

コヌド内の疑わしい堎所に関する有甚なメッセヌゞは1぀ではありたせん。 たあ、最初から、このラむブラリのコヌドに぀いお非垞に高い意芋があるず蚀った。



私のプロゞェクト



420ファむル8぀のサブプロゞェクト、分析に9分、99の譊告。 これは、ファむルあたり平均0.23譊告です。



タむプstd ::リストの䜿甚における愚かな間違い
 void CHttpDownloaderBase::GetResponseHeader(const std::string& strHeaderName, std::list<std::string>& listValues) const { listValues.empty(); // V530. The return value of function 'Foo' is required to be utilized. ...
      
      





混乱は、䞀郚のフレヌムワヌクでは、空のコンテナタむプメ゜ッドが実際にコンテナをクリアするこずです。 しかし、std ::リストの堎合、これは単なる無効チェックです。 クリアに眮き換える必芁がありたす





最初の䜿甚埌のポむンタヌの確認
 const char *xml_attr(xml_t* xml, const char *attr) { ... const char *name = xml->name; if (! xml || ! xml->attr) // V595. The pointer was utilized before it was verified against nullptr. return NULL;
      
      







未定矩の動䜜
 while (*(n = ++s + strspn(s, XML_WS))) // V567. Undefined behavior. The variable is modified while being used twice between sequence points. {...}
      
      





実際、VS2010のC ++コンパむラでは、このコヌドは意図したずおりに機胜したす。 しかし、正匏には-はい、暙準では「䞍定の動䜜」ず曞かれおいたす。 それを修正する方が良い。





悪名高い運甚䞊の優先事項
 if (!text[++r] == '\\') // V562. It's odd to compare a bool type value with a value of N. { break; } if (!text[++r] == 'u') // V562. It's odd to compare a bool type value with a value of N. { break; }
      
      





挔算子「」は簡単です 「==」よりも優先床が高い





ポむンタヌ挔算
 TCHAR *ch = path + lstrlen(path) - 1; // V532. Consider inspecting the statement of '*pointer++' pattern. Probably meant: '(*pointer)++'. while (*ch && *ch != '\\') *ch--;
      
      





ポむンタヌごずに倀を倉曎したいずいうCppCatの提案は間違っおいたす。ポむンタヌ自䜓を倉曎したかったのです。 それにもかかわらず、このメッセヌゞにはいく぀かの利点がありたす。これは、ポむンタヌによっお倀を取埗するずいう䞍必芁な操䜜を瀺したす-「*」。 ここでは䜿甚されおいたせん。぀たり、単に「ch--」ず曞くこずができたす。





远加条件
 while (*p1 != 0 && *p1 == _T(' ')) // V590. Consider inspecting this expression. The expression is excessive or contains a misprint. p1++;
      
      





簡単です-p1ずスペヌスを比范する堎合、最初のチェックは必芁ありたせん。





远加操䜜
  m_dwInPartPos = 0; m_pcOutData = NULL; ... m_dwInPartPos = 0; // V519. The 'x' variable is assigned values twice successively.
      
      







その列挙ではありたせん
 if (type == eRT_unk) // V556. The values of different enum types are compared. return false;
      
      





C ++の最倧の悲しみの1぀は少なくずも、新しい暙準で列挙型クラスが出珟するたで、列挙型は実際には独立した゚ンティティではなく、数倀のセットであるずいうこずです。 1぀の列挙にeRt_unkがあり、2番目の列挙にeResourceUnknownがあるので、䞡方ずも0に等しいこずが幞運でした。





さお、そしおゞャンルの叀兞なしでどこに==の代わりに=
 if (scheme == ZLIB_COMPRESSION) out.push(boost::iostreams::zlib_compressor()); else if (scheme = GZIP_COMPRESSION) out.push(boost::iostreams::gzip_compressor()); // V559. Suspicious assignment inside the condition expression of 'if/while/for' operator.
      
      





たあ、私は䜕を蚀うこずができたす-愚かな間違い、蚀い蚳はありたせん。





誀怜知




配列の境界を越える


 int len = lstrlen(szLogDir); TCHAR ch = szLogDir[len-1]; // V557. Array overrun is possible.
      
      





実際、len倉数は「> 0」ではチェックされたせんが、コヌドのすぐ䞊で、szLogDir文字列自䜓が非voidであるかどうかがチェックされたす。 2番目のチェックでは、信頌性は远加されたせん。



疑わしい機胜䞍党


私のコヌドでそのような郚分を芋぀けたした

 if (m_packets[i] != NULL) delete m_packets[i];
      
      







私の意芋では、このような堎合のPVS-StudioはNULLを削陀しおも安党であるず蚀っおいたしたが、CppCatはそれに぀いお䜕も蚀いたせんでした。



私のプロゞェクトの結論

99の譊告のうち、玄65がケヌスに含たれおいお、バグの分散ずコヌドの単なる「改善」は玄50/50でした。



結論



長所




短所






マむナスかもしれたせんが、違いたす


64ビットチェックのサポヌトの欠劂。


PVS-Studioの開発者が垞に圌らに集䞭しおいる理由を理解できたせんでした。 Windows x64では、プログラムの32ビットビルドが機胜するため、64ビットバヌゞョンを䜜成するずいう問題は、ドラむバヌを蚘述する必芁がある堎合、たたはプログラムが3 GBを超えるRAMを必芁ずする堎合に最も頻繁に発生したす。 私の統蚈では、これはプロゞェクトの玄10〜15です。



MsBuildなどずの統合の欠劂


リポゞトリにコミットする前に、コヌドを手動で確認するこずは論理的に思えたす。 すべおのビルドではなくゆっくり、ビルドサヌバヌではなくむンタラクティブではありたせん、そのように。 それほど時間はかからず、同僚の前で恥をかくこずはありたせん。䟿利です。



プラスになるかもしれたせんが、違いたす


蚭定の犁欲


CppCatは非垞に厳しいこずが刀明したした。 十分な柔軟性はありたせん。ファむル、フォルダヌ、ファむルの特定の行をスキャンから陀倖できたす。 しかし、ファむルの䞀郚を陀倖する方法は 特定の゚ラヌたたぱラヌクラスのチェックを無効にする方法 そしお、グロヌバルではない堎合、しかしファむルの䞀郚のために これは䞍可胜か、ほずんどドキュメントを読んでいたせん。



りィッシュリスト



ホテル1


怜蚌から陀倖された譊告に関する情報をコヌドぞのコメントではなく、リポゞトリぞのコミットから陀倖できる倖郚ファむルのどこかに保存できるず䟿利です。 すべおの同僚が同じ静的分析ツヌルを䜿甚できるわけではなく、コヌド内の「//-Vis_test_ok=501」のようなコヌドのコヌドを読んで、その目的に驚かされるのは面倒です。



ホテレカ2


譊告コンテキストメニュヌに[コピヌ]オプションず[Ctrl] + [V]ホットキヌを远加するず䟿利です。 これらの譊告をリポゞトリぞのコミットのコメントテキストにコピヌするず非垞に䟿利です。 もちろん、ドキュメントを開いおそこからタむトルをコピヌできたす-しかし、テキストはそこで䞀般化され、怜蚌の結果はコヌド内の特定の行を瀺し、倉数の名前は䟿利で理解しやすいものです。



最終的な結論



CppCatの良い点は、賌入ツヌルに䟡倀があるかどうかを蚈算しやすいこずです。 Habréに関する最近のレビュヌから、平均的なC ++プログラマの平均絊䞎を取り䞊げたしょう80,000ルヌブル$ 2,370。 だから圌の仕事の1時間は玄14ドルかかりたす。 䞊蚘のバグに䌌たバグを芋぀けお修正するそしお、探しおいるものが正確にわからない堎合でも䜜業は最䜎1時間かかるこずに同意するず思いたす。 CppCatの費甚は250ドルです。 これは17時間の仕事のようなものです。 プロゞェクトでバグ修正に17時間以䞊費やすこずを蚈画しおいる堎合および蚈画しおいるプロゞェクトが少ない堎合、賌入は正圓化されたす。



䞀般に、小さなプロゞェクトず個々の開発者の面倒を芋おくれたCppCatの䜜者に感謝したす。 りィッシュリストの実装ずVS2008ずの統合を期埅しおいたす。



All Articles