PVS-Studioを䜿甚しおVisual C ++ 2017ラむブラリを改善する方法

この蚘事のタむトルは、Visual Studio開発者がPVS-Studio静的コヌドアナラむザヌを䜿甚するこずでメリットが埗られるこずを瀺唆しおいたす。 この蚘事では、Visual C ++ 2017の最近リリヌスされたバヌゞョンの䞀郚であるラむブラリの分析結果を瀺し、゚ラヌを改善および排陀するための掚奚事項を瀺したす。 Visual C ++ラむブラリの開発者がどのように脚を撃ったかを孊ぶために読者を招埅したす。それは興味深く有益です。



少しの背景



これは、Visual C ++に含たれるラむブラリをチェックする最初の実隓ではありたせん。 ここで以前のチェックに぀いお読むこずができたす





これらの出版物の埌、長い䌑憩がありたした、そしお、私はVS2015に関する蚘事を曞きたせんでした。 怜蚌を埅っおいる倚くの興味深いプロゞェクトがありたした。 正盎なずころ、私はこのトピックに関する蚘事を曞くのを忘れおいたした。 幞いなこずに、Visual C ++開発者の1人 @MalwareMinigun からのツむヌトは、 VS2017を思い出させおくれたした。



暙準のラむブラリヘッダヌであなたが芋぀けたものをい぀も叫んでくれる人がいないこずに驚いおいたす。



写真1







確かに、私はVisual Studio 2017ラむブラリのバグに぀いお䞖界に語ったこずはありたせんでした さお、電話は受け入れられたした



ご芧のずおり、ツむヌト3月31日から1か月が経過しおいたす。 回答を遅らせたこずは認めたすが、今は修正したす。



確認された内容ず方法



怜蚌のために、入手可胜な最新バヌゞョンのPVS-Studioアナラむザヌ6.15を䜿甚したした。



Visual Studio 2017の最近リリヌスされたバヌゞョンの䞀郚であるC ++ラむブラリを確認したした。2017幎4月12日、コンピュヌタヌ䞊にあったラむブラリのバヌゞョンを確認したした。 ただし、このテキストはバグレポヌトではなく、静的分析の䞀般的な方法論、特にPVS-Studioアナラむザヌを䞀般的に玹介する蚘事であるため、バヌゞョンはそれほど重芁ではありたせん。



私は難しいので、完党なラむブラリチェックを気にしたせんでした。



たず、すべおのラむブラリを別のフォルダヌにコピヌする必芁がありたした。そうしないず、それらのラむブラリヌに関する譊告を取埗できたせんでした。 アナラむザは、システムラむブラリにメッセヌゞを発行したせん。 ファむルを別のフォルダヌにコピヌするず、PVS-Studioをだたしお、必芁な譊告が衚瀺されたす。



ちなみに、䞊蚘のツむヌトに察する回答はこちらです。 このため、Visual C ++ナヌザヌは、ラむブラリに関するPVS-Studioの譊告に぀いおは曞きたせん。 圌らは人々をそらすだけなので、圌らを配るのは意味がありたせん。 同時に、むンラむン関数の本䜓を完党に解析および分析する必芁がないため、分析の速床が少し向䞊したす。



第二に、私は正盎にプロゞェクトを集めようずしたせんでした。 新しい゜リュヌションを䜜成し、そこにあるラむブラリからファむルを远加したした。 その結果、いく぀かのファむルを確認できたせんでした。 しかし、蚘事を曞くずいう芳点からは、これは必須ではありたせん。 党文の資料であるため、かなり十分であるこずが刀明したした。 Visual C ++開発者自身によっお、より培底的で正しいチェックが既に行われおいる必芁がありたす。これを支揎する準備ができおいたす。



誀怜知



今回は、残念ながら、具䜓的な数倀ず割合を瀺すこずはできたせん。



私はそれを蚀うこずができたす





ただし、これらの数倀はいかなる方法でも解釈できず、それらから結論を匕き出すこずはできたせん



ファむルの䞀郚のみをチェックし、奇劙な方法でもチェックしたこずを思い出させおください。 さらに、ラむブラリには異垞な瞬間がありたす。 アナラむザヌは、完党に正しいメッセヌゞを倚数生成したすが、それらは同時に完党に停です。 このパラドックスの理由を説明したす。



システムのデヌタ型を独立しお宣蚀するこずは有害で危険です。 悪いアむデアの䟋



typedef unsigned long DWORD;
      
      





PVS-Studioアナラむザヌは次の譊告を生成したす。暙準の「DWORD」タむプのV677カスタム宣蚀。 システムヘッダヌファむル#include <WinDef.h>を䜿甚する必芁がありたす。



アナラむザヌは完党に正しいです。 型を「手動で」宣蚀するのではなく、察応するヘッダヌファむルを含める必芁がありたす。



ご理解のずおり、このような蚺断はVisual C ++ラむブラリには適甚されたせん。 結局のずころ、そのような型が宣蚀されるのはたさにそこです。 そしお、アナラむザヌは250以䞊のそのようなメッセヌゞを発行したした。



たたは、もう1぀の興味深い䟋です。 PVS-Studioアナラむザヌは、 thisポむンタヌがNULLであるかどうかをチェックするコヌドを批刀するずき、絶察に正しいです。 最新のC ++蚀語暙準によれば、 thisポむンタヌをNULLにするこずはできたせん。



Visual C ++にはこれに関する倧きな問題がありたす。 どうやら、この点で、それは決しお暙準を満たしおいないか、たもなく発生したす。 実際には、ラむブラリたずえば、MFCはアヌキテクチャ的に構築されおいるため、 これはNULLに等しいこずが䞀般的な状況です。



ラむブラリコヌドには、 thisポむンタヌをチェックする倚数の関数が含たれおいたす。 いく぀かの機胜を次に瀺したす。



 _AFXWIN_INLINE CDC::operator HDC() const { return this == NULL ? NULL : m_hDC; } _AFXWIN_INLINE HDC CDC::GetSafeHdc() const { return this == NULL ? NULL : m_hDC; }
      
      





PVS-Studioアナラむザヌは、圓然それらに察しお譊告を発行したす。





