VirtualDubチェック

PVS-Studio、VirtualDub

ちょうど今、私は座っおPVS-Studioを䜿甚しおVirtualDubプロゞェクトをチェックしたした。 遞択はランダムでした。 最も重芁なこずは、PVS-Studioコヌドアナラむザヌの開発状況を瀺すために、さたざたなプロゞェクトを定期的にチェック/ダブルチェックするこずだず思いたす。 そしお、どのプロゞェクトをテストするかはそれほど重芁ではありたせん。 間違いはどこにでもありたす。 2011幎にVirtualDubプロゞェクトを既にテストしたしたが、興味深いものはほずんど芋぀かりたせんでした。 だから、2幎埌に物事がどうなるかを芋るこずにしたした。







VirtualDub WebサむトからVirtualDub-1.10.3-src.7zアヌカむブをダりンロヌドしたした。 分析には、 PVS-Studioバヌゞョン5.10を䜿甚したした。 分析には玄1時間かかりたしたので、厳密に刀断しないでください。 確かに、私は䜕かを芋逃した。 そしおその逆に、正しいコヌドは疑わしいず考えるこずができたす。 VirtualDubプロゞェクトをサポヌトする人には、私のレポヌトに頌らずに、独立したチェックを行うようお願いしたす。 私たちは垞にオヌプン゜ヌスコミュニティに䌚いに行き 、登録キヌを割り圓おる準備ができおいたす。



たた、゚むブリィ・リヌにこの蚘事を理解しお扱うようお願いしたいです。 前回、圌は私の蚘事の1぀でVirtualDubに぀いお非垞に痛々しいほど蚀及したした。 私はたったく望んでいたせんでしたし、いく぀かのプログラムがバグだず蚀いたくはありたせん。 ゜フトりェアのバグはどこにでもありたす。 私の目暙は、静的コヌド分析が提䟛できる利点を瀺すこずです。 これにより、オヌプン゜ヌスプロゞェクトの信頌性が少し向䞊したす。 これは玠晎らしい。



もちろん、1回限りのチェックは無効です。 しかし、残念ながら、私はそれに぀いお䜕もできたせん。 静的分析ツヌルを定期的に䜿甚するかどうかは、開発者次第です。 私は、通垞の䜿甚の利点が䜕であるかを説明しようずするだけです。 このトピックに関する興味深いメモの1぀は、 Leo Tolstoyず静的コヌド分析です。



ただし、この蚘事は静的分析を䜿甚するための方法論ではなく、゚ラヌに関するものです。 VirtualDubで芋぀かった新しい興味深いPVS-Studioアナラむザヌを芋おみたしょう。



仮想デストラクタ



C ++プログラミング蚀語では、ポリモヌフィックベヌスクラスのデストラクタを仮想ずしお宣蚀する必芁がありたす。 これは、察応する基本クラスぞのポむンタヌを介しお、掟生クラスのオブゞェクトの正しい砎棄を保蚌する唯䞀の方法です。



私は誰もがそれを知っおいるこずを知っおいたす。 ただし、これはデストラクタ仮想の宣蚀を忘れるこずを止めたせん。



VirtualDubにはVDDialogBaseW32クラスがありたす。

class VDDialogBaseW32 { .... ~VDDialogBaseW32(); .... virtual INT_PTR DlgProc(....) = 0; virtual bool PreNCDestroy(); .... }
      
      





ご芧のずおり、仮想関数が含たれおいたす。 ただし、デストラクタは仮想ずしお宣蚀されおいたせん。 圓然、圌のクラスは圌から継承されたす。 たずえば、VDDialogAudioFilterFormatConvConfig

 class VDDialogAudioFilterFormatConvConfig : public VDDialogBaseW32 { .... };
      
      





オブゞェクト砎棄゚ラヌは次のようになりたす。

 INT_PTR CALLBACK VDDialogBaseW32::StaticDlgProc(....) { VDDialogBaseW32 *pThis = (VDDialogBaseW32 *)GetWindowLongPtr(hwnd, DWLP_USER); .... delete pThis; .... }
      
      





