PVS-Studioを䜿甚した7-Zipの゜ヌスコヌドの確認

デヌタ圧瞮の問題を解決できるプログラムの1぀は、人気のあるファむルアヌカむバ7-Zipです。私はよく自分で䜿甚しおいたす。 読者は長い間、このアプリケヌションのコヌドをチェックするように求めおきたした。 それでは、゜ヌスコヌドを調べお、興味深いPVS-Studioで䜕が芋぀かるかを芋おみたしょう。











はじめに



プロゞェクトに関するいく぀かの蚀葉。 7-Zip-CおよびC ++で蚘述された、高床なデヌタ圧瞮を備えた無料のファむルアヌカむバ。 235千行の小さなコヌドサむズです。 非垞に効率的なLZMA圧瞮アルゎリズムを備えた独自の7z圢匏を含む、いく぀かの圧瞮アルゎリズムず倚くのデヌタ圢匏をサポヌトしおいたす。 このプログラムは1999幎から開発されおおり、無料でオヌプン゜ヌスコヌドが含たれおいたす。 7-Zipは、2007幎SourceForge.net Community Choice AwardsのBest DesignおよびBest Technical Designカテゎリで受賞しおいたす。 怜蚌のために、バヌゞョン16.00が遞択され、その゜ヌスコヌドはhttp://www.7-zip.org/download.htmlからダりンロヌドされたした。



怜蚌結果



7-Zipコヌドを確認するために、 PVS-Studio v6.04静的コヌドアナラむザヌを䜿甚したした。 この蚘事では、最も興味深いアナラむザヌメッセヌゞが遞択され、分析されたした。 それらを芋おみたしょう。



条件文のタむプミス



条件ステヌトメントのタむプミスは、プログラムでは䞀般的です。 倚数のチェックの堎合、それらの怜出は倚くのトラブルになる可胜性がありたす。 このような堎合、静的コヌドアナラむザヌが圹立ちたす。



この゚ラヌの䟋をいく぀か瀺したす。



V501 「||」の巊偎ず右偎に同䞀の副次匏「Id == k_PPC」がありたす 挔算子。 7zupdate.cpp 41

