Visual C ++ 2012ラむブラリで芋぀かった゚ラヌ

プログラムの゚ラヌを怜出する方法の1぀は、静的コヌド分析です。 この方法論の人気が高たっおいるこずを嬉しく思いたす。 これは、静的分析が倚くの機胜の1぀であるVisual Studioによっお倧幅に促進されたす。 この機胜は簡単に詊しお、定期的に䜿甚を開始できたす。 静的コヌド分析が奜きだず気づいた人は、C / C ++ / C ++ 11蚀語甚のプロフェッショナルなPVS-Studioアナラむザヌを提䟛できるこずを嬉しく思いたす。



はじめに



Visual Studio開発環境では、静的コヌド分析が可胜です。 この分析は非垞に䟿利で䜿いやすいです。 ただし、Visual Studioは膚倧な数の機胜を実行するこずを理解しおおく必芁がありたす。 これは、1぀の機胜が垞に専甚のツヌルを倱うこずを意味したす。 リファクタリングおよびカラヌリング機胜は、Visual Assistず比范しお倱われたす。 統合された画像線集の機胜は、Adobe PhotoshopやCorelDRAWよりも圓然悪いものです。 状況は、静的コヌド分析の機胜ず䌌おいたす。



私たちは理論化したせん。 緎習に移りたしょう。 Visual Studio 2012フォルダヌにある興味深いPVS-Studioアナラむザヌの衚瀺を芋おみたしょう。



実際、Visual Studioに含たれおいる゜ヌスファむルを確認する予定はありたせんでした。 すべおが偶然に起こりたした。 Visual Studio 2012では、新しいC ++ 11蚀語暙準のサポヌトにより、倚くのヘッダヌファむルが倉曎されたした。 タスクは、PVS-Studioアナラむザヌに含たれるヘッダヌファむルを凊理できるこずを確認するこずでした。



思いがけないこずに、header * .hファむルでいく぀かの゚ラヌに気づきたした。 Visual Studio 2012に含たれるファむルをさらに詳しく調べるこずにしたした。぀たり、次のようなフォルダヌです。



ラむブラリを構築するためのプロゞェクトたたはメむクファむルがないため、完党なチェックは機胜したせんでした。 そのため、ラむブラリコヌドのわずかな郚分のみをチェックするこずができたした。 怜蚌が䞍完党であっおも、結果は非垞に興味深いものです。



Visual C ++のラむブラリ内でPVS-Studioアナラむザヌが芋぀けたものを知りたしょう。 ご芧のずおり、これらの゚ラヌはすべお、Visual C ++自䜓に組み蟌たれたアナラむザヌを介しおリヌクされたした。



芋぀かった疑わしいスポットの䞀郚



以䞋のすべおのコヌドスニペットに゚ラヌが含たれおいるずは䞻匵したせん。 PVS-Studioアナラむザヌによっお提案されたリストから、最も疑わしいず思われる堎所を遞択したした。



奇劙なサむクル



この疑わしいコヌドが最初に芋぀かりたした。 圌は研究の継続の掚進力を務めた。

template <class T> class ATL_NO_VTABLE CUtlProps : public CUtlPropsBase { .... HRESULT GetIndexOfPropertyInSet(....) { .... for(ULONG ul=0; ul<m_pUPropSet[*piCurSet].cUPropInfo; ul++) { if( dwPropertyId == pUPropInfo[ul].dwPropId ) *piCurPropId = ul; return S_OK; } return S_FALSE; } .... };
      
      





V612ルヌプ内の無条件の「戻り」。 atldb.h 4829



ルヌプの本䜓は1回だけ実行されたす。 ゚ラヌは説明を必芁ずしたせん。 ほずんどの堎合、目的の倀が芋぀かったら「return」ステヌトメントを呌び出す必芁がありたす。 この堎合、コヌドは次のようになりたす。

 for(ULONG ul=0; ul<m_pUPropSet[*piCurSet].cUPropInfo; ul++) { if( dwPropertyId == pUPropInfo[ul].dwPropId ) { *piCurPropId = ul; return S_OK; } }
      
      





疑わしい投圱



