Andrey Karpovは、ManticoreプロゞェクトコヌドはSphinxプロゞェクトコヌドよりも優れおいるず考えおいたす

スフィンクスvsマンティコア 私の読者は、コヌドの品質に関しおManticoreずSphinxのプロゞェクトを比范するように頌みたした。 これは、マスタヌした1぀の方法でしか行えたせん。PVS-Studio静的アナラむザヌを䜿甚しおプロゞェクトをチェックし、コヌドの゚ラヌ密床を蚈算したす。 したがっお、これらのプロゞェクトでCおよびC ++コヌドをテストしたしたが、私の意芋では、Manticoreコヌドの品質はSphinxコヌドの品質よりも高くなっおいたす。 圓然、これは非垞に狭い芖野であり、研究の真実性を装うわけではありたせん。 しかし、圌らは私に尋ね、私はできる限り比范をしたした。



スフィンクスずマンティコア



最初に、SphinxプロゞェクトずManticoreプロゞェクトに぀いお理解したしょう。



Sphinxは、Andrey Aksyonovが開発した党文怜玢システムであり、GNU GPLラむセンスの䞋で配垃されおいたす。 特城的な機胜は、むンデックス䜜成ず怜玢の高速化、および既存のDBMSおよび䞀般的なWebプログラミング蚀語のAPIずの統合です。



ここから゜ヌスコヌドを取りたした。 CおよびC ++のコヌドを䜿甚し、サヌドパヌティラむブラリを含たない堎合のプロゞェクトのサむズ-156 KLOC。 コメントは10.2を占めおいたす。 これは、「クリヌンコヌド」が144 KLOCであるこずを意味したす。



Manticoreは Sphinxのフォヌクです。 元のSphinxチヌムの䞻芁メンバヌずしおスタヌトしたManticoreチヌムは、党文怜玢甚の高速で安定した匷力なフリヌ゜フトりェアを提䟛するずいう次の目暙を蚭定したした。



ここから゜ヌスコヌドを取りたした。 CおよびC ++でコヌドを取埗し、サヌドパヌティラむブラリを含たない堎合のプロゞェクトのサむズ-170 KLOC。 コメントは10.1を占めおいたす。 これは、「クリヌンコヌド」が152 KLOCであるこずを意味したす。



Manticoreプロゞェクトのコヌドの行数はわずかに倚く、芋぀かった゚ラヌの密床を評䟡する際にこれを考慮したす。



比范分析



これらのプロゞェクトのコヌドは非垞に䌌おおり、倚くの堎合、1぀のプロゞェクトず別のプロゞェクトの䞡方に同じ゚ラヌが存圚したす。 今回は衚面的に分析を行い、PVS-Studioアナラむザヌによっお発行される䞀般的な高レベル譊告のみを調査したこずをすぐに蚀わなければなりたせん。



プロゞェクトをより慎重に比范するのが面倒なのはなぜですか 私が蚀ったように、プロゞェクトは非垞に䌌おおり、すでに高レベルの譊告を芋たずき、私は退屈しおいたした。 䞀般的に、状況は明確です。 アナラむザヌは非垞によく䌌た譊告リストを生成したしたが、Sphinxプロゞェクトにのみもう少し倚くの譊告リストがありたす。 他のレベルの譊告でも状況はたったく同じになるず思いたす。



この蚘事では、゚ラヌのあるコヌドの䞀郚のみを怜蚎したすが、それは䜕らかの理由で私にずっお興味深いず思われたした。 プロゞェクトのより詳现な分析は、開発者が行うこずができたす。 䞀時的なラむセンスキヌを提䟛する準備ができおいたす。



読者はPVS-Studioのデモ版もダりンロヌドしお、プロゞェクトのコヌドを確認するこずをお勧めしたす。 きっずあなたはそれらの䞭にたくさんの興味深いものを芋぀けるでしょう。



よくある間違い



SphinxプロゞェクトずManticoreの䞡方で芋぀かった゚ラヌから始めたす。



CWE-476NULLポむンタヌ逆参照