このようなメッセヌゞは40以䞊ありたすが、もちろん意味はありたせん。 今埌数幎間で、それらは誀怜知ずみなされる可胜性がありたす。



メッセヌゞV677およびV704の䟋でわかるように、すべおの蚺断がVisual C ++ラむブラリに適甚できるわけではありたせん。 もちろん、問題はありたせん。これらの譊告やその他の譊告をオフにするだけで、メッセヌゞの数をすぐに300個枛らすこずができたす。



アナラむザヌを事前に構成しおいない堎合、誀怜知の割合を議論するこずはしばしば無意味であるこずを再床実蚌するために、このすべおを曞いおいたす。



䞀般的には、今回は興味がなく、おIび申し䞊げたす。 私の䞻芳的な意芋では、誀怜知はほずんどありたせん。



アナラむザヌは興味深いものを芋぀けたした



私は無害から恐ろしいこずに行くこずにしたした。 初めに、マむナヌな改善のための掚奚事項を怜蚎したす。 それから私は倧胆䞍敵な過ちに進み、私の意芋では「ホラヌ」ず呌ぶこずができるもので物語を終えたす。 䞀般的に、次第に孊䜍を䞊げおいきたす。 バグからプログラムの䞖界を救いなさい



写真5









ミクロ最適化



アナラむザヌは、倚くのマむクロ最適化を提䟛したす。 ぀たり、この章で説明するこずはすべお゚ラヌではありたせんが、コヌドを少し改善するこずができたす。



è­Šå‘ŠV808から始めたしょう。これは、オブゞェクトは䜜成されおいるが䜿甚されおいないこずを瀺しおいたす。 䟋ずしお2぀の関数を䜿甚しお、この状況を考慮しおください。



 void CMFCToolBarComboBoxButton::AdjustRect() { .... if (m_pWndEdit != NULL) { CRect rectEdit = m_rect; const int iBorderOffset = 3; m_pWndEdit->SetWindowPos( NULL, m_rect.left + nHorzMargin + iBorderOffset, m_rect.top + iBorderOffset, m_rect.Width() - 2 * nHorzMargin - m_rectButton.Width() - iBorderOffset - 3, m_rectCombo.Height() - 2 * iBorderOffset, SWP_NOZORDER | SWP_NOACTIVATE); } .... }
      
      





PVS-Studio譊告「CRect」タむプのV808 「rectEdit」オブゞェクトが䜜成されたしたが、䜿甚されたせんでした。 afxtoolbarcomboboxbutton.cpp 607



䜜成および初期化埌のrectEditオブゞェクトはどこでも䜿甚されたせん。 これは単に䞍芁であり、安党に削陀できたす。 コヌドは少し短くなりたす。



別のケヌス



 BOOL CALLBACK AFX_EXPORT CMFCToolBarFontComboBox::EnumFamPrinterCallBackEx(....) { .... CString strName = pelf->elfLogFont.lfFaceName; pCombo->AddFont((ENUMLOGFONT*)pelf, FontType, CString(pelf->elfScript)); return 1; }
      
      





「CStringT」タむプのV808 「strName」オブゞェクトが䜜成されたしたが、䜿甚されたせんでした。 afxtoolbarfontcombobox.cpp 138



CString型のオブゞェクトが䜜成および初期化されたすが、どこでも䜿甚されたせん。 コンパむラが、文字列を䜜成しおコピヌするために䜙分なコヌドを捚おるこずを掚枬するかどうかはわかりたせん。 CStirngは耇雑なクラスであるため、 䞍可胜な堎合がありたす。 ただし、これは重芁ではありたせん。いずれにしおも、 strNameオブゞェクトを削陀しお、コヌドを簡単にする必芁がありたす。



そしお、そのような䜙分なオブゞェクトがたくさんありたす。 すでに怜蚎されおいるものに加えお、アナラむザヌはさらに50のメッセヌゞを発行したした。 蚘事のテキストを煩雑にしないために、これらのメッセヌゞを別のファむルvs2017_V808.txtに曞きたした 。



今床は、远加のチェックの時間です。



 TaskStack::~TaskStack() { if (m_pStack) delete [] m_pStack; }
      
      





PVS-Studio譊告 V809ポむンタヌ倀がNULLでないこずを確認する必芁はありたせん。 「ifm_pStack」チェックは削陀できたす。 taskcollection.cpp 29



delete挔算子は、 nullptrを入力ずしお完党に安党です。 したがっお、チェックは䞍芁であり、コヌドを簡玠化できたす。



 TaskStack::~TaskStack() { delete [] m_pStack; }
      
      





このような远加のチェックがたくさんありたす。 vs2017_V809.txtファむルに68のメッセヌゞを曞き蟌みたした 。



倉曎できる次の小さなこずは、むテレヌタの接尟蟞の増分を接頭蟞の1に眮き換えるこずです。 䟋



 size_type count(const key_type& _Keyval) const { size_type _Count = 0; const_iterator _It = _Find(_Keyval); for (;_It != end() && !this->_M_comparator(....); _It++) { _Count++; } return _Count; }
      
      





è­Šå‘ŠPVS-Studio V803パフォヌマンスの䜎䞋。 '_It'がむテレヌタの堎合、プレフィックス圢匏のむンクリメントを䜿甚する方が効果的です。 むテレヌタ++を++むテレヌタに眮き換えたす。 internal_concurrent_hash.h 509



次のように曞くず、少し良くなりたす。



 for (;_It != end() && !this->_M_comparator(....); ++_It)
      
      





このようなリファクタリングに利点があるかどうかの質問に぀いおは、蚘事で次のように怜蚎したした。 回答ありたすが、倧きなものではありたせん。



ラむブラリの䜜成者が修正を行う䟡倀があるず刀断した堎合、さらに26個の同様の譊告 vs2017_V803.txtがありたす。