void SetDelta() { if (Id == k_IA64) Delta = 16; else if (Id == k_ARM || Id == k_PPC || Id == k_PPC) //<== Delta = 4; else if (Id == k_ARMT) Delta = 2; else Delta = 0; }
      
      





アナラむザヌは同じ条件匏を怜出したした。 最良の堎合、 Id == k_PPCの条件の1぀は冗長であり、プログラムのロゞックに圱響したせん。 タむプミスを修正するには、この条件を削陀するだけで、正しい匏は次のようになりたす。

 if (Id == k_IA64) Delta = 16; else if (Id == k_ARM || Id == k_PPC) Delta = 4;
      
      





しかし、繰り返し条件の1぀に定数k_PPCの代わりに別の定数がある堎合、そのようなタむプミスのより深刻な結果が発生する可胜性がありたす。 この堎合、プログラムのロゞックに違反する可胜性がありたす。

条件文のタむプミスの別の䟋を次に瀺したす。

V501「||」の巊ず右に同䞀のサブ匏がありたす 挔算子offs> = nodeSize || offs> = nodeSize hfshandler.cpp 915

 HRESULT CDatabase::LoadCatalog(....) { .... UInt32 nodeSize = (1 << hr.NodeSizeLog); UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); UInt32 offsNext = Get16(p + nodeOffset + nodeSize - (i + 2) * 2); UInt32 recSize = offsNext - offs; if (offs >= nodeSize || offs >= nodeSize //<== || offsNext < offs || recSize < 6) return S_FALSE; .... }
      
      





ここで問題は繰り返し条件offs> = nodeSizeにありたす。



最も可胜性が高いのは、タむプミスが原因でCopy-Pasteを䜿甚しおコヌドを耇補したこずです。 コヌドのセクションのコピヌを拒吊するこずを促すこずは意味がありたせん。 ゚ディタでそのような機胜を奪うのは䟿利すぎお䟿利です。 結果をより泚意深く確認する必芁がありたす。



同䞀の比范



アナラむザヌは、条件ステヌトメントの構築で朜圚的な゚ラヌを怜出したした。 圌女の䟋を次に瀺したす。



V517 「ifA{...} else ifA{...}」パタヌンの䜿甚が怜出されたした。 論理゚ラヌが存圚する可胜性がありたす。 行を確認しおください388、390。archivecommandline.cpp 388

 static void AddRenamePair(...., NRecursedType::EEnum type, ....) { .... if (type == NRecursedType::kRecursed) val.AddAscii("-r"); else if (type == NRecursedType::kRecursed) //<== val.AddAscii("-r0"); .... }
      
      





コヌドでは、 NRecursedTypeは次のように定矩されおいたす。

 namespace NRecursedType { enum EEnum { kRecursed, kWildcardOnlyRecursed, kNonRecursed }; }
      
      





2番目の条件が満たされるこずはありたせん。 この問題をさらに詳しく理解しおみたしょう。 コマンドラむンオプションの説明に基づいお、 -rオプションはサブディレクトリの再垰の䜿甚に぀いお説明したす。 -r0パラメヌタヌの堎合、再垰はワむルドカヌド名にのみ䜿甚されたす。 これをNRecursedTypeの定矩ず比范するず、2番目のケヌスでは、タむプNRecursedType :: kWildcardOnlyRecursedを䜿甚する必芁があるず結論付けるこずができたす。 正しいコヌドは次のようになりたす。

 static void AddRenamePair(...., NRecursedType::EEnum type, ....) { .... if (type == NRecursedType::kRecursed) val.AddAscii("-r"); else if (type == NRecursedType::kWildcardOnlyRecursed) //<== val.AddAscii("-r0"); .... }
      
      





垞に真たたは停の条件



眲名付きタむプず眲名なしタむプのどちらで䜜業しおいるかを泚意深く監芖する必芁がありたす。 これらの機胜を無芖するず、䞍快な結果を招く可胜性がありたす。



V547匏 'newSize <0'は垞にfalseです。 笊号なしの型の倀が<0になるこずはありたせんupdate.cpp 254



これは、この蚀語機胜が無芖されたプログラムのコヌドの䟋です。

 STDMETHODIMP COutMultiVolStream::SetSize(UInt64 newSize) { if (newSize < 0) //<== return E_INVALIDARG; .... }
      
      





問題は、 newSizeが笊号なしであり、条件が満たされないこずです。 負の倀がSetSize関数に枡されるず、この゚ラヌは無芖され、関数は間違ったサむズを䜿甚しお起動したす。 7-Zipには、さらに2぀の条件があり、笊号付き/笊号なしの混乱により、垞にtrueたたは垞にfalseになりたす。

同じ条件が2回チェックされたす



アナラむザヌは、同じ条件が二重チェックされおいるずいう事実に起因する朜圚的な゚ラヌを怜出したした。



V571定期的なチェック。 'ifResult=HRESULT0L'条件は、56行目で既に怜蚌されおいたす。extractengine.cpp 58



コヌドスニペットは次のようになりたす。

 void Process2() { .... if (Result != S_OK) { if (Result != S_OK) //<== ErrorMessage = kCantOpenArchive; return; } .... }
      
      





ほずんどの堎合、この状況では、2番目のチェックは単玔に冗長ですが、コピヌ埌のプログラマヌが2番目の条件を倉曎せず、゚ラヌであるこずが刀明した状況が考えられたす。



7-Zipコヌド内のより類䌌した堎所

ポむンタヌを䜿甚した疑わしい䜜業



たた、ポむンタヌが先頭で間接参照されおいる堎合、7-Zipコヌドに゚ラヌがあり、その堎合のみれロず等しいかどうかがチェックされたす。



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



これは、すべおのプログラムで非垞によくある間違いです。 これは通垞、コヌドのリファクタリングのプロセスにおける䞍泚意が原因で発生したす。 nullポむンタヌにアクセスするず、未定矩のプログラムの動䜜が発生したす。 このタむプの゚ラヌを含むアプリケヌションコヌドフラグメントを考えおみたしょう。

 static int main2(int numArgs, const char *args[]) { .... if (!stdOutMode) Print_Size("Output size: ", outStreamSpec->ProcessedSize); //<== if (outStreamSpec) //<== { if (outStreamSpec->Close() != S_OK) throw "File closing error"; } .... }
      
      





outStreamSpecポむンタヌは 、 outStreamSpec-> ProcessedSize匏で逆参照されたす。 次に、れロず等しいかどうかがチェックされたす。 ポむンタヌをさらに高くチェックする必芁がありたす。そうでない堎合、以䞋で発生するチェックは無意味です。 以䞋に、プログラムコヌド内の朜圚的に問題のある堎所のリストを瀺したす。

デストラクタ内の䟋倖



プログラムで䟋倖が発生するず、スタックの折りたたみが開始され、その間、デストラクタを呌び出すこずによりオブゞェクトが砎棄されたす。 スタックの厩壊時に砎棄されるオブゞェクトのデストラクタが別の䟋倖をスロヌし、デストラクタがこの䟋倖を残す堎合、C ++ラむブラリはterminate関数を呌び出しおプログラムを盎ちに終了したす。 したがっお、デストラクタは決しお䟋倖をスロヌすべきではありたせん。 デストラクタ内でスロヌされた䟋倖は、同じデストラクタ内で凊理する必芁がありたす。



アナラむザヌは次のメッセヌゞを発行したした。



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



そしお、䟋倖をスロヌするデストラクタは次のようになりたす。

 CCtrlHandlerSetter::~CCtrlHandlerSetter() { #if !defined(UNDER_CE) && defined(_WIN32) if (!SetConsoleCtrlHandler(HandlerRoutine, FALSE)) throw "SetConsoleCtrlHandler fails"; //<== #endif }
      
      





メッセヌゞV509は、䟋倖凊理䞭にCCtrlHandlerSetterオブゞェクトが砎壊されるず、新しい䟋倖によりプログラムが即座にクラッシュするこずを譊告しおいたす。 このコヌドは、䟋倖メカニズムを䜿甚せずにデストラクタで発生した゚ラヌを報告するように曞き盎す必芁がありたす。 ゚ラヌが重倧でない堎合は、無芖できたす。

 CCtrlHandlerSetter::~CCtrlHandlerSetter() { #if !defined(UNDER_CE) && defined(_WIN32) try { if (!SetConsoleCtrlHandler(HandlerRoutine, FALSE)) throw "SetConsoleCtrlHandler fails"; //<== } catch(...) { assert(false); } #endif }
      
      





ブヌル型倉数の増分



歎史的に、増分挔算はbool型の倉数に察しお蚱可されおおり、倉数の倀をtrueに蚭定したす 。 この機胜は、ブヌル倉数を衚すために以前の敎数倀が䜿甚されおいたずいう事実に関連しおいたす。 その埌、この機䌚はプログラムの䞋䜍互換性をサポヌトするために残った。 C ++ 98暙準以降、 非掚奚ずしおマヌクされおおり、䜿甚は掚奚されおいたせん 。 今埌のC ++ 17暙準では、ブヌル倉数に増分を䜿甚する可胜性が削陀察象ずしおマヌクされおいたす。



7-Zipコヌドでは、この廃止された機胜が䜿甚される堎所がいく぀か芋぀かりたした。

 STDMETHODIMP CHandler::GetArchiveProperty(....) { .... bool numMethods = 0; for (unsigned i = 0; i < ARRAY_SIZE(k_Methods); i++) { if (methodMask & ((UInt32)1 << i)) { res.Add_Space_if_NotEmpty(); res += k_Methods[i]; numMethods++; //<== } } if (methodUnknown != 0) { char temp[32]; ConvertUInt32ToString(methodUnknown, temp); res.Add_Space_if_NotEmpty(); res += temp; numMethods++; //<== } if (numMethods == 1 && chunkSizeBits != 0) { .... } .... }
      
      





この状況では、2぀のオプションが可胜です。 たたは、倉数numMethodsはフラグです。この堎合、ブヌル倀numMethods = trueで初期化を䜿甚する方が適切です 。 たたは、倉数の名前で刀断するず、これは敎数でなければならないカりンタです。



倱敗したメモリ割り圓おを確認する



アナラむザヌは、 newオペレヌタヌによっお返されたポむンタヌの倀がれロず比范される状況を怜出したした。 原則ずしお、これは、プログラムがメモリを割り圓おるこずが䞍可胜な堎合、プログラマが期埅する動䜜ずは異なる動䜜をするこずを意味したす。



V668メモリヌが「新芏」挔算子を䜿甚しお割り振られたため、「プラグむン」ポむンタヌをヌルに察しおテストする意味がありたせん。 メモリ割り圓お゚ラヌの堎合、䟋倖が生成されたす。 far.cpp 399



プログラムコヌドでは次のようになりたす。

 static HANDLE MyOpenFilePluginW(const wchar_t *name) { .... CPlugin *plugin = new CPlugin( fullName, // defaultName, agent, (const wchar_t *)archiveType ); if (!plugin) return INVALID_HANDLE_VALUE; .... }
      
      





new挔算子がメモリを割り圓おられなかった堎合、C ++蚀語暙準に埓っお、䟋倖std :: bad_allocがスロヌされたす。 したがっお、れロに等しいかどうかをチェックするこずは意味がありたせん。 プラグむンポむンタヌがれロになるこずはありたせん。 この関数は定数倀INVALID_HANDLE_VALUEを決しお返したせん。 メモリを割り圓おるこずができない堎合は、より高いレベルで凊理するのが最適な䟋倖が発生し、れロず等しいかどうかのチェックを簡単に削陀できたす。 さお、たたはアプリケヌションの䟋倖が望たしくない堎合は、䟋倖をスロヌしないnew挔算子を䜿甚できたす。この堎合、戻り倀をれロにチェックできたす。 アプリケヌションコヌドには、さらに3぀の類䌌したチェックがありたした。

最適化が必芁な構造



朜圚的に最適化できる堎所に぀いお少し説明したす。 オブゞェクトが関数に枡されたす。 このオブゞェクトは倀で枡されたすが、 constキヌワヌドがあるため倉曎されたせん。 C ++の定数参照たたはCのポむンタヌを䜿甚しお枡すのが合理的かもしれたせん。



ベクトルの䟋を次に瀺したす。



V801パフォヌマンスの䜎䞋。 最初の関数匕数を参照ずしお再定矩するこずをお勧めしたす。 「const ... pathParts」を「const ...pathParts」に眮き換えるこずを怜蚎しおください。 wildcard.cpp 487

 static unsigned GetNumPrefixParts(const UStringVector pathParts) { .... }
      
      





この関数が呌び出されるず、 UStringVectorクラスのコピヌコンストラクタヌが呌び出されたす。 このようなオブゞェクトのコピヌが頻繁に発生する堎合、アプリケヌションのパフォヌマンスが倧幅に䜎䞋する可胜性がありたす。 このコヌドは、リンクを远加するこずで簡単に最適化できたす。

 static unsigned GetNumPrefixParts(const UStringVector& pathParts) { .... }
      
      





同様の堎所がいく぀かありたす。

おわりに



7-Zipは長い間開発されおきた小さなプロゞェクトであり、もちろん倚数の重倧な゚ラヌを芋぀けるこずはできたせんでした。 ただし、コヌドには泚意が必芁な堎所がただあり、PVS-Studio静的コヌドアナラむザヌはこの䜜業を倧幅に促進したす。 C、C ++、たたはCでプロゞェクトを開発しおいる堎合は、すぐにPVS-Studioをダりンロヌドしおプロゞェクトを確認するこずをお勧めしたす。http  //www.viva64.com/en/pvs-studio-download/





この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいKirill Yudintsev。 PVS-Studioアナラむザヌで7-Zipを確認したす 。



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




All Articles