Expr_StrIn_c ( const CSphAttrLocator & tLoc, int iLocator, ConstList_c * pConsts, UservarIntSet_c * pUservar, ESphCollation eCollation ) : Expr_ArgVsConstSet_c<int64_t> ( NULL, pConsts ) , ExprLocatorTraits_t ( tLoc, iLocator ) , m_pStrings ( NULL ) , m_pUservar ( pUservar ) { assert ( tLoc.m_iBitOffset>=0 && tLoc.m_iBitCount>0 ); assert ( !pConsts || !pUservar ); m_fnStrCmp = GetCollationFn ( eCollation ); const char * sExpr = pConsts->m_sExpr.cstr(); // <= .... }
      
      





かなり倧きなコヌドスニペットを甚意したしたが、恐れないでください。ここではすべおが簡単です。 pConstsの正匏な匕数に泚意しおください。 このポむンタヌは、 sExpr倉数を初期化するためにコンストラクタヌで䜿甚されたす。 同時に、 NULLが匕数ずしお枡された堎合、コンストラクタヌのどこにもチェックがありたせん。 NULLポむンタヌに察する保護はありたせん。 pConsts倉数は倧胆に逆参照されおいたす。



ご泚意 assertの圢匏のチェックがありたすが、リリヌスバヌゞョンでは圹に立たないため、このようなチェックでは十分ずは蚀えたせん。



次に、 Expr_StrIn_cクラスのむンスタンスが䜜成されるCreateInNode関数のコヌドを芋おみたしょう。



 ISphExpr * ExprParser_t::CreateInNode ( int iNode ) { .... case TOK_ATTR_STRING: return new Expr_StrIn_c ( tLeft.m_tLocator, tLeft.m_iLocator, NULL, // <= pUservar, m_eCollation ); .... }
      
      





3番目の実匕数はNULLです。 したがっお、このコヌドフラグメントが実行されるず、nullポむンタヌは逆参照されたす。



アナラむザヌは、譊告を発行しおこの゚ラヌを通知したす。V522ヌルポむンタヌ「pConsts」の逆参照が行われる堎合がありたす。 NULLポむンタヌは「Expr_StrIn_c」関数に枡されたす。 3番目の匕数を調べたす。 行を確認しおください5407、5946。sphinxexpr.cpp 5407



PVS-Studioはデヌタフロヌ分析を実行し、2぀の異なる関数の本䜓を調べるため、この゚ラヌは興味深いものです。 ただし、圌ははるかに耇雑なネストされた分析を実行できたす。 この堎合を怜蚎しおください。



たず、 SendBytes関数を怜蚎したす。実際には、nullポむンタヌの逆参照が発生したす。



 void ISphOutputBuffer::SendBytes ( const void * pBuf, int iLen ) { int iOff = m_dBuf.GetLength(); m_dBuf.Resize ( iOff + iLen ); memcpy ( m_dBuf.Begin() + iOff, pBuf, iLen ); }
      
      





pBufポむンタヌに泚意しおください。 どこでもチェックされず、すぐにmemcpy関数の実際の匕数ずしお枡されたす。 したがっお、 pBufポむンタヌがヌルの堎合、 memcpy関数が呌び出されるず、ヌルポむンタヌで読み取りが行われたす。



PVS-Studioが゚ラヌがあるず刀断したのはなぜですか この質問に答えるために、コヌルチェヌンを䞊っお、 SendMysqlOkPacket関数を怜蚎したす。



 void SendMysqlOkPacket ( ISphOutputBuffer & tOut, BYTE uPacketID, int iAffectedRows=0, int iWarns=0, const char * sMessage=NULL, bool bMoreResults=false ) { DWORD iInsert_id = 0; char sVarLen[20] = {0}; void * pBuf = sVarLen; pBuf = MysqlPack ( pBuf, iAffectedRows ); pBuf = MysqlPack ( pBuf, iInsert_id ); int iLen = (char *) pBuf - sVarLen; int iMsgLen = 0; if ( sMessage ) iMsgLen = strlen(sMessage) + 1; tOut.SendLSBDword ( (uPacketID<<24) + iLen + iMsgLen + 5); tOut.SendByte ( 0 ); tOut.SendBytes ( sVarLen, iLen ); if ( iWarns<0 ) iWarns = 0; if ( iWarns>65535 ) iWarns = 65535; DWORD uWarnStatus = iWarns<<16; if ( bMoreResults ) uWarnStatus |= ( SPH_MYSQL_FLAG_MORE_RESULTS ); tOut.SendLSBDword ( uWarnStatus ); tOut.SendBytes ( sMessage, iMsgLen ); }
      
      





私は、機胜党䜓を持ち蟌たなければならなかったこずをおizeびしたす。 sMessage匕数がNULLであるずいう関数からの保護がないこずを瀺したかったのです。 sMessageポむンタヌは、単にSendBytes関数に枡されたす。



たた、仮匕数sMessageのデフォルト倀はNULLであるこずに泚意しおください。

 const char * sMessage=NULL,
      
      





これ自䜓が危険です。 ただし、デフォルトの匕数がNULLであるずいう事実は䜕も意味したせん。 おそらく、正しい匕数が垞に関数に枡されたす。 したがっお、次に進みたしょう。



 inline void Ok( int iAffectedRows=0, int iWarns=0, const char * sMessage=NULL, bool bMoreResults=false ) { SendMysqlOkPacket ( m_tOut, m_uPacketID, iAffectedRows, iWarns, sMessage, bMoreResults ); if ( bMoreResults ) m_uPacketID++; }
      
      





Ok関数では、 sMessage匕数は単にSendMysqlOkPacket関数に枡されたす。 続けたしょう。

 void HandleMysqlMultiStmt (....) { .... dRows.Ok ( 0, 0, NULL, bMoreResultsFollow ); .... }
      
      





これで旅は終わりです。 4぀の実匕数のみが関数に枡されたす。 残りの匕数はデフォルト倀を取りたす。 これは、5番目のパラメヌタヌsMessage-がNULLになり、nullポむンタヌが逆参照されるこずを意味したす 。



この゚ラヌを瀺すPVS-Studioアナラむザヌの譊告V522 nullポむンタヌ「pBuf」の逆参照が行われる堎合がありたす。 NULLポむンタヌは「Ok」関数に枡されたす。 3番目の匕数を調べたす。 行を確認しおください2567、12267、12424、14979。searchd.cpp 2567



CWE-570匏は垞に停です



たず、 ESphBinRead列挙を怜蚎しおください 。



 enum ESphBinRead { BIN_READ_OK, ///< bin read ok BIN_READ_EOF, ///< bin end BIN_READ_ERROR, ///< bin read error BIN_PRECACHE_OK, ///< precache ok BIN_PRECACHE_ERROR ///< precache failed };
      
      





ご芧のずおり、負の倀を持぀名前付き定数はありたせん。



念のため、 ReadBytes関数を芋お、䜕のトリックもなく正盎に倀を返すこずを確認しおください。



 ESphBinRead CSphBin::ReadBytes ( void * pDest, int iBytes ) { .... return BIN_READ_EOF; .... return BIN_READ_ERROR; .... return BIN_READ_OK; }
      
      





ご芧のずおり、関数から返されるすべおの倀は0以䞊です。゚ラヌが発生したコヌドの順番です。



 static void DictReadEntry (....) { .... if ( pBin->ReadBytes ( pKeyword, iKeywordLen )<0 ) { assert ( pBin->IsError() ); return; } .... }
      
      





PVS-Studio譊告V547匏は垞にfalseです。 sphinx.cpp 22416



このようなチェックは意味がありたせん。 条件は垞にfalseであり、その結果、デヌタの読み取り時の誀った状況は凊理されたせん。 ほずんどの堎合、それは次のように曞かれおいるはずです。

 if ( pBin->ReadBytes ( pKeyword, iKeywordLen ) != BIN_READ_OK)
      
      





このコヌドは、コヌドの䜜成者が、プログラムが誀った状況を凊理するだけだず考えおいるこずを瀺しおいたす。 実際、私は非垞に頻繁に、䞍正確/非暙準の状況を凊理するコヌドの欠陥に遭遇したす。 したがっお、䜕かがうたくいかないず、プログラムはしばしばクラッシュしたす。 ゚ラヌハンドラは、単に間違っお蚘述されおいたす。



もちろん、これが起こる理由は䞍明です。 プログラムのこのようなセクションのテストは難しく、面癜くない。 これは、静的アナラむザヌがコヌドをチェックする頻床に関係なくチェックするため、静的アナラむザヌが優れたヘルパヌになる可胜性があるケヌスの1぀です。



CWE-14コンパむラヌがコヌドを削陀しおバッファヌをクリアする



 static bool GetFileStats (....) { .... struct_stat tStat; memset ( &tStat, 0, sizeof ( tStat ) ); if ( stat ( szFilename, &tStat ) < 0 ) { if ( pError ) *pError = strerror ( errno ); memset ( &tStat, 0, sizeof ( tStat ) ); // <= return false; } .... }
      
      





PVS-Studio譊告V597コンパむラは、 'tStat'オブゞェクトのフラッシュに䜿甚される 'memset'関数呌び出しを削陀できたした。 プラむベヌトデヌタを消去するには、memset_s関数を䜿甚する必芁がありたす。 sphinx.cpp 19987



コンパむラヌは、 memset関数の呌び出しを削陀する暩利を持っおいたす。memset関数は、プログラムで゚ラヌが発生した堎合、 tStatのプラむベヌトデヌタをクリアする必芁がありたす。



コンパむラがこれを行う理由は、䜕床も曞いたので、繰り返したせん。 ただそのような状況に遭遇しおいない人のために、 V597蚺断の説明を読むか、 CWE-14の説明を参照するこずを提案したす。



CWE-762メモリ管理ルヌチンの䞍䞀臎



たず、2぀のマクロの実装を確認する必芁がありたす。



 #define SafeDelete(_x) \ { if (_x) { delete (_x); (_x) = nullptr; } } #define SafeDeleteArray(_x) \ { if (_x) { delete [] (_x); (_x) = nullptr; } }
      
      





これで、このコヌドの゚ラヌを自分で簡単に芋぀けるこずができるず思いたす。

 int CSphIndex_VLN::DebugCheck ( FILE * fp ) { .... CSphRowitem * pInlineStorage = NULL; if ( pQword->m_iInlineAttrs ) pInlineStorage = new CSphRowitem [ pQword->m_iInlineAttrs ]; .... // cleanup SafeDelete ( pInlineStorage ); .... }
      
      





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



ご芧のずおり、1぀の芁玠のみが䜜成されたかのように、メモリが配列に割り圓おられ、解攟されたす。 SafeDeleteマクロの代わりに、 ここでSafeDeleteArrayマクロを䜿甚する必芁がありたす 。



ナニヌクなバグ



䞊蚘では、SphinxずManticoreの䞡方のコヌドに存圚するいく぀かのバグを芋たした。 この堎合、もちろん、1぀のプロゞェクトのみに固有の゚ラヌがありたす。 たずえば、そのような堎合を考えおみたしょう。



どちらのプロゞェクトにもRotateIndexMT関数がありたす。 ただし、さたざたな方法で実装されたす。 Sphinxプロゞェクトの実装では、この関数には欠陥CWE-690NULLポむンタヌ逆参照ぞの未チェックの戻り倀が含たれおいたす。



たず、 CheckServedEntry関数の宣蚀を芋おください。



 static bool CheckServedEntry(const ServedIndex_c * pEntry, // <= const char * sIndex, CSphString & sError );
      
      





最初の匕数は、定数オブゞェクトぞのポむンタヌです。 したがっお、関数はこのオブゞェクトずポむンタヌ自䜓を倉曎できたせん。



゚ラヌを含む関数

 static bool RotateIndexMT ( .... ) { .... ServedIndex_c * pServed = g_pLocalIndexes->GetWlockedEntry ( sIndex ); pServed->m_sNewPath = ""; // <= if ( !CheckServedEntry ( pServed, sIndex.cstr(), sError ) ) { if ( pServed ) // <= pServed->Unlock(); return false; } .... }
      
      





PVS-Studio譊告V595 nullptrに察しお怜蚌される前に、「pServed」ポむンタヌが䜿甚されたした。 行を確認しおください17334、17337。searchd.cpp 17334



pServedポむンタヌは最初に逆参照されたす。 次に、 CheckServedEntry関数が呌び出されたす 。これは、既にわかっおいるように、最初の実際の匕数ずしお枡されたpServedポむンタヌを倉曎できたせん。



次に、 pServedポむンタヌのNULL がチェックされたす 。 うん そのため、ポむンタヌはヌルになる可胜性がありたす。 したがっお、䞊蚘の最初のポむンタヌの間接参照の前に、チェックを远加する必芁がありたす。



別のオプションポむンタヌがNULLにならない堎合、 pServedが䞍芁かどうかをチェックしたす 。 いずれにしおも、コヌドを修正する必芁がありたす。



たずめるず



Sphinxプロゞェクトは、Manticoreプロゞェクトよりもサむズが小さくなっおいたす。 同時に、Sphinxプロゞェクトでは、Manticoreプロゞェクトよりも倚くの゚ラヌず「匂いのあるコヌド」に気付きたした。



プロゞェクトのサむズず気づいた欠陥の数を考えるず、次の結果が埗られたした。 Manticoreの゚ラヌ密床を1ずしおみたしょう。その埌、Sphinxプロゞェクトでは、私の掚定による゚ラヌ密床は1.5です。



私の調査結果。 Sphinxプロゞェクトの゚ラヌ密床は、Manticoreプロゞェクトず比范しお1.5倍高いです。 したがっお、Manticoreコヌドの品質は、Sphinxプロゞェクトの品質よりも優れおいたす。 フォヌクは元のものよりも良くなった。



繰り返したすが、これは非垞に少量の情報に基づいた私の䞻芳的な意芋です。 䞀郚のコンポヌネントのコヌドの゚ラヌ密床は、プロゞェクト党䜓の品質ず信頌性を意味したせん。



PVS-Studioアナラむザヌをダりンロヌドしお詊しおください。 簡単です。 最終的には、完璧なコヌドを曞いたずしおも、同僚のコヌドの゚ラヌをい぀でも探すこずができたす:)。



ご枅聎ありがずうございたした。 TwitterたたはRSSを賌読しお、新しい出版物に遅れないようにしおください。







この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳リンクを䜿甚しおください Andrey Karpovは、ManticoreプロゞェクトのコヌドはSphinxプロゞェクトのコヌドよりも優れおいるず考えおいたす



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



All Articles