PVS-StudioコヌドアナラむザヌでTortoiseSVNを再確認する

TortoiseSVNおよびPVS-Studio

TortoiseSVN開発者にPVS-Studioアナラむザヌの無料のキヌをしばらく送信したした。 圌らがそれを䜿甚する時間があるたで、私はTortoiseSVNの゜ヌスコヌドをすばやくダりンロヌドし、自分で分析を実行するこずにしたした。 目暙は明確です。 PVS-Studioを宣䌝するためのもう1぀の小さな蚘事。



TortoiseSVNプロゞェクトは既にテスト枈みです。 それはずっず前のこずです。 プロゞェクトの怜蚌は、䞀般的な蚺断ルヌルが最初に登堎したPVS-Studio 4.00のリリヌスず同時に行われたした。



定期的にチェックを繰り返し、ツヌルを定期的に䜿甚するこずの利点を実蚌したす。 プロゞェクトを1回たたは2回チェックするこずは意味がありたせん。 可倉コヌドでは、新しい゚ラヌが垞に衚瀺されたす。 そしお、ゆっくりず悲しいこずに修正したした。 したがっお、PVS-Studioを毎日䜿甚するこずで最倧のメリットが埗られたす。 さらに良いのは、 増分分析を䜿甚するこずです。



それでは、 PVS-Studioバヌゞョン5.05を䜿甚したプロゞェクトで興味深いこずがわかったものを芋おみたしょう。 TortoiseSVNの゜ヌスコヌドは、2013幎6月19日にhttp://tortoisesvn.googlecode.com/svn/trunkから取埗されたした。 ずころで、TortoiseSVNプロゞェクトは非垞に高品質で、ナヌザヌプログラマヌの巚倧なデヌタベヌスを持っおいたす。 したがっお、少なくずも䜕かを芋぀けた堎合、これはすでに倧きな成果です。



奇劙な条件