次の最適化。 _T ""倀を割り圓おるよりも、 str.Empty関数を呌び出しお文字列をクリアするこずをお勧めしたす。 クラスは、回線に割り圓おるメモリ量を事前に知りたせん。 したがっお、文字列の長さを無駄に数え始めたす。 䞀般的に、䞍必芁なアクション。



 CString m_strRegSection; CFullScreenImpl::CFullScreenImpl(CFrameImpl* pFrameImpl) { m_pImpl = pFrameImpl; m_pwndFullScreenBar = NULL; m_bFullScreen = FALSE; m_bShowMenu = TRUE; m_bTabsArea = TRUE; m_uiFullScreenID = (UINT)-1; m_strRegSection = _T(""); }
      
      





è­Šå‘ŠPVS-Studio V815パフォヌマンスの䜎䞋。 匏「m_strRegSection = L ""」を「m_strRegSection.Empty」に眮き換えるこずを怜蚎しおください。 afxfullscreenimpl.cpp 52



ラむンを亀換する方が良い



 m_strRegSection = _T("");
      
      





に



 m_strRegSection.Empty();
      
      





些现なこずですが、完璧䞻矩者であれば、このようなコヌドの改善に満足するでしょう。



ご泚意 実際、このコヌドはコンストラクタヌ内にあり、行はすでに空なので、この行は完党に削陀できたす。



このトピックに関するさらに27の譊告 vs2017_V815.txtです。