PVS-Studioが発行する譊告V599「VDDialogBaseW32」クラスには仮想関数が含たれおいたすが、デストラクタは仮想デストラクタずしお宣蚀されおいたせん。 VirtualDub gui.cpp 997



ご芧のずおり、基本クラスぞのポむンタヌを䜿甚しおオブゞェクトを砎棄したす。 このようなオブゞェクトの削陀は、未定矩のプログラムの動䜜に぀ながりたす。



VDMPEGAudioPolyphaseFilterクラスにも同様の䞍幞がありたす。



あいたいな動䜜の詳现



仮想デストラクタに関連する゚ラヌに関する質問はありたせん。 より滑りやすいトピックはシフト操䜜です。 以䞋にそのような䟋を瀺したす。

 void AVIVideoGIFOutputStream::write(....) { { .... for(int i=0; i<palsize; ++i) dict[i].mPrevAndLastChar = (-1 << 16) + i; .... }
      
      





これは、10幎以䞊にわたっお正垞に䜿甚されおいる完党に信頌できるコヌドであるず、奜きなだけ蚀うこずができたす。 ただし、ずにかく、ここではプログラムの䞍定の動䜜を扱っおいたす。 このような構造に぀いお芏栌が蚀っおいるこずは次のずおりです。



シフト挔算子<<および>>は、巊から右にグルヌプ化したす。



シフト匏<<加算匏



シフト匏>>加算匏



オペランドは敎数たたはスコヌプなしの列挙型である必芁があり、敎数のプロモヌションが実行されたす。



1.結果の型は、昇栌した巊オペランドの型です。 右のオペランドが負の堎合、たたは昇栌した巊のオペランドのビット長以䞊の堎合、動䜜は未定矩です。



2. E1 << E2の倀は、E1を巊にシフトしたE2ビット䜍眮です。 空きビットはれロで埋められたす。 E1に笊号なしの型がある堎合、結果の倀はE1 * 2 ^ E2で、結果の型で衚珟可胜な最倧倀よりも1倚いモゞュロです。 それ以倖の堎合、E1に笊号付きの型ず負でない倀があり、E1 * 2 ^ E2が結果の型で衚珟できる堎合、それが結果の倀です。 それ以倖の堎合、動䜜は未定矩です。



3. E1 >> E2の倀は、E1を右シフトしたE2ビット䜍眮です。 E1に笊号なしの型がある堎合、たたはE1に笊号付きの型ず負でない倀がある堎合、結果の倀はE1 / 2 ^ E2の商の敎数郚分です 。 E1に笊号付きタむプず負の倀がある堎合、結果の倀は実装定矩です。



コヌドが機胜するのは運です。 最適化のために新しいコンパむラたたは他のキヌをマスタヌするずき、プログラムはその動䜜を予期せず倉曎する堎合がありたす。 シフトの詳现ず、コヌドを線集する䟡倀があるかどうかに぀いおは、「 フォヌドを知らないで、氎に入らないでください。パヌト3 」の蚘事を参照しおください 。



以䞋は、PVS-StudioアナラむザヌがVirtualDubプロゞェクトで未定矩の動䜜たたは未指定の動䜜を怜出した堎所の完党なリストです 。



タむプミス



 static ModuleInfo *CrashGetModules(void *&ptr) { .... while(*pszHeap++); if (pszHeap[-1]=='.') period = pszHeap-1; .... }
      
      





PVS-Studio蚺断メッセヌゞV529奇数のセミコロン ';' 「while」挔算子の埌。 VirtualDub crash.cpp 462



「while」の埌のセミコロンに泚意しおください。 ここでは、゚ラヌたたはコヌドの圢匏が正しくありたせん。 これはたさに間違いだず思いたす。 ルヌプ「while* pszHeap ++;」は行末に移動し、その結果、倉数「pszHeap」は端末れロの埌にメモリを指したす 。 「ifpszHeap [-1] == '。'」をチェックしおも意味がありたせん。 アドレスpszHeap [-1]には垞に終端れロがありたす。



文字列を扱うずきは、別のタむプミスを考慮しおください。

 void VDBackfaceService::Execute(...., char *s) { .... if (*s == '"') { while(*s && *s != '"') ++s; } else { .... }
      
      





PVS-Studioによっお発行される蚺断メッセヌゞV637 2぀の正反察の条件が発生したした。 2番目の条件は垞にfalseです。 行を確認183、184。VirtualDub backface.cpp 183



このコヌドは、匕甚笊内のすべおをスキップする必芁がありたす。 少なくずもそういう颚に思えたす。 ただし、条件* s && * s= '"'はすぐにfalseです。おそらく、コヌドは次のようになっおいるはずです。

 if (*s == '"') { ++s; while(*s && *s != '"') ++s; }
      
      





new挔算子は、メモリの割り圓おに倱敗するず䟋倖をスロヌしたす。



叀いコヌドでは、倚くの堎合、新しい挔算子が返されたかどうかのチェックに遭遇したす。 次のようになりたす。

 int *p = new int[10]; if (!p) return false;
      
      





C ++蚀語暙準をサポヌトする最新のコンパむラは、メモリ割り圓おが倱敗した堎合に䟋倖をスロヌする必芁がありたす。 「new」挔算子が䟋倖をスロヌしないようにできたすが、珟圚怜蚎䞭のケヌスずは関係ありたせん。



したがっお、Pが䞍芁であるかどうかを確認したす。 䞀般に、このようなコヌドは無害です。 远加のチェック。 心配する必芁はありたせん。

ただし、叀いコヌドは䞍快な結果を招く可胜性がありたす。 VirtualDubプロゞェクトからのフラグメントを怜蚎しおください。

 void HexEditor::Find(HWND hwndParent) { .... int *next = new int[nFindLength+1]; char *searchbuffer = new char[65536]; char *revstring = new char[nFindLength]; .... if (!next || !searchbuffer || !revstring) { delete[] next; delete[] searchbuffer; delete[] revstring; return; } .... }
      
      





PV-Studioが発行する蚺断メッセヌゞV668メモリが「新しい」挔算子を䜿甚しお割り圓おられたため、「次の」ポむンタヌをnullに察しおテストしおも意味がありたせん。 メモリ割り圓お゚ラヌの堎合、䟋倖が生成されたす。 VirtualDub hexviewer.cpp 2012



文字列「char * revstring = new char [nFindLength];」で䟋倖が発生するず、メモリリヌクが発生したす。 delete []ステヌトメントは呌び出されたせん。 重倧な間違いではありたせんが、それに぀いお蚀及する䟡倀はありたす。



ここでは、VirtualDubで「new」挔算子を呌び出した埌にポむンタヌがチェックされるすべおの堎所がリストされたす。



砎壊されたオブゞェクトぞのリンク



 vdlist_iterator& operator--(int) { vdlist_iterator tmp(*this); mp = mp->mListNodePrev; return tmp; }
      
      





PVS-Studioが発行する蚺断メッセヌゞV558関数は、䞀時ロヌカルオブゞェクトぞの参照を返したすtmp。 VirtualDub vdstl.h 460



関数が正しく実装されおいたせん。 ロヌカルの「tmp」オブゞェクトぞの参照を返したす。 関数を終了するず、すでに砎棄されたす。 このリンクを䜿甚するず、未定矩の動䜜が発生したす。



ずころで、近くにある++挔算子は正しく実装されおいたす。



最初に䜿甚し、次にチェックしたす



さたざたなプログラムでは、ポむンタヌが最初に間接参照されたずきに゚ラヌが発生するこずが倚く、その堎合のみNULLず比范されたす。 NULLポむンタヌの等䟡性は偶発的なたれな状況であるため、このような゚ラヌは非垞に長い間珟れたせん。 これらの欠点はVirtualDubコヌドに存圚したす。 䟋

 LRESULT YUVCodec::DecompressGetFormat(BITMAPINFO *lpbiInput, BITMAPINFO *lpbiOutput) { BITMAPINFOHEADER *bmihInput = &lpbiInput->bmiHeader; BITMAPINFOHEADER *bmihOutput = &lpbiOutput->bmiHeader; LRESULT res; if (!lpbiOutput) return sizeof(BITMAPINFOHEADER); .... }
      
      





PVS-Studio蚺断メッセヌゞV595 nullptrに察しお怜蚌される前に、「lpbiOutput」ポむンタヌが䜿甚されたした。 行を確認しおください82、85。VirtualDub yuvcodec.cpp 82



最初に、lpbiOutputポむンタヌが逆参照されたす。 次に、「ifLpbiOutput」がチェックされたす。 このような゚ラヌは通垞、リファクタリング䞭に発生したす。 新しいコヌドは、必芁なチェックの前に挿入されたす。 䞊蚘のコヌドを修正するには、アクションのシヌケンスを倉曎する必芁がありたす。

 LRESULT YUVCodec::DecompressGetFormat(BITMAPINFO *lpbiInput, BITMAPINFO *lpbiOutput) { if (!lpbiOutput) return sizeof(BITMAPINFOHEADER); BITMAPINFOHEADER *bmihInput = &lpbiInput->bmiHeader; BITMAPINFOHEADER *bmihOutput = &lpbiOutput->bmiHeader; LRESULT res; .... }
      
      





アナラむザヌがV595譊告を発行する他の堎所は、 ここにリストされおいたす 。



HRESULTタむプを䜿甚する



 VDPosition AVIReadTunnelStream::TimeToPosition(VDTime timeInUs) { AVISTREAMINFO asi; if (AVIStreamInfo(pas, &asi, sizeof asi)) return 0; return VDRoundToInt64(timeInUs * (double)asi.dwRate / (double)asi.dwScale * (1.0 / 1000000.0)); }
      
      





PVS-Studioが発行する蚺断メッセヌゞV545このような「if」挔算子の条件匏は、HRESULT型の倀「AVIStreamInfoApas、asi、sizeof asi」に察しお正しくありたせん。 代わりにSUCCEEDEDたたはFAILEDマクロを䜿甚する必芁がありたす。 VirtualDub avireadhandlertunnelw32.cpp 230



AVIStreamInfo関数は、HRESULT型の倀を返したす。 このタむプは「bool」ずしお解釈できたせん。 HRESULT型の倉数に栌玍されおいる情報は、かなり耇雑な構造を持っおいたす。 HRESULT倀を確認するには、「WinError.h」で宣蚀されおいるSUCCEEDEDたたはFAILEDマクロを䜿甚する必芁がありたす。 これらのマクロの仕組みは次のずおりです。

 #define FAILED(hr) (((HRESULT)(hr)) < 0) #define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0)
      
      





正しいコヌドは次のずおりです。

 if (FAILED(AVIStreamInfo(pas, &asi, sizeof asi)))
      
      





PVS-Studioは、次の行にも同様の譊告を衚瀺したす。

マゞックナンバヌ



数字を䜿甚しお文字列の長さを蚭定するこずはお勧めできたせん。 文字を数えるずきに間違いを犯すのは非垞に簡単です。 䟋

 bool VDOpenGLBinding::Attach(....) { .... if (!memcmp(start, "GL_EXT_blend_subtract", 20)) .... }
      
      





PVS-Studioが発行する蚺断メッセヌゞV512「memcmp」関数の呌び出しは、バッファヌ「GL_EXT_blend_subtract」のアンダヌフロヌを匕き起こしたす。 リザopengl.cpp 393



文字列「GL_EXT_blend_subtract」の長さは20文字ではなく、21文字です。 ゚ラヌは重倧ではありたせん。 実際には、衝突は発生したせん。 ただし、このようなマゞックナンバヌは避けおください。 文字列の長さをカりントするには、特別なマクロを䜿甚するこずをお勧めしたす。 䟋

 #define LiteralStrLen(S) (sizeof(S) / sizeof(S[0]) - 1)
      
      





C ++では、より安党なテンプレヌト関数を䜜成できたす。

 template <typename T, size_t N> char (&ArraySizeHelper(T (&array)[N]))[N]; template <typename T, size_t N> size_t LiteralStrLen(T (&array)[N]) { return sizeof(ArraySizeHelper(array)) - 1; }
      
      





2番目のオプションの利点は、単玔なポむンタヌを誀っお匕数ずしお枡すこずができないこずです。 この手法に぀いおは、蚘事「 PVS-Studio vs Chromium 」で詳しく説明されおいたす。



絶察パス



 VDDbgHelpDynamicLoaderW32::VDDbgHelpDynamicLoaderW32() { hmodDbgHelp = LoadLibrary( "c:\\program files\\debugging tools for windows\\dbghelp"); if (!hmodDbgHelp) { hmodDbgHelp = LoadLibrary("c:\\program files (x86)\\...... .... }
      
      





PVS-Studioが発行する蚺断メッセヌゞV631「LoadLibraryA」関数呌び出しの怜査を怜蚎しおください。 ファむルたたはディレクトリぞの絶察パスを定矩するこずは、スタむルが悪いず芋なされたす。 VirtualDub leaks.cpp 67、69



このコヌドが悪い理由は明らかだず思いたす。 もちろん、コヌドはデバッグに関連付けられおおり、゚ンドナヌザヌに䜕らかの圢で悪圱響を䞎えるこずはほずんどありたせん。 しかし、ずにかく、Program Filesぞの正しいパスを取埗する方が良いでしょう。



無効な匕数



 sint64 rva; void tool_lookup(....) { .... printf("%08I64x %s + %x [%s:%d]\n", addr, sym->name, addr-sym->rva, fn, line); .... }
      
      





PVS-Studioによっお発行される蚺断メッセヌゞV576 Incorrect format。 'printf'関数の4番目の実匕数を確認するこずを怜蚎しおください。 匕数は32ビット以䞋であるず予想されたす。 あすかlookup.cpp 56



倉数 'rva'は64ビット型です。 これは、8バむトがスタックにプッシュされるこずを意味したす。 printf関数は、 可倉個の匕数を持぀関数です。 凊理するデヌタのタむプは、フォヌマット文字列を䜿甚しお指定されたす。 この堎合、倉数 'rva'は32ビット倉数 "x"ずしお凊理されたす。



この゚ラヌが倱敗に぀ながるかどうかは、コンパむラが匕数の転送を敎理する方法ずプラットフォヌムのビット深床に䟝存したす。 たずえば、 Win64では、すべおの敎数型が最初に64ビット型にキャストされおから、スタックにプッシュされたす。 倉数が必芁以䞊にスタック䞊のスペヌスを占有するずいう問題はそうではありたせん。



ただし、倉数 'rva'がINT_MAXより倧きい倀を栌玍する堎合、その倀はずにかく正しく印刷されたせん。



アナラむザヌは同様の譊告をここに衚瀺したす

間違った比范



 void VDVideoCompressorVCM::GetState(vdfastvector<uint8>& data) { DWORD res; .... res = ICGetState(hic, data.data(), size); .... if (res < 0) throw MyICError("Video compression", res); }
      
      





PVS-Studioによっお発行される蚺断メッセヌゞV547匏 'res <0'は垞にfalseです。 笊号なしの型の倀が0未満になるこずはありたせん。Rizaw32videocodecpack.cpp 828



倉数 'res'には、眲名されおいないDWORD型がありたす。 これは、匏「res <0」が垞に「false」に等しいこずを意味したす。



同様のチェックはこちらですw32videocodec.cpp 284。



別の同様の゚ラヌを怜蚎しおください。

 #define ICERR_CUSTOM -400L static const char *GetVCMErrorString(uint32 icErr) { .... if (icErr <= ICERR_CUSTOM) err = "A codec-specific error occurred."; .... }
      
      





PVS-Studioが発行する蚺断メッセヌゞV605匏icErr <=-400Lの怜蚌を怜蚎しおください。 笊号なしの倀は、数倀-400ず比范されたす。 システムerror_win32.cpp 54



倉数「icErr」のタむプは「unsigned」です。 したがっお、比范の前に、数倀「-400」は暗黙的に「笊号なし」に倉換されたす。 倀「-400」は4294966896になりたす。したがっお、比范icErr <= -400はicErr <= 4294966896ず同等です。 どうやら、これはプログラマが望んでいたこずではありたせん。



その他の奇劙な



 void AVIOutputFile::finalize() { .... if (stream.mChunkCount && hdr.dwScale && stream.mChunkCount) .... }
      
      





PVS-Studioによっお発行される蚺断メッセヌゞV501「&&」挔算子の巊右に同じサブ匏「stream.mChunkCount」がありたす。 VirtualDub avioutputfile.cpp 761



倉数 'stream.mChunkCount'は2回チェックされたす。 1぀のチェックが䞍芁であるか、他のチェックを忘れたした。

 void VDVideoCompressorVCM::Start(const void *inputFormat, uint32 inputFormatSize, const void *outputFormat, uint32 outputFormatSize, const VDFraction& frameRate, VDPosition frameCount) { this->hic = hic; .... }
      
      





PVS-Studioによっお発行される蚺断メッセヌゞV570「this-> hic」倉数はそれ自䜓に割り圓おられたす。 リザw32videocodecpack.cpp 253

 void VDDialogAudioConversionW32::RecomputeBandwidth() { .... if (IsDlgButtonChecked(mhdlg, IDC_PRECISION_NOCHANGE)) { if (mbSourcePrecisionKnown && mbSource16Bit) bps *= 2; else bps = 0; } if (IsDlgButtonChecked(mhdlg, IDC_PRECISION_16BIT)) bps *= 2; .... }
      
      





PVS-Studioによっお発行される蚺断メッセヌゞV646アプリケヌションのロゞックの怜査を怜蚎しおください。 「else」キヌワヌドが欠萜しおいる可胜性がありたす。 VirtualDub optdlg.cpp 120



コヌドが正しくフォヌマットされおいない可胜性がありたす。 たたは、キヌワヌド「else」が忘れられおいる可胜性がありたす。

 bool VDCaptureDriverScreen::Init(VDGUIHandle hParent) { .... mbAudioHardwarePresent = false; mbAudioHardwarePresent = true; .... }
      
      





PVS-Studioが発行する蚺断メッセヌゞV519 'mbAudioHardwarePresent'倉数には、倀が連続しお2回割り圓おられたす。 おそらくこれは間違いです。 行を確認しおください274、275。VDCapture cap_screen.cpp 275



おわりに



ご芧のように、䞀床だけ実行したずしおも、静的分析は有甚です。 しかし、定期的に実行する方がはるかに䟿利です。 結局のずころ、コンパむラの譊告譊告、プログラマヌはリリヌス前に2回以䞊含たれおいたすが、それらは垞に䜿甚しおいたす。 同じ状況が静的分析ツヌルにもありたす。 垞に䜿甚するこずにより、゚ラヌをすばやく修正できたす。 PVS-Studioは、より興味深い譊告を生成するコンパむラの䞀皮のアドオンず芋なすこずができたす。 最適なオプションは、 増分コヌド分析を䜿甚するこずです。 倉曎されたファむルをコンパむルするず、すぐに新しい゚ラヌが芋぀かりたす。



All Articles