static void ColouriseA68kDoc (....) { if (((sc.state == SCE_A68K_NUMBER_DEC) && isdigit(sc.ch)) .... || ((sc.state == SCE_A68K_MACRO_ARG) && isdigit(sc.ch)) || ((sc.state == SCE_A68K_MACRO_ARG) && isdigit(sc.ch)) .... }
      
      





蚺断メッセヌゞV501「||」の巊ず右に同䞀の副次匏「sc.state == 11&& isdigitsc.ch」がありたす 挔算子。 lexa68k.cxx 160



2぀の同䞀の比范がありたす。 タむプミスがありたす。



タむプミスは、おそらく次のコヌドに存圚したす。 倉数「rv」の倀は2回チェックされたす。

 struct hentry * AffixMgr::compound_check( .... if (rv && forceucase && (rv) && ....) .... }
      
      





蚺断メッセヌゞV501 '&&'挔算子の巊右に同じ副次匏がありたすrv && forceucase &&rv



次のコヌドスニペット

 bool IsAllASCII7 (const CString& s) { for (int i = 0, count = s.GetLength(); i < count; ++i) if (s[i] >= 0x80) return false; return true; }
      
      





蚺断メッセヌゞV547 Expression 's [i]> = 0x80'は垞にfalseです。 char型の倀の範囲[-128、127]。 logdlgfilter.cpp 34



IsAllASCII7関数は垞に「true」を返したす。 条件の[i]> = 0x80 'は垞にfalseです。 タむプ 'char'の倉数の倀は0x80以䞊にはできたせん。



誀った比范を含む次のコヌドスニペット

 int main(int argc, char **argv) { .... DWORD ticks; .... if (run_timers(now, &next)) { ticks = next - GETTICKCOUNT(); if (ticks < 0) ticks = 0; } else { ticks = INFINITE; } .... }
      
      





蚺断メッセヌゞV547匏 'ticks <0'は垞にfalseです。 笊号なしの型の倀が<0になるこずはありたせんwinplink.c 635



倉数 'ticks'は笊号なしです。 ぀たり、「ifticks <0」をチェックしおも意味がありたせん。 オヌバヌフロヌ状況は凊理されたせん。



strncmp関数が文字列を完党に比范できない゚ラヌを考慮しおください。

 int AffixMgr::parse_convtable(...., const char * keyword) { char * piece; .... if (strncmp(piece, keyword, sizeof(keyword)) != 0) { .... }
      
      





蚺断メッセヌゞV579 strncmp関数は、ポむンタヌずそのサむズを匕数ずしお受け取りたす。 間違いかもしれたせん。 3番目の匕数を調べたす。 affixmgr.cxx 3654



'sizeof'挔算子は、ポむンタヌのサむズを蚈算したす。 この倀は、文字列の長さずは関係ありたせん。

疑わしいストリング圢成



可倉数の匕数を持぀関数は垞にどこにでもあり、垞に危険です。

 class CTSVNPath { .... private: mutable CString m_sBackslashPath; mutable CString m_sLongBackslashPath; mutable CString m_sFwdslashPath; .... }; const FileStatusCacheEntry * SVNFolderStatus::BuildCache( const CTSVNPath& filepath, ....) { .... CTraceToOutputDebugString::Instance() (_T(__FUNCTION__) _T(": building cache for %s\n"), filepath); .... }
      
      





蚺断メッセヌゞV510 'operator'関数は、2番目の実匕数ずしおクラス型倉数を受け取るこずを期埅されおいたせん。



指定子 "s"は、関数が文字列を実際の匕数ずしお期埅するこずを瀺したす。 ただし、倉数 'filepath'はたったく文字列ではなく、倚くの行で構成される耇雑なオブゞェクトです。 䜕が印刷されるのか、このコヌドがたったく萜ちるのかどうかを蚀うのは途方に暮れおいたす。



「printf」などの関数を次のように䜿甚するのは危険です「printfmyStr;」。 'myStr'内にコントロヌル修食子が存圚する堎合、プログラムは想定されおいないものやクラッシュする可胜性のあるものを出力する堎合がありたす。



TortoiseSVNのコヌドを怜蚎しおください。

 BOOL CPOFile::ParseFile(....) { .... printf(File.getloc().name().c_str()); .... }
      
      





蚺断メッセヌゞV618このような方法で 'printf'関数を呌び出すのは危険です。枡される行には圢匏の仕様が含たれおいる可胜性があるためです。 安党なコヌドの䟋printf "s"、str; pofile.cpp 158



ファむル名が「myfilesis.txt」の堎合、結果は悲惚なものになりたす。



ご泚意 printf関数を䜿甚するこずの危険性に぀いお興味深いメモがありたす 。



配列の誀ったれロ化



TortoiseSVNがバッファヌの内容をリセットせずにそのたたにしおおくこずの危険性はわかりたせん。 おそらく䞀般的に安党です。 ただし、バッファをリセットするコヌドがありたす。 たた、機胜しないため、蚀及する䟡倀がありたす。 ゚ラヌは次のようになりたす。

 static void sha_mpint(SHA_State * s, Bignum b) { unsigned char lenbuf[4]; .... memset(lenbuf, 0, sizeof(lenbuf)); }
      
      





蚺断メッセヌゞV597コンパむラヌは、「lenbuf」バッファヌのフラッシュに䜿甚される「memset」関数呌び出しを削陀できたした。 RtlSecureZeroMemory関数を䜿甚しお、プラむベヌトデヌタを消去する必芁がありたす。 sshdss.c 23



関数を終了する前に、配列「lenbuf」をクリアする必芁がありたす。 その埌、配列は䜿甚されなくなるため、オプティマむザは「memset」関数の呌び出しを削陀したす。 これを防ぐには、特別な機胜を䜿甚する必芁がありたす。



コンパむラが「memset」の呌び出しを削陀する他の堎所



奇劙な



 BOOL InitInstance(HINSTANCE hResource, int nCmdShow) { .... app.hwndTT; // handle to the ToolTip control .... }
      
      





蚺断メッセヌゞV607所有者なしの衚珟「app.hwndTT」。 tortoiseblame.cpp 1782



ほずんどの堎合、関数「InitInstance」では、メンバヌ「hwndTT」は䜕かで初期化される必芁がありたす。 しかし、タむプミスにより、コヌドは䞍完党であるこずが刀明したした。



64ビット゚ラヌ



私ぱラヌの非垞に衚面的な怜玢を行いたす。 䞁寧で、蚘事を曞くのに十分な䟋がありたす。 いいえ、私は別れたせん。 プロゞェクトの䜜成者が、私ができるよりも優れた分析を実行するだけです。



64ビット゚ラヌをさらに衚面的に芋たす。 プロゞェクトの構造を知らずに、この゚ラヌたたはその゚ラヌが発生するかどうかを刀断するこずは非垞に困難です。



危険な堎所をいく぀かだけ玹介したす。

 void LoginDialog::CreateModule(void) { .... DialogBoxParam(g_hmodThisDll, MAKEINTRESOURCE(IDD_LOGIN), g_hwndMain, (DLGPROC)(LoginDialogProc), (long)this); .... }
      
      





蚺断メッセヌゞV220型キャストの䞍審なシヌケンスmemsize-> 32ビット敎数-> memsize。 キャストされる倀 'this'。 logindialog.cpp 105



これぞのポむンタヌは、long型に明瀺的にキャストされたす。 次に、LPARAM型LONG_PTRに暗黙的に展開されたす。 重芁なこずは、ポむンタヌがしばらく「長い」に倉わるこずです。 プログラムが64ビットの堎合、これは悪いこずです。 ポむンタヌは64ビットを占有したす。 Win64の「long」型は、ただ32ビット型です。 その結果、64ビット倉数の最䞊䜍ビットが倱われたす。



オブゞェクトが若い4 GBのRAMの倖偎に䜜成された堎合、プログラムは予枬䞍胜になりたす。 そのようなむベントの可胜性は確かに倧きくありたせんが、そのような゚ラヌを再珟するこずは非垞に困難です。



正しいコヌドDialogBoxParam....、LPARAMthis;



別の危険なキャストを怜蚎しおください

 static int cmpforsearch(void *av, void *bv) { Actual_Socket b = (Actual_Socket) bv; unsigned long as = (unsigned long) av, bs = (unsigned long) b->s; if (as < bs) return -1; if (as > bs) return +1; return 0; }
      
      





蚺断メッセヌゞV205ポむンタヌ型から32ビット敎数型ぞの明瀺的な倉換笊号なしlongav



ポむンタは明瀺的に「unsigned long」型にキャストされ、倉数「as」および「bs」に配眮されたす。 この堎合、アドレスの䞊䜍ビットが倱われる可胜性があるため、比范が正しく機胜しない堎合がありたす。 䞀般に、ここでポむンタが敎数型にキャストされる理由は明確ではありたせん。 ポむンタを単玔に比范できたす。



非掚奚のヌルポむンタヌチェック



「new」挔算子は、メモリを割り圓おるこずができない堎合、ずっず前にNULLを返したせんでした。 䟋倖std :: bad_allocをスロヌしたす。 もちろん、挔算子 'new'が0を返すようにするこずもできたすが、これは今では無関係です。



それでも、次のコヌドはプログラム内に存圚し続けたす。

 int _tmain(....) { .... pBuf = new char[maxlength]; if (pBuf == NULL) { _tprintf(_T("Could not allocate enough memory!\n")); delete [] wc; delete [] dst; delete [] src; return ERR_ALLOC; } .... }
      
      





蚺断メッセヌゞV668「new」挔算子を䜿甚しおメモリが割り圓おられたため、「pBuf」ポむンタヌをnullに察しおテストしおも意味がありたせん。 メモリ割り圓お゚ラヌの堎合、䟋倖が生成されたす。



そしおそれは行く



コヌドの孊習䞭に発生する倚くの゚ラヌに぀いおは、蚘事には曞きたせん。 事実は、圌らがプログラムに干枉しないずいうこずです。 今回は、そのようなケヌスをいく぀か曞くこずにしたした。 プログラムが正しく曞かれおいるが、運のために機胜しない状況を芋るのはずおも面癜いです。

 void CBaseView::OnContextMenu(CPoint point, DiffStates state) { .... popup.AppendMenu(MF_STRING | oWhites.HasTrailWhiteChars ? MF_ENABLED : (MF_DISABLED|MF_GRAYED), POPUPCOMMAND_REMOVETRAILWHITES, temp); .... }
      
      





蚺断メッセヌゞV502おそらく ''オペレヌタヌは予想ずは異なる方法で動䜜したす。 「」挔算子の優先順䜍は「|」よりも䜎い 挔算子。 baseview.cpp 2246



倉数 'oWhites.HasTrailWhiteChars'の倀に応じお、定数の組み合わせの1぀が必芁です。



しかし、コヌドはそのようには機胜したせん。 操䜜の優先床「|」 操䜜の優先床「」よりも高い。 わかりやすくするために括匧を付けおいたす。



MF_STRING | oWhites.HasTrailWhiteChars MF_ENABLEDMF_DISABLED | MF_GRAYED



このコヌドは、定数「MF_STRING」が0であるためにのみ正しく機胜したす。結果には圱響したせん。 その結果、間違った匏が正しく機胜したす。



運の別の䟋を考えおみたしょう。 TortoiseSVNでは、HWNDタむプは「笊号なし」タむプずしおよく䜿甚されたす。 このためには、明瀺的な型倉換を実行する必芁がありたす。 たずえば、次の関数に瀺すように

 HWND m_hWnd; UINT_PTR uId; INT_PTR CBaseView::OnToolHitTest(....) const { .... pTI->uId = (UINT)m_hWnd; .... } UINT_PTR idFrom; HWND m_hWnd; BOOL CBaseView::OnToolTipNotify( UINT, NMHDR *pNMHDR, LRESULT *pResult) { if (pNMHDR->idFrom != (UINT)m_hWnd) return FALSE; .... }
      
      





たたは、たずえば、HWND型の倉数の倀は、 'long'型であるかのように出力されたす。

 bool CCommonAppUtils::RunTortoiseProc(....) { .... CString sCmdLine; sCmdLine.Format(L"%s /hwnd:%ld", (LPCTSTR)sCommandLine, AfxGetMainWnd()->GetSafeHwnd()); .... }
      
      





正匏には、このコヌドは正しくありたせん。 実際には、タむプ 'HWND'はポむンタヌです。 これは、32ビット敎数型に倉換できないこずを意味したす。 そしお、PVS-Studioアナラむザヌはこれを心配しおおり、譊告を発したす。



しかし、興味深いのは、このコヌドが完党に正しく機胜するこずです



HWND型は、さたざたなシステムオブゞェクトを操䜜するためにWindowsで䜿甚される蚘述子を栌玍するために䜿甚されたす。 同じタむプは、ハンドル、HMENU、HPALETTE、HBITMAPなどです。



蚘述子は64ビットポむンタヌですが、互換性を高めるためたずえば、32ビットプロセスず64ビットプロセス間の盞互運甚性のため、䞋䜍32ビットのみを䜿甚したす。 詳现に぀いおは、「 Microsoftむンタヌフェむス定矩蚀語MIDL64ビットポヌティングガむド 」USERおよびGDIハンドルは笊号拡匵32b倀を参照しおください。



HWND型を32ビット型に配眮するずき、開発者はこれらの仮定にほずんど基づいおいたせんでした。 ほずんどの堎合、これはWindows API開発者の幞運ず努力のために、正しく動䜜するあたりきれいなコヌドではありたせん。



おわりに



開発䞭は静的解析を定期的に䜿甚しおください。初期段階で倚くの゚ラヌが芋぀かりたす。 圓然、最初にPVS-Studioコヌドアナラむザヌを理解するこずをお勧めしたす。 ただし、他にも倚くの優れたコヌドアナラむザヌがありたす。 静的コヌド分析ツヌルです。



参照資料



蚘事に蚘茉されおいる埮劙な点のいく぀かを明確にする远加のリンク。

  1. 知識ベヌス。 メモリの䞊曞き-なぜですか
  2. ドキュメンテヌション V668 「new」挔算子を䜿甚しおメモリが割り圓おられたため、nullに察しおポむンタヌをテストしおも意味がありたせん。
  3. 知識ベヌス。 64ビットプログラムでintにポむンタを正しくキャストする方法は
  4. カルポフ・アンドレむ、゚フゲニヌ・リズコフ。 64ビットC / C ++アプリケヌションの開発のためのレッスン 。



All Articles