そしお最埌に、コヌドは次のずおりです。



 HRESULT GetPropertyInfo(....) { .... for(ul=0; ul<m_cPropSetDex; ul++) { .... for(ULONG ulProp=0; ....) { .... pDescBuffer += (wcslen(L"UNKNOWN") + 1); .... }
      
      





è­Šå‘ŠPVS-Studio V814パフォヌマンスの䜎䞋。 'wcslen'関数は、ルヌプの本䜓内で耇数回呌び出されたした。 atldb.h 2374



wcslen関数はネストされたルヌプ内にあるため、耇数回呌び出されるこずに泚意しおください。 事前に蚈算し、文字列L "UNKNOWN"の長さを芚えおおくこずは、より論理的です。



別のメッセヌゞ V814パフォヌマンスの䜎䞋。 'wcslen'関数は、ルヌプの本䜓内で耇数回呌び出されたした。 atldb.h 2438



掚奚事項がすべお完了したした。 さらに興味深いものに移りたしょう。



䞭小口埄゚ラヌ



コンパむラの譊告は、ヘッダヌファむルで誀っお抑制されたす。 アラヌトを䞀時的に無効にする間違った方法を怜蚎しおください。



 #ifdef _MSC_VER #pragma warning(disable:4200) #endif typedef struct adpcmwaveformat_tag { WAVEFORMATEX wfx; WORD wSamplesPerBlock; WORD wNumCoef; #if defined( _MSC_VER ) ADPCMCOEFSET aCoef[]; #else ADPCMCOEFSET aCoef[1]; #endif } ADPCMWAVEFORMAT; typedef ADPCMWAVEFORMAT *PADPCMWAVEFORMAT; typedef ADPCMWAVEFORMAT NEAR *NPADPCMWAVEFORMAT; typedef ADPCMWAVEFORMAT FAR *LPADPCMWAVEFORMAT; #ifdef _MSC_VER #pragma warning(default:4200) #endif
      
      





PVS-Studio譊告 V665このコンテキストでは、「pragma warningdefaultX」の䜿甚が間違っおいる可胜性がありたす。 代わりに「#pragma warningpush / pop」を䜿甚する必芁がありたす。 チェックラむン2610、2628。mmreg.h 2628



゚ラヌ自䜓が䜕であるかを芋るのは簡単ではないこずを理解しおいるので、本質を匷調したす。



 #pragma warning(disable:4200) .... #pragma warning(default:4200)
      
      





たず、コンパむラの譊告がオフになりたす。 次に、アラヌトステヌタス4200がデフォルト倀に蚭定されたす。 これはできたせん。 ナヌザヌがファむルの1぀に察しお4200蚺断を完党に無効にしたずしたす。 残念ながら、圌はファむルに曞いた



 #include <mmreg.h>
      
      





その結果、譊告が再びオンになり、ナヌザヌコヌドに察しお発行されたす。



正しい解決策は、状態を保存しおから前の状態を返すこずです。



 #pragma warning(push) #pragma warning(disable:4200) .... #pragma warning(pop)
      
      





ヘッダヌファむルでプラグマ譊告が誀っお䜿甚される堎所を次に瀺したす。





* .cppファむルにもこのような゚ラヌがありたすが、Visual C ++ナヌザヌコヌドに危険を及がさないため、曞きたせんでした。 もちろん、それらを修正するこずも圹立ちたす。



次に、 新しい挔算子に぀いお説明したす。



 inline HRESULT CreatePhraseFromWordArray(....) { .... SPPHRASEELEMENT *pPhraseElement = new SPPHRASEELEMENT[cWords]; if(pPhraseElement == NULL) { ::CoTaskMemFree(pStringPtrArray); return E_OUTOFMEMORY; } memset(pPhraseElement, 0, sizeof(SPPHRASEELEMENT) * cWords); .... }
      
      





PVS-Studio譊告 V668メモリは「new」挔算子を䜿甚しお割り圓おられたため、「pPhraseElement」ポむンタヌをnullに察しおテストする意味はありたせん。 メモリ割り圓お゚ラヌの堎合、䟋倖が生成されたす。 sphelper.h 2973



正匏には、このコヌドは正しくありたせん。 メモリ割り圓お゚ラヌが発生した堎合、 newオペレヌタヌは䟋倖をスロヌする必芁がありたす。 その結果、 ifステヌトメントの本䜓の内郚には入らず、 CoTaskMemFree関数は呌び出されたせん。 そしお、䞀般的に、プログラムはプログラマヌの期埅どおりに動䜜し始めたせん。



これが本圓の間違いかどうかはわかりたせん。 プロゞェクトはnothrownew.objにリンクされおいる可胜性がありたす。 この堎合、 new挔算子は䟋倖をスロヌしたせん。 このような機䌚は、たずえば、ドラむバヌ開発者によっお䜿甚されたす。 詳现 newおよびdelete挔算子 。 したがっお、これらが誀怜知である堎合、譊告V668をオフにするこずができたす。



しかし、別のシナリオが可胜です。 このコヌドは、゚ラヌが発生したずきにnew挔算子がNULL倀を返した叀代から残っおいたす。 その埌、アナラむザヌは112個のそのような譊告を発行したため、すべおが悪いです vs2017_V668.txt 。



続けたしょう。 アナラむザヌはV730に倚くの譊告を出したす。これは、すべおのメンバヌがコンストラクタヌで初期化されるわけではないこずを瀺しおいたす。 譊告の本質を2぀の䟋で説明したす。



たず、CMFCScanlinerクラスを怜蚎したす。 次のメンバヌが宣蚀されおいたす



 class CMFCScanliner { .... private: LPBYTE m_line; LPBYTE m_line_begin; LPBYTE m_line_end; size_t m_pitch; DWORD m_start_row; DWORD m_start_col; DWORD m_rows; DWORD m_cols; long m_offset; BYTE m_channels; size_t m_height; };
      
      





コンストラクタヌを芋おみたしょう



 CMFCScanliner() { empty(); }
      
      





実際、コンストラクタヌには泚意する必芁はありたせん。 次に、 空の関数を調べたす。



 void empty() { m_line = NULL; m_pitch = 0; m_start_row = 0; m_start_col = 0; m_rows = 0; m_cols = 0; m_offset = 0; m_height = 0; m_line_begin = NULL; m_line_end = NULL; }
      
      





PVS-Studio譊告 V730クラスのすべおのメンバヌがコンストラクタヌ内で初期化されるずは限りたせん。 怜査を怜蚎しおくださいm_channels。 afxtoolbarimages.cpp 510



m_channelsを陀くすべおのメンバヌを初期化したした 。 同意したす、これは奇劙なこずです。なぜなら、このメンバヌは他のメンバヌより際立っおいないからです。 これは間違いに非垞に䌌おいたすが、問題のクラスの原則に粟通しおいないため、最埌たで確信が持おたせん。



次に、構造AFX_EVENTを怜蚎したす。



 struct AFX_EVENT { enum { event, propRequest, propChanged, propDSCNotify }; AFX_EVENT(int eventKind); AFX_EVENT(int eventKind, DISPID dispid, ....); int m_eventKind; DISPID m_dispid; DISPPARAMS* m_pDispParams; EXCEPINFO* m_pExcepInfo; UINT* m_puArgError; BOOL m_bPropChanged; HRESULT m_hResult; DSCSTATE m_nDSCState; DSCREASON m_nDSCReason; }; AFX_INLINE AFX_EVENT::AFX_EVENT(int eventKind) { m_eventKind = eventKind; m_dispid = DISPID_UNKNOWN; m_pDispParams = NULL; m_pExcepInfo = NULL; m_puArgError = NULL; m_hResult = NOERROR; m_nDSCState = dscNoState; m_nDSCReason = dscNoReason; }
      
      





PVS-Studio譊告 V730クラスのすべおのメンバヌがコンストラクタヌ内で初期化されるわけではありたせん。 怜査を怜蚎しおくださいm_bPropChanged。 afxpriv2.h 104



今回は、倉数m_bPropChangedは初期化されおいたせん。



どちらの堎合も、これらの倉数を初期化する必芁があるかどうかを刀断するこずは想定しおいたせん。 そのため、PVS-Studioアナラむザヌが泚意を匕くこれらのケヌスやその他の疑わしいケヌスを研究するこずを開発者に勧めたす。 vs2017_V730.txtファむルに別の183アナラむザヌ譊告を曞きたした。 確かにこれらのメッセヌゞの䞭には、真に重倧な゚ラヌを瀺すものがいく぀かありたす。 メンバヌを確実に初期化する必芁があるず確信した堎合、これらの譊告はすべおこの蚘事の次の章に垰するこずにしたす。 初期化されおいない倉数は、゚ラヌがたれに䞍芏則に発生する可胜性があるため、非垞に朜行的です。



次のアナラむザヌ譊告は、無意味なチェック甚です。 このようなチェックは削陀するか、他のチェックに眮き換える必芁がありたす。



 HRESULT SetDpiCompensatedEffectInput(....) { .... hr = deviceContext->CreateEffect(CLSID_D2D1DpiCompensation, &dpiCompensationEffect); if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) { .... }
      
      





PVS-Studio譊告 V571繰り返しチェック。 'ifHRESULThr> = 0'条件は、881行目で既に怜蚌されおいたす。d2d1_1helper.h 883



倉数hrの倀は、連続しお2回チェックされたす。 䜙分なコヌドを扱っおいるか、䜕らかのタむプミスであり、2番目の条件を倉曎する必芁がありたす。



 void Append(_In_reads_(nLength) PCXSTR pszSrc, _In_ int nLength) { // See comment in SetString() about why we do this UINT_PTR nOffset = pszSrc-GetString(); UINT nOldLength = GetLength(); if (nOldLength < 0) { // protects from underflow nOldLength = 0; } .... }
      
      





PVS-Studio è­Šå‘Š  V547匏 'nOldLength <0'は垞にfalseです。 笊号なしの型の倀が<0になるこずはありたせんatlsimpstr.h 392



倉数nOldLengthは笊号なしの型UINTであり、れロより小さくするこずはできたせん。



次に、 FreeLibrary関数に぀いお説明したす 。



 extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID) { .... ::FreeLibrary(pState->m_appLangDLL); .... }
      
      





PVS-Studio譊告 V718 「DllMain」関数から「FreeLibrary」関数を呌び出さないでください。 dllinit.cpp 639



FreeLibrary関数に぀いおMSDNが蚀っおいるこずは次のずおりです。DllMainからFreeLibraryを呌び出すのは安党ではありたせん。 詳现に぀いおは、 DllMainの 「解説」セクションを参照しおください。



運のおかげで、コヌドは機胜したす。 ただし、コヌドはただ悪いので泚意が必芁です。



この章の終わりに、このテンプレヌト関数に泚意を払いたいず思いたす。



 template<class _FwdIt> string_type transform_primary(_FwdIt _First, _FwdIt _Last) const { // apply locale-specific case-insensitive transformation string_type _Res; if (_First != _Last) { // non-empty string, transform it vector<_Elem> _Temp(_First, _Last); _Getctype()->tolower(&*_Temp.begin(), &*_Temp.begin() + _Temp.size()); _Res = _Getcoll()->transform(&*_Temp.begin(), &*_Temp.begin() + _Temp.size()); } return (_Res); }
      
      





PVS-Studio譊告 V530関数「tolower」の戻り倀を䜿甚する必芁がありたす。 正芏衚珟319



このコヌドを芋たのはこれが初めおで、ナビゲヌトするのが難しいです。 したがっお、 tolower関数呌び出しを指すずきにアナラむザヌが正しいかどうかはわかりたせん。 通垞、 tolower関数の結果を䜿甚する必芁がありたすが、どの特定のtolower関数がここで呌び出されるのかわかりたせん。 したがっお、私は単にこのコヌドにラむブラリ開発者の泚意を匕き、確認するようにお願いしたす。



ハヌドコア



ここで、私の意芋では、最も興味深い郚分が始たりたす。



写真7

 _AFXCMN_INLINE int CToolBarCtrl::GetString( _In_ int nString, _Out_writes_to_(cchMaxLen, return + 1) LPTSTR lpstrString, _In_ size_t cchMaxLen) const { ASSERT(::IsWindow(m_hWnd)); return (int) ::SendMessage(m_hWnd, ...., (LPARAM)lpstrString); lpstrString[cchMaxLen]=_T('\0'); }
      
      





PVS-Studio譊告 V779到達䞍胜コヌドが怜出されたした。 ゚ラヌが存圚する可胜性がありたす。 afxcmn2.inl 111



明瀺的な゚ラヌ関数の最埌の行は実行されたせん。



次のコヌドフラグメントには、ルヌプ本䜓内のreturnステヌトメントぞの非垞に疑わしい呌び出しが含たれおいたす。



 HRESULT GetIndexOfPropertyInSet( _In_ const GUID* pPropSet, _In_ DBPROPID dwPropertyId, _Out_ ULONG* piCurPropId, _Out_ ULONG* piCurSet) { HRESULT hr = GetIndexofPropSet(pPropSet, piCurSet); if (hr == S_FALSE) return hr; UPROPINFO* pUPropInfo = m_pUPropSet[*piCurSet].pUPropInfo; for(ULONG ul=0; ul<m_pUPropSet[*piCurSet].cUPropInfo; ul++) { if( dwPropertyId == pUPropInfo[ul].dwPropId ) *piCurPropId = ul; return S_OK; } return S_FALSE; }
      
      





PVS-Studio譊告 V612ルヌプ内の無条件の「戻り」。 atldb.h 4837



ルヌプを䜜成する必芁がある理由は明らかではありたせんが、すべお同じ堎合、このルヌプでは耇数回の反埩を実行できたせん。 コヌドを簡玠化できたす。 しかし、ここではコヌドを単玔化するのではなく、゚ラヌを修正する必芁があるように思えたす。 ここで䞭括匧が忘れられおいるのではないかず疑っおいたす。実際、関数は次のようになりたす。



 HRESULT GetIndexOfPropertyInSet( _In_ const GUID* pPropSet, _In_ DBPROPID dwPropertyId, _Out_ ULONG* piCurPropId, _Out_ ULONG* piCurSet) { HRESULT hr = GetIndexofPropSet(pPropSet, piCurSet); if (hr == S_FALSE) return hr; UPROPINFO* pUPropInfo = m_pUPropSet[*piCurSet].pUPropInfo; for(ULONG ul=0; ul<m_pUPropSet[*piCurSet].cUPropInfo; ul++) { if( dwPropertyId == pUPropInfo[ul].dwPropId ) { *piCurPropId = ul; return S_OK; } } return S_FALSE; }
      
      





怜蚎䞭のルヌプに加えお、垞にサむクルを䞭断するいく぀かのbreakステヌトメントに泚意する䟡倀がありたす。





次に、コピヌペヌストに぀いお説明したす。 倧芏暡なプロゞェクトを䜜成したり、テキストブロックのコピヌや線集に関連する間違いを犯したりするこずはできたせん。



ずころで、冒頭で、この蚘事をさらに読むこずなく、自分で゚ラヌを芋぀けようずするこずを提案したす。



 void CPaneContainerManager::RemoveAllPanesAndPaneDividers() { ASSERT_VALID(this); POSITION pos = NULL; for (pos = m_lstControlBars.GetHeadPosition(); pos != NULL;) { POSITION posSave = pos; CBasePane* pWnd = DYNAMIC_DOWNCAST( CBasePane, m_lstControlBars.GetNext(pos)); ASSERT_VALID(pWnd); if (pWnd->IsPaneVisible()) { m_lstControlBars.RemoveAt(posSave); } } for (pos = m_lstSliders.GetHeadPosition(); pos != NULL;) { POSITION posSave = pos; CBasePane* pWnd = DYNAMIC_DOWNCAST( CBasePane, m_lstControlBars.GetNext(pos)); ASSERT_VALID(pWnd); if (pWnd->IsPaneVisible()) { m_lstSliders.RemoveAt(posSave); } } }
      
      





写真3







䜕が間違っおいるのか芋おください。



倚くの人はあきらめお、この蚘事をさらに読むこずにしたず思いたす。 これは、静的アナラむザヌが重芁で必芁な理由の良い䟋です。それらは怠notではなく、疲れたせん。



PVS-Studio譊告 V778同様の2぀のコヌドフラグメントが芋぀かりたした。 おそらく、これはタむプミスであり、「m_lstControlBars」の代わりに「m_lstSliders」倉数を䜿甚する必芁がありたす。 afxpanecontainermanager.cpp 1645



ただし、アナラむザヌの譊告を読んだ埌でも、コヌド内の゚ラヌを芋぀けるのは簡単ではありたせん。 したがっお、私はそれを枛らし、重芁なものを残したす



 for (... m_lstControlBars ...) { CBasePane* pWnd = ... m_lstControlBars ... m_lstControlBars.RemoveAt(); } for (... m_lstSliders ...) { CBasePane* pWnd = ... m_lstControlBars ... m_lstSliders.RemoveAt(); }
      
      





最初のルヌプではm_lstControlBarsコンテナヌが凊理され、2番目のルヌプではm_lstSlidersコンテナヌが凊理されたす。



99の確率で、2番目のサむクルはCopy-Pasteテクノロゞヌを䜿甚しお蚘述されたした。最初のサむクルが取埗、コピヌされ、 m_lstControlBarsずいう名前がm_lstSlidersに眮き換えられたした 。 しかし、ある堎所では名前を眮き換えるのを忘れおいたした



゚ラヌは次のずおりです。CBasePane* pWnd = ... m_lstControlBars ...



それは良い間違いでしたが、次は矎しさで圌女に劣っおいたせん。 CMFCScanlinerクラスでのむンクリメント/デクリメント挔算子の実装方法を怜蚎しおください。



 class CMFCScanliner { .... inline const CMFCScanliner& operator ++ () { m_line += m_offset; return *this; } inline const CMFCScanliner& operator ++ (int) { m_line += m_offset; return *this; } inline const CMFCScanliner& operator -- () { m_line -= m_offset; return *this; } inline const CMFCScanliner& operator -- (int) { m_line += m_offset; return *this; } .... };
      
      





PVS-Studio譊告 V524 「-」関数の本䜓が「++」関数の本䜓ず完党に同等であるこずは奇劙です。 afxtoolbarimages.cpp 656



最新のステヌトメントの実装に泚意しおください。 + =から-=ぞの倉曎を忘れたした。 これは叀兞的です これが実際の「 最埌の行効果 」です



リヌクが発生する可胜性のある堎所は3぀ありたした。 それらの1぀を次に瀺したす。



 CSpinButtonCtrl* CMFCPropertyGridProperty::CreateSpinControl( CRect rectSpin) { ASSERT_VALID(this); ASSERT_VALID(m_pWndList); CSpinButtonCtrl* pWndSpin = new CMFCSpinButtonCtrl; if (!pWndSpin->Create(WS_CHILD | WS_VISIBLE | UDS_ARROWKEYS | UDS_SETBUDDYINT | UDS_NOTHOUSANDS, rectSpin, m_pWndList, AFX_PROPLIST_ID_INPLACE)) { return NULL; } .... }
      
      





PVS-Studio譊告 V773 「pWndSpin」ポむンタヌを解攟せずに関数が終了したした。 メモリリヌクが発生する可胜性がありたす。 afxpropertygridctrl.cpp 1490



Create関数が゚ラヌで実行された堎合、 pWndSpin倉数にポむンタヌが保存されおいるオブゞェクトは削陀されたせん。



同様に





C ++蚀語暙準によれば、void *型のポむンタヌで削陀挔算子を呌び出すず、未定矩の動䜜が発生したす。そしお、読者がすでに掚枬したように、これはVisual C ++ラむブラリで起こりたす。



 typedef void *PVOID; typedef PVOID PSECURITY_DESCRIPTOR; class CSecurityDescriptor { .... PSECURITY_DESCRIPTOR m_pSD; .... }; inline CSecurityDescriptor::~CSecurityDescriptor() { delete m_pSD; // <=  m_pSD   void * free(m_pOwner); free(m_pGroup); free(m_pDACL); free(m_pSACL); }
      
      





PVS-Studio譊告V772は、無効なポむンタヌに察しお「削陀」挔算子を呌び出すず、未定矩の動䜜を匕き起こしたす。atlcom.h 1039



同様の問題はここにありたす





機胜CMFCReBar :: CalcFixedLayoutは、パラメヌタの取りbStretchを、それを䜿甚しおいたせん。むしろ、最初の䜿甚の前に、bStretchは明瀺的に1を蚘述したす。



 CSize CMFCReBar::CalcFixedLayout(BOOL bStretch, BOOL bHorz) { ASSERT_VALID(this); ENSURE(::IsWindow(m_hWnd)); // the union of the band rectangles is the total bounding rect int nCount = (int) DefWindowProc(RB_GETBANDCOUNT, 0, 0); REBARBANDINFO rbBand; rbBand.cbSize = m_nReBarBandInfoSize; int nTemp; // sync up hidden state of the bands for (nTemp = nCount; nTemp--; ) { rbBand.fMask = RBBIM_CHILD|RBBIM_STYLE; VERIFY(DefWindowProc(RB_GETBANDINFO, nTemp, (LPARAM)&rbBand)); CPane* pBar = DYNAMIC_DOWNCAST( CPane, CWnd::FromHandlePermanent(rbBand.hwndChild)); BOOL bWindowVisible; if (pBar != NULL) bWindowVisible = pBar->IsVisible(); else bWindowVisible = (::GetWindowLong( rbBand.hwndChild, GWL_STYLE) & WS_VISIBLE) != 0; BOOL bBandVisible = (rbBand.fStyle & RBBS_HIDDEN) == 0; if (bWindowVisible != bBandVisible) VERIFY(DefWindowProc(RB_SHOWBAND, nTemp, bWindowVisible)); } // determine bounding rect of all visible bands CRect rectBound; rectBound.SetRectEmpty(); for (nTemp = nCount; nTemp--; ) { rbBand.fMask = RBBIM_STYLE; VERIFY(DefWindowProc(RB_GETBANDINFO, nTemp, (LPARAM)&rbBand)); if ((rbBand.fStyle & RBBS_HIDDEN) == 0) { CRect rect; VERIFY(DefWindowProc(RB_GETRECT, nTemp, (LPARAM)&rect)); rectBound |= rect; } } // add borders as part of bounding rect if (!rectBound.IsRectEmpty()) { CRect rect; rect.SetRectEmpty(); CalcInsideRect(rect, bHorz); rectBound.right -= rect.Width(); rectBound.bottom -= rect.Height(); } bStretch = 1; return CSize((bHorz && bStretch) ? 32767 : rectBound.Width(), (!bHorz && bStretch) ? 32767 : rectBound.Height()); }
      
      





PVS-Studio譊告V763パラメヌタヌ 'bStretch'は、䜿甚される前に垞に関数本䜓で曞き換えられたす。 afxrebar.cpp 209



「bStretch = 1;」ずいう行は、誰かがデバッグ甚に䜜成しお、削陀するのを忘れたように芋えたす。おそらくこれがたさに起こったこずです。開発者にこの奇劙なコヌドをチェックしお察凊するように䟝頌したす。AdjustDockingLayout



関数がCBasePaneおよびCDockSiteクラスでどのように宣蚀されおいるかを怜蚎しおください。



 class CBasePane : public CWnd { .... virtual void AdjustDockingLayout(HDWP hdwp = NULL); .... }; class CDockSite : public CBasePane { .... virtual void AdjustDockingLayout(); .... };
      
      





PVS-Studio譊告V762仮想機胜が誀っおオヌバヌラむドされた可胜性がありたす。掟生クラス「CDockSite」および基本クラス「CBasePane」の関数「AdjustDockingLayout」の最初の匕数を参照しおください。afxdocksite.h 94



基本クラスのある時点で、hdwp匕数が関数宣蚀に远加され、子孫クラスで忘れられたようです。その結果、これらは2぀の異なる機胜になりたした。



同様の状況





クラス内の関数に぀いお説明しおいるので、仮想デストラクタに぀いお、たたはそれらの䞍圚に぀いお説明したしょう。たず、CAccessTokenクラスを芋おください。



 class CAccessToken { .... mutable CRevert *m_pRevert; }; inline bool CAccessToken::ImpersonateLoggedOnUser() const throw(...) { .... delete m_pRevert; m_pRevert = _ATL_NEW CRevertToSelf; .... }
      
      





アナラむザヌは譊告を生成したす。V599「CRevert」クラスには仮想関数が含たれおいたすが、仮想デストラクタヌは存圚したせん。atlsecurity.h 5252



圌がそれをする理由を芋おみたしょう。我々は興味がメンバヌであるm_pRevert型のオブゞェクトぞのポむンタである、CRevert。クラスは倚態的に䜿甚されたす。次のコヌド行を芋るず、この結論に到達できたす。



 m_pRevert = _ATL_NEW CRevertToSelf;
      
      





クラスCRevertToSelfはから継承CRevert。これらのクラスを今すぐ知りたしょう



 class CRevert { public: virtual bool Revert() throw() = 0; }; class CRevertToSelf : public CRevert { public: bool Revert() throw() { return 0 != ::RevertToSelf(); } };
      
      





このCRevertクラスには䜕が欠けおいたすか仮想デストラクタがありたせん。



PVS-Studioアナラむザヌに新しい蚺断を远加するだけでなく、既存の蚺断を改善したす。たずえば、最近V611蚺断は、メモリの割り圓おず解攟のプロセスが異なる機胜にある堎合にメモリを解攟する問題を探すこずを孊びたした。次に、これが実際にどのように機胜するかを瀺したす。



 template <class TAccessor> class CBulkRowset : public CRowset<TAccessor> { .... void SetRows(_In_ DBROWCOUNT nRows) throw() { if (nRows == 0) nRows = 10; if (nRows != m_nRows) { delete m_phRow; m_phRow = NULL; m_nRows = nRows; } } HRESULT BindFinished() throw() { m_nCurrentRows = 0; m_nCurrentRow = 0; m_hr = S_OK; if (m_phRow == NULL) { m_phRow = _ATL_NEW HROW[m_nRows]; if (m_phRow == NULL) return E_OUTOFMEMORY; } return S_OK; } .... HROW* m_phRow; .... }
      
      





PVS-Studio 譊告V611メモリは「new T []」挔算子を䜿甚しお割り圓おられたしたが、「delete」挔算子を䜿甚しお解攟されたした。このコヌドを調べるこずを怜蚎しおください。「delete [] m_phRow;」を䜿甚するこずをお勧めしたす。atldbcli.h 5689 new []挔算子を䜿甚



しお、BindFinished関数でメモリが割り圓おられたす。



 m_phRow = _ATL_NEW HROW[m_nRows];
      
      





delete挔算子を䜿甚しおSetRows関数でメモリを解攟したす



 delete m_phRow;
      
      





結果未定矩の動䜜。memset



関数の非垞に疑わしい呌び出しに぀いお説明したしょう。ただし、誀ったコヌドを芋る前に、すべおが問題のないコヌドを芋おください。正しいコヌドは次のずおりです。







 void CToolTipCtrl::FillInToolInfo(TOOLINFO& ti, ....) const { memset(&ti, 0, sizeof(AFX_OLDTOOLINFO)); ti.cbSize = sizeof(AFX_OLDTOOLINFO); .... }
      
      





これは兞型的な状況です。memset関数を呌び出すず、構造䜓のすべおのメンバヌがクリアされたすれロで埋められたす。次に、そのサむズが構造に曞き蟌たれたす。これはWinAPIの暙準的な方法です。そのため、倚くの関数は、それらが機胜する構造のバヌゞョン圢匏を芋぀けたす。



䞊蚘のコヌドでは、すべおが論理的です。AFX_OLDTOOLINFO構造䜓のサむズが蚈算されたす。次に、構造のこのサむズを䜿甚しおmemset関数を呌び出し、同じサむズが構造内に曞き蟌たれたす。



異垞なコヌドを考えおみたしょう



 BOOL CControlBar::PreTranslateMessage(MSG* pMsg) { .... TOOLINFO ti; memset(&ti, 0, sizeof(AFX_OLDTOOLINFO)); ti.cbSize = sizeof(TOOLINFO); .... }
      
      





PVS-Studio譊告V512「memset」関数を呌び出すず、バッファヌ「ti」がアンダヌフロヌしたす。barcore.cpp 384



構造䜓はTOOLINFO型です。曞き蟌たれるTOOLINFO構造䜓のサむズです。ti.cbSize = sizeofTOOLINFO; 。



ただし、匏sizeofAFX_OLDTOOLINFOを䜿甚しおリセット可胜なバむト数が蚈算されるため、構造は完党にはリセットされず、郚分的にのみリセットされたす。



その結果、構造フィヌルドの䞀郚は初期化されないたたになりたす。memsetが構造党䜓を満たさない



別のケヌスがありたす。



 GUID m_Id; void zInternalStart() { .... // Zero the activity id in case we end up logging the stop. ZeroMemory(&m_Id, sizeof(&m_Id)); .... }
      
      





PVS-Studio譊告V512「memset」関数を呌び出すず、バッファヌ「m_Id」のアンダヌフロヌが発生したす。 traceloggingactivity.h 656



構造のサむズではなく、ポむンタヌのサむズが蚈算される堎合の叀兞的な゚ラヌ。その結果、32ビットアプリケヌションず64ビットアプリケヌションのどちらがコンパむルされおいるかに応じお、最初の4バむトたたは8バむトのみがリセットされたす。 GUIDの構造は16バむト128ビットです。



正しいオプション



 ZeroMemory(&m_Id, sizeof(m_Id));
      
      





è­Šå‘ŠV595がないわけではありたせん。この蚺断はCおよびC ++プログラムで最も䞀般的な゚ラヌの1぀を怜出するため、これは驚くこずではありたせん。ただし、Cではすべおが完璧ではありたせん。



゚ラヌの本質は、チェックされる前にポむンタヌの逆参照が発生するずいう事実にありたす。



この蚺断を䜿甚しお特定されたコヌドを怜蚎しおください。



 HRESULT CBasePane::get_accHelp(VARIANT varChild, BSTR *pszHelp) { if ((varChild.vt == VT_I4) && (varChild.lVal == CHILDID_SELF)) { *pszHelp = SysAllocString(L"ControlPane"); return S_OK; } if (((varChild.vt != VT_I4) && (varChild.lVal != CHILDID_SELF)) || (NULL == pszHelp)) { return E_INVALIDARG; } .... }
      
      





PVS-Studio譊告V595 nullpsrに察しお怜蚌される前に、「pszHelp」ポむンタヌが䜿甚されたした。行をチェックしおください1324、1328。afxbasepane.cpp 1324



関数が次のように呌び出された堎合



 VARIANT foo; foo.vt = VT_I4; foo.lVal = CHILDID_SELF; get_accHelp(foo, NULL);
      
      





この関数はE_INVALIDARGのステヌタスを返す必芁がありたすが、代わりにnullポむンタヌが逆参照されたす。



アナラむザヌは次のように掚論したした。ポむンタヌは逆参照されたす。ただし、以䞋では、このNULLポむンタヌが等しいかどうかのチェックがありたす。そのようなチェックがあるので、どうやらポむンタヌはヌルである可胜性がありたす。れロの堎合、トラブルが発生したす。ええ、譊告する必芁がありたす



私が蚀ったように、これは非垞によくある間違いです。 Visual C ++ラむブラリも䟋倖ではありたせん。リファクタリングを埅っおいるさらに17の堎所がありたすvs2017_V595.txt。



そしお、FALSE定数ずS_FALSE定数の混同に関連する最終゚ラヌを考慮しおください。



 BOOL CMFCRibbonPanel::OnSetAccData (long lVal) { .... if (nIndex < 0 || nIndex >= arElements.GetSize()) { return FALSE; } if (GetParentWnd()->GetSafeHwnd() == NULL) { return S_FALSE; } ASSERT_VALID(arElements[nIndex]); return arElements[nIndex]->SetACCData(GetParentWnd(), m_AccData); }
      
      





PVS-Studio 譊告V716 returnステヌトメントでの疑わしい型倉換HRESULTを返したしたが、関数は実際にはBOOLを返したす。afxribbonpanel.cpp4107。



この関数はBOOL型を返したす。芪りィンドりからHWNDを取埗できない堎合、プログラマは関数からFALSE倀を返したいず考えおいたした。しかし、圌は封印し、すべおを根本的に倉えるS_FALSEを曞きたした。



これは、S_FALSE定数の宣蚀方法です。



 #define S_FALSE ((HRESULT)1L)
      
      





䜕が起こっおいるのかはすでに理解しおいるず思いたすが、念のために説明したす。



「return S_FALSE;」ず曞くこずは、「return TRUE;」ず曞くこずず同じです。壮倧な倱敗。



゚ラヌは䞀人ではなく、圌女には友人がいたす







蚘事の冒頭で述べたように、すべおのファむルをチェックしたせんでした。さらに、アナラむザヌによっお発行された譊告の䞭から䜕かを芋萜ずしおいるかもしれたせん。したがっお、このドキュメントの開発者をいく぀かの゚ラヌを修正するためのガむドず芋なさないようにしおください。開発者自身がラむブラリを完党にチェックし、アナラむザヌの譊告を泚意深く調べるず、はるかに優れおいたす。



おわりに



写真4









もう䞀床、静的コヌド分析ツヌルの利点を読者に瀺すこずができたした。



䞀぀の間違いに察しお譊告したい。時々、リリヌスに備えお䞀郚のプログラマヌが静的アナラむザヌを起動するず聞きたす。誰かがこれを行い、これが正垞であるず䞻匵する堎合、圌は深く誀解されおいたす。これは、静的アナラむザヌを䜿甚する間違った方法です。 これは、リリヌス前にコンパむラの譊告をオンにしお、残りの時間はプロゞェクトで䜜業し、それらを完党に無効にするこずず同じです。



PVS-Studioアナラむザヌのデモ版をむンストヌルし、プロゞェクトを確認しおみおください。



PVS-Studio補品ペヌゞhttps : //www.viva64.com/en/pvs-studio/



サポヌトされおいる蚀語ずコンパむラ





読んでくれおありがずう、Twitter @Code_Analysisでフォロヌしおください。







この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいAndrey Karpov。 PVS-Studioを䜿甚しおVisual C ++ 2017ラむブラリを改善する方法



蚘事を読んで質問がありたすか
倚くの堎合、蚘事には同じ質問が寄せられたす。 ここで回答を収集したした PVS-Studioバヌゞョン2015に関する蚘事の読者からの質問ぞの回答 。 リストをご芧ください。



All Articles