読みにくい䟋に謝りたす。 䞉項挔算子の状態に泚意しおください。

 // TEMPLATE FUNCTION proj _TMPLT(_Ty) inline _CMPLX(_Ty) proj(const _CMPLX(_Ty)& _Left) { // return complex projection return (_CMPLX(_Ty)( _CTR(_Ty)::_Isinf(real(_Left)) || _CTR(_Ty)::_Isinf(real(_Left)) ? _CTR(_Ty)::_Infv(real(_Left)) : real(_Left), imag(_Left) < 0 ? -(_Ty)0 : (_Ty)0)); }
      
      





V501 「||」の巊偎ず右偎に同䞀のサブ匏「_Ctraits <_Ty> :: _ Isinfreal_Left」がありたす 挔算子。 xcomplex 780



この条件では、匏「_CTR_Ty:: _ Isinfreal_Left」が2回繰り返されたす。 ここに゚ラヌがあるかどうか、そしおコヌドを修正する方法を蚀うのは難しいです。 ただし、この機胜は明らかに泚目に倀したす。



䞍芁なチェック



 template<typename BaseType, bool t_bMFCDLL = false> class CSimpleStringT { .... void Append(_In_reads_(nLength) PCXSTR pszSrc, _In_ int nLength) { .... UINT nOldLength = GetLength(); if (nOldLength < 0) { // protects from underflow nOldLength = 0; } .... };
      
      





V547匏 'nOldLength <0'は垞にfalseです。 笊号なしの型の倀が<0になるこずはありたせんatlsimpstr.h 420



ここに間違いはありたせん。 コヌドから刀断するず、行の長さが負になるこずはありたせん。 CSimpleStringTクラスには、察応するチェックがありたす。 倉数nOldLengthが笊号なしの型であるずいう事実は、䜕も砎壊したせん。 ずにかく、長さは垞に正です。 これは単なる冗長コヌドです。



誀ったラむン圢成



 template <class T> class CHtmlEditCtrlBase { .... HRESULT SetDefaultComposeSettings( LPCSTR szFontName=NULL, .....) const { CString strBuffer; .... strBuffer.Format(_T("%d,%d,%d,%d,%s,%s,%s"), bBold ? 1 : 0, bItalic ? 1 : 0, bUnderline ? 1 : 0, nFontSize, szFontColor, szBgColor, szFontName); .... } };
      
      





V576圢匏が正しくありたせん 。 'Format'関数の8番目の実匕数を確認するこずを怜蚎しおください。 wchar_t型シンボルの文字列ぞのポむンタヌが必芁です。 afxhtml.h 826



このコヌドは、UNICODEプログラムで誀ったメッセヌゞを生成したす。 'Format'関数は、8番目の匕数がLPCTSTR型であるず想定しおいたす。 ただし、倉数 'szFontName'は垞にLPCSTR型です。



マむナスポヌト



 typedef WORD ATL_URL_PORT; class CUrl { ATL_URL_PORT m_nPortNumber; .... inline BOOL Parse(_In_z_ LPCTSTR lpszUrl) { .... //get the port number m_nPortNumber = (ATL_URL_PORT) _ttoi(tmpBuf); if (m_nPortNumber < 0) goto error; .... };
      
      





V547匏 'm_nPortNumber <0'は垞にfalseです。 笊号なしの型の倀が<0になるこずはありたせんatlutil.h 2775



ポヌト番号がれロより小さいこずを確認しおも機胜したせん。 倉数「m_nPortNumber」の笊号なしタむプは「WORD」です。 タむプ「WORD」は「unsigned short」です。



未定矩の動䜜



次のマクロは、Visual C ++ヘッダヌファむルにありたす。

 #define DXVABitMask(__n) (~((~0) << __n))
      
      





どこで䜿甚されおも、あいたいな動䜜が発生したす。 もちろん、Visual C ++開発者は、この構造が安党かどうかをよく知っおいたす。 おそらく、Visual C ++は垞に負の数のシフトを同じ方法で凊理するずいう自信がありたす。 正匏には、負の数をシフトするず、未定矩の動䜜が発生したす。 このトピックの詳现に぀いおは、「 フォヌドを知らない、氎に入らないでください。パヌト3 」を参照しおください。



64ビットモヌドでの誀った操䜜



この64ビット゚ラヌパタヌンに぀いおは、64ビットC / C ++アプリケヌションの開発に関するレッスンで詳しく説明したす。 間違いが䜕であるかを理解するために、 レッスン12に慣れるこずをお勧めしたす。

 class CWnd : public CCmdTarget { .... virtual void WinHelp(DWORD_PTR dwData, UINT nCmd = HELP_CONTEXT); .... }; class CFrameWnd : public CWnd { .... }; class CFrameWndEx : public CFrameWnd { .... virtual void WinHelp(DWORD dwData, UINT nCmd = HELP_CONTEXT); .... };
      
      





V301予期しない関数のオヌバヌロヌド動䜜。 掟生クラス「CFrameWndEx」および基本クラス「CFrameWnd」の関数「WinHelpW」の最初の匕数を参照しおください。 afxframewndex.h 154



「WinHelp」関数が「CFrameWndEx」クラスで誀っお宣蚀されおいたす。 最初の匕数のタむプは「DWORD_PTR」でなければなりたせん。 同様の゚ラヌは、他のいく぀かのクラスでも芋られたす。



先頭のポむンタヌが䜿甚され、NULLず比范されたす



そのような堎所はかなり倚く芋぀かりたした。 特定の各ケヌスが危険であるかどうかを分析するこずは非垞に疲れたす。 ラむブラリの䜜成者は、これではるかにうたくいくでしょう。 いく぀かの䟋を挙げたす。

 BOOL CDockablePane::PreTranslateMessage(MSG* pMsg) { .... CBaseTabbedPane* pParentBar = GetParentTabbedPane(); CPaneFrameWnd* pParentMiniFrame = pParentBar->GetParentMiniFrame(); if (pParentBar != NULL && (pParentBar->IsTracked() || pParentMiniFrame != NULL && pParentMiniFrame->IsCaptured() ) ) .... }
      
      





V595 nullptrに察しお怜蚌される前に、「pParentBar」ポむンタヌが䜿甚されたした。 行を確認しおください2840、2841。afxdockablepane.cpp 2840



最初に、「pParentBar」ポむンタヌを䜿甚しおGetParentMiniFrame関数を呌び出したす。 れヌタは、このポむンタヌがNULLである可胜性があるず突然疑われ、察応するチェックが実行されたす。

 AFX_CS_STATUS CDockingManager::DeterminePaneAndStatus(....) { .... CDockablePane* pDockingBar = DYNAMIC_DOWNCAST(CDockablePane, *ppTargetBar); if (!pDockingBar->IsFloating() && (pDockingBar->GetCurrentAlignment() & dwEnabledAlignment) == 0) { return CS_NOTHING; } if (pDockingBar != NULL) { return pDockingBar->GetDockingStatus( pt, nSensitivity); } .... }
      
      





V595 nullptrに察しお怜蚌される前に、「pDockingBar」ポむンタヌが䜿甚されたした。 行を確認しおください582、587。afxdockingmanager.cpp 582



最初は、ポむンタヌ「pDockingBar」がアクティブに䜿甚され、その埌突然NULLず比范されたす。



最埌の䟋

 void CFrameImpl::AddDefaultButtonsToCustomizePane(....) { .... for (POSITION posCurr = lstOrigButtons.GetHeadPosition(); posCurr != NULL; i++) { CMFCToolBarButton* pButtonCurr = (CMFCToolBarButton*)lstOrigButtons.GetNext(posCurr); UINT uiID = pButtonCurr->m_nID; if ((pButtonCurr == NULL) || (pButtonCurr->m_nStyle & TBBS_SEPARATOR) || (....) { continue; } .... }
      
      





V595 nullptrに察しお怜蚌される前に、「pButtonCurr」ポむンタヌが䜿甚されたした。 行を確認しおください1412、1414。afxframeimpl.cpp 1412



クラス「m_nID」のメンバヌに気軜に連絡しおください。 そしお、条件 'pButtonCurr'が0になる可胜性があるこずがわかりたす。



砎壊されたオブゞェクトを䜿甚する



 CString m_strBrowseFolderTitle; void CMFCEditBrowseCtrl::OnBrowse() { .... LPCTSTR lpszTitle = m_strBrowseFolderTitle != _T("") ? m_strBrowseFolderTitle : (LPCTSTR)NULL; .... }
      
      





V623 「」挔算子の怜査を怜蚎しおください。 䞀時オブゞェクトが䜜成され、その埌砎棄されたす。 afxeditbrowsectrl.cpp 308



䞉項挔算子は、異なる型の倀を返すこずはできたせん。 したがっお、タむプCStringのオブゞェクトは、「LPCTSTRNULL」から暗黙的に䜜成されたす。 次に、この空の文字列から暗黙的にバッファぞのポむンタが取埗されたす。 問題は、CStringのような䞀時オブゞェクトが砎壊されるこずです。 その結果、「lpszTitle」ポむンタヌの倀が無効になり、操䜜できなくなりたす。 ここでは、この゚ラヌパタヌンの詳现な説明を読むこずができたす。



時間の問題



 UINT CMFCPopupMenuBar::m_uiPopupTimerDelay = (UINT) -1; .... void CMFCPopupMenuBar::OnChangeHot(int iHot) { .... SetTimer(AFX_TIMER_ID_MENUBAR_REMOVE, max(0, m_uiPopupTimerDelay - 1), NULL); .... }
      
      





V547匏 '0>m_uiPopupTimerDelay-1'は垞にfalseです。 笊号なしの型の倀が<0になるこずはありたせん。afxpopupmenubar.cpp 968



倀 '-1'は特別なフラグずしお䜿甚されたす。 「max」マクロを䜿甚しお、プログラマはm_uiPopupTimerDelay倉数の負の倀から保護するこずを望んでいたした。 倉数には笊号なしの型があるため、これは機胜したせん。 垞にれロ以䞊です。 正しいコヌドは次のようになりたす。

 SetTimer(AFX_TIMER_ID_MENUBAR_REMOVE, m_uiPopupTimerDelay == (UINT)-1 ? 0 : m_uiPopupTimerDelay - 1, NULL);
      
      





同様の゚ラヌはこちらです



無意味な線



 BOOL CMFCTasksPaneTask::SetACCData(CWnd* pParent, CAccessibilityData& data) { .... data.m_nAccHit = 1; data.m_strAccDefAction = _T("Press"); data.m_rectAccLocation = m_rect; pParent->ClientToScreen(&data.m_rectAccLocation); data.m_ptAccHit; return TRUE; }
      
      





V607所有者のない匏「data.m_ptAccHit」。 afxtaskspane.cpp 96



「data.m_ptAccHit;」ずは䜕ですか おそらくここで、圌らはこの倉数に倀を割り圓おるのを忘れおいたのでしょうか



別の0が必芁な堎合がありたす



 BOOL CMFCTasksPane::GetMRUFileName(....) { .... const int MAX_NAME_LEN = 512; TCHAR lpcszBuffer [MAX_NAME_LEN + 1]; memset(lpcszBuffer, 0, MAX_NAME_LEN * sizeof(TCHAR)); if (GetFileTitle((*pRecentFileList)[nIndex], lpcszBuffer, MAX_NAME_LEN) == 0) { strName = lpcszBuffer; return TRUE; } .... }
      
      





V512 「memset」関数の呌び出しにより、バッファヌ「lpcszBuffer」のアンダヌフロヌが発生したす。 afxtaskspane.cpp 2626



このコヌドは、終端がれロではない文字列を返す可胜性があるずいう疑いがありたす。 おそらく、配列の最埌の芁玠もリセットする必芁がありたす。

 memset(lpcszBuffer, 0, (MAX_NAME_LEN + 1) * sizeof(TCHAR));
      
      





奇劙な「if」



 void CMFCVisualManagerOfficeXP::OnDrawBarGripper(....) { .... if (bHorz) { rectFill.DeflateRect(4, 0); } else { rectFill.DeflateRect(4, 0); } .... }
      
      





V523 「then」ステヌトメントは「else」ステヌトメントず同等です。 afxvisualmanagerofficexp.cpp 264



危険なクラスsingle_link_registry



クラス「single_link_registry」を䜿甚するず、すべおの䟋倖を正しく凊理した堎合でも、アプリケヌションが予期せず終了する堎合がありたす。 クラス「single_link_registry」のデストラクタを芋おみたしょう。

 virtual ~single_link_registry() { // It is an error to delete link registry with links // still present if (count() != 0) { throw invalid_operation( "Deleting link registry before removing all the links"); } }
      
      





V509デストラクタ内の「スロヌ」挔算子は、try..catchブロック内に配眮する必芁がありたす。 デストラクタ内で䟋倖を発生させるこずは違法です。 agents.h 759



このデストラクタは䟋倖をスロヌする堎合がありたす。 これは悪い考えです。 プログラムで䟋倖が発生するず、デストラクタの呌び出しによるオブゞェクトの砎壊が始たりたす。 「single_link_registry」デストラクタで゚ラヌが発生するず、別の䟋倖がスロヌされたす。 デストラクタでは凊理されたせん。 その結果、C ++ラむブラリは、terminate関数を呌び出しおプログラムを盎ちにクラッシュさせたす。



同様の悪いデストラクタ



別の奇劙なルヌプ



 void CPreviewView::OnPreviewClose() { .... while (m_pToolBar && m_pToolBar->m_pInPlaceOwner) { COleIPFrameWnd *pInPlaceFrame = DYNAMIC_DOWNCAST(COleIPFrameWnd, pParent); if (!pInPlaceFrame) break; CDocument *pViewDoc = GetDocument(); if (!pViewDoc) break; // in place items must have a server document. COleServerDoc *pDoc = DYNAMIC_DOWNCAST(COleServerDoc, pViewDoc); if (!pDoc) break; // destroy our toolbar m_pToolBar->DestroyWindow(); m_pToolBar = NULL; pInPlaceFrame->SetPreviewMode(FALSE); // restore toolbars pDoc->OnDocWindowActivate(TRUE); break; } .... }
      
      





V612ルヌプ内の無条件の「ブレヌク」。 viewprev.cpp 476



ルヌプには「継続」ステヌトメントはありたせん。 ルヌプの最埌は「break」です。 これは非垞に奇劙です。 サむクルは垞に1回だけ実行されたす。 ここに゚ラヌがあるか、「while」を「if」に眮き換える方が良いでしょう。



奇劙な定数



リストには興味深いものではないコヌドに関する他のマむナヌなコメントがありたす。 䜕が危機にclearしおいるのかが明確になるように、1぀の䟋を挙げたす。



afxdrawmanager.cppファむルでは、䜕らかの理由で、Pi番号に定数が蚭定されおいたす。

 const double AFX_PI = 3.1415926;
      
      





V624定数3.1415926が䜿甚されおいたす。 結果の倀は䞍正確になる可胜性がありたす。 <math.h>のM_PI定数の䜿甚を怜蚎しおください。 afxdrawmanager.cpp 22



これは間違いではなく、定数の粟床で十分です。 しかし、M_PI定数を䜿甚しない理由は明確ではありたせん。M_PI定数ははるかに正確に蚭定されおいたす。

 #define M_PI 3.14159265358979323846
      
      





Visual C ++開発者の呌び出し



残念ながら、Visual C ++の䞀郚であるラむブラリを構築するためのプロゞェクトたたはメむクファむルはありたせん。 したがっお、分析は非垞に衚面的です。 䜕かを芋぀けお、それに぀いお曞きたした。 他に疑わしい堎所がないずは思わないでください:)。



PVS-Studioを䜿甚しおラむブラリをチェックする方がはるかに䟿利であるず確信しおいたす。 必芁に応じお、メむクファむルぞの統合を促し、支揎する準備ができおいたす。 たた、䜕が間違いで䜕がそうでないかを理解するのも簡単です。



結論



Visual Studio 2012では、C / C ++コヌドの静的分析がありたす。 しかし、これはこれで十分ずいう意味ではありたせん。 これは最初のステップにすぎたせん。 これは、コヌドの品質を改善するための新しいテクノロゞヌを簡単か぀安䟡に詊すチャンスです。 そしお、気に入ったら、私たちのずころに来おPVS-Studioを賌入しおください。 このツヌルは、欠陥ずより集䞭的に戊いたす。 圌はこのために投獄されおいたす。 私たちはそれでお金を皌ぎたす。぀たり、非垞に積極的に開発しおいたす。



Visual C ++ラむブラリで゚ラヌが芋぀かりたしたが、静的分析がありたす。 Clangコンパむラにはバグが芋぀かりたしたが、静的分析がありたす。 私たちを取埗し、私たちはあなたのプロゞェクトの゚ラヌを定期的に芋぀けたす。 Visual Studio 2005、2008、2010、2012には優れたむンタヌンがあり、バックグラりンドで゚ラヌを探すこずができたす。



All Articles