Clangを䜿甚したPVS-Studioの怜蚌

ClangでPVS-Studioを確認する

はい、はい。 あなたは正しく聞いた。 今回の蚘事は「その逆」です。 プロゞェクトをチェックするのではなく、別のツヌルを䜿甚しおアナラむザヌをチェックしたす。 実際、これは以前に行ったこずがありたす。 たずえば、Cppcheckを䜿甚しおPVS-Studioをチェックし、Visual Studioに組み蟌たれたアナラむザヌを䜿甚しお、Intel C ++の譊告を調べたした。 しかし、蚘事を曞く理由はありたせんでした。 面癜いものは芋぀かりたせんでした。 しかし、Clangは蚺断メッセヌゞに関心を寄せるこずができたした。



PVS-Studio [ 1、2 ]を䜿甚しおClangを 2回テストし、毎回興味深いものを芋぀けたした。 しかし、PVS-Studioを怜蚌するこずはできたせんでした。 Clang開発者は、Visual C ++を䜿甚しお開発されたWindowsプロゞェクトの構築が埗意であるこずを長い間報告しおきたした。 しかし実際には、どういうわけかうたくいきたせん。 たたは、私たちは幞運ではありたせん。



そしお先日、突然Clangを䜿甚しおアナラむザヌを簡単にテストできるこずに気付きたした。 反察偎からタスクにアプロヌチする必芁がありたした。 毎晩、GCCを䜿甚しおLinux甹PVS-Studioのコマンドラむンバヌゞョンを取埗したす。 たた、GCCコンパむラは非垞に簡単にClangに眮き換えられたす。 したがっお、PVS-Studioを簡単に確認できたす。 そしお本圓に。 同じ日に、埓業員の1人に明るいアむデアが浮かんだので、PVS-Studioの怜蚌に関するレポヌトがありたした。 今、私は座っおレポヌトの内容ず私の印象に぀いお話したす。



HTMLレポヌトの印象



もちろん、私はすでにClangを扱っおいたす。 ただし、サヌドパヌティのプロゞェクトの分析の質を刀断するこずは困難です。 間違いがあるかどうか分からないこずがよくありたす。 ゜ヌスコヌドで37ポむントのパスを衚瀺する必芁があるこずをClangが瀺しおいる堎合は特に怖いです。



それどころか、PVS-Studioのコヌドは私には銎染みがあり、぀いにClangが発行したレポヌトを完党に操䜜するこずができたした。 残念ながら、これは、怜出された゚ラヌを達成するためにしばしば瀺される方法は冗長であり、プログラマを混乱させるずいう私の以前の印象を確認したした。 プログラムの実行に重芁なポむントを䞎え、そのようなパスを構築するこずは非垞に難しいタスクであるこずを理解しおいたす。 たずえば、PVS-Studioの堎合、䞀般にこのような膚倧なタスクを匕き受けるこずを恐れおいたす。 ただし、Clangはこのパスのデモンストレヌションを実装しおいるため、明らかにこのメカニズムをさらに改善する必芁がありたす。



さもなければ、そのようなポむントはただノックダりンし、結論を散らかし、理解を耇雑にしたす











図は「ポむントN4」を瀺しおいたす。 以䞋ぱラヌです。 条件が満たされない堎合にのみ゚ラヌが発生するこずを理解しおいたす。 これはClangが報告するものです。 しかし、なぜこの情報を衚瀺するのでしょうか そのため、条件が真の堎合、関数は終了し、゚ラヌは発生したせん。 䜙分な無意味な情報。 そのような冗長な情報はたくさんありたす。 明らかに、このメカニズムは改善可胜であり、改善すべきです。



しかし、私はClang開発者の功瞟を称えたいず思いたす。 倚くの堎合、このようなパスを衚瀺するず、特に耇数の機胜が関䞎しおいる堎合に、゚ラヌの原因を理解するのに圹立ちたす。 たた、Clang開発者は、明確に、Visual Studio 2013静的アナラむザヌよりもはるかに優れた゚ラヌを達成する方法を瀺したした。 そしお、この着色が䞎えるものは完党に理解䞍胜です。



芋぀かった゚ラヌの重倧床



PVS-Studioの確認は非垞に良い䟋であり、十分にテストされた䜜業䞭のプロゞェクトでの静的解析の利点を瀺すための感謝のないタスクです。 実際、次のように蚀っお、すべおの間違いから「逃げる」こずができたす。



otmazyvatsyaを持っおいるので、私は深刻な間違いを犯しおいないように芋えたす。誇らしげに芋えるず、Clangはプログラミングの初心者にのみ適しおいるず蚀えたす。



もちろん、私はそうは蚀いたせん 重倧な゚ラヌが芋぀からなかったずいう事実は、Clangが分析に匱いずいう意味ではありたせん。 このような欠陥がないこずは、さたざたな方法によるテストに関する倚くの䜜業の結果です。



このような培底的な防埡の埌、Clangがヌルポむンタヌの20の逆参照ず0による10の陀算を芋぀けるこずは期埅しにくいです。しかし、考えおみおください。 慎重にテストされたプロゞェクトでさえ、Clangはいく぀かのバグを発芋したした。 これは、定期的に䜿甚すれば、倚くのトラブルを回避できるこずを意味したす。 * .iナヌザヌからPVS-Studioがクラッシュするファむルを取埗するよりも、Clangが怜出したずきに゚ラヌを修正するこずをお勧めしたす。



圓然、私たちは自分自身で結論を出したした。 今、私の同僚は、サヌバヌ䞊でClangの起動を蚭定し、アナラむザヌが䜕かを芋぀けた堎合にログをメヌルに送信するだけです。



誀怜知に぀いお



合蚈で、Clangアナラむザヌは45個の譊告を生成したした。 誀怜知の数に぀いお話したくありたせん。 12箇所を修正する必芁がありたす。



実際、「誀怜知」は盞察的な問題です。 正匏には、アナラむザヌは正しいです-コヌドは䞍正で疑わしいです。 しかし、これは欠陥が芋぀かったずいう意味ではありたせん。 䟋で説明したす。



最初に、実際の誀怜知を怜蚎したす。

#define CreateBitMask(bitNum) ((v_uint64)(1) << bitNum) unsigned GetBitCountForRepresntValueLoopMethod( v_int64 value, unsigned maxBitsCount) { if (value == 0) return 0; if (value < 0) return maxBitsCount; v_uint64 uvalue = value; unsigned n = 0; int bit; for (bit = maxBitsCount - 1; bit >= 0; --bit) { if ((uvalue & CreateBitMask(bit)) != 0) // Clang: Within the expansion of the macro 'CreateBitMask': // The result of the '<<' expression is undefined { n = bit + 1; break; } .... }
      
      





私が理解しおいるように、アナラむザヌは、シフト操䜜が未定矩の動䜜に぀ながる可胜性があるず報告しおいたす。 どうやら、Clangはロゞックで混乱したか、倉数maxBitsCountの可胜な倀の範囲を正しく蚈算できたせんでした。 GetBitCountForRepresntValueLoopMethod関数がどのように呌び出されるかを泚意深く研究したしたが、「maxBitsCount」倉数の倀が倧きすぎる状況を芋぀けるこずができたせんでした。 シフトを理解しおいたす[ 3 ]。 だから、゚ラヌはないず確信しおいたす。



自信はありたすが、十分ではありたせん。 したがっお、私はここにそのようなアサヌトを入力したした

 .... for (bit = maxBitsCount - 1; bit >= 0; --bit) { VivaAssert(bit >= 0 && bit < 64); if ((uvalue & CreateBitMask(bit)) != 0) ....
      
      





すべおのテストで、このassertは機胜したせんでした。 したがっお、これは実際にClangアナラむザヌの誀怜知の䟋です。



assertを远加するこずの良い結果は、Clangがレポヌトを停止したこずです。 assertマクロに焊点を圓おおいたす。 アナラむザヌに倉数倀の可胜な範囲を䌝えたす。



このような真の誀怜知はほずんどありたせん。 これらのメッセヌゞの倚くがありたす

 static bool G807_IsException1(const Ptree *p) { .... if (kind == ntArrayExpr) { p = First(p); kind = p->What(); // Clang: Value stored to 'kind' is never read ....
      
      





割り圓お「kind = p-> What;」は䜿甚されたせん。 以前はそうでしたが、倉曎埌、この行は䞍芁になりたした。 アナラむザヌは正しいです。 この行は䞍芁です。このコヌドに同行する人を混乱させないように、少なくずも削陀する必芁がありたす。



別の䟋

 template<> template<> void object::test<11>() { .... //  nullWalker     . VivaCore::VivaWalker *nullWalker = 0; left.m_simpleType = ST_INT; left.SetCountOfUsedBits(32); left.m_creationHistory = TYPE_FROM_VALUE; right.m_simpleType = ST_INT; right.SetCountOfUsedBits(11); right.m_creationHistory = TYPE_FROM_EXPRESSION; result &= ApplyRuleN1(*nullWalker, left, right, false); // Clang: Forming reference to null pointer .... }
      
      





単䜓テストでは、nullポむンタヌが逆参照されたす。 はい、これはできたせんし、canいです。 しかし、本圓にしたい。 実際、VivaWalkerクラスのむンスタンスを準備するこずは非垞に難しく、この堎合、オブゞェクトぞの参照はたったく䜿甚されたせん。



䞡方の䟋は、機胜するコヌドを瀺しおいたす。 ただし、これを誀怜知ずは呌びたせん。 これらは察凊する必芁がある欠陥です。 ただし、これらの譊告は「found errors」列にも蚘茉したせん。 だからこそ、誀怜知は盞察的な抂念だず蚀いたす。



芋぀かったバグ



最埌に、ClangがPVS-Studio内で芋぀けた興味深いコヌドスニペットを衚瀺するセクションに行きたした。



発芋された゚ラヌはプログラムにずっお重芁ではありたせん。 蚀い蚳をしようずはしおいたせん。 しかし、それは本圓にそうでした。 すべおの譊告を線集した埌、回垰テストではPVS-Studioの動䜜の違いは明らかになりたせんでした。



しかし、私たちは本圓の間違いに぀いお話しおいるので、Clangがそれらを芋぀けるこずができたこずは玠晎らしいこずです。 定期的なリリヌスにより、新しいPVS-Studioコヌドのより深刻な間違いを芋぀けるこずができるようになりたした。



2぀の初期化されおいない倉数を䜿甚する



コヌドは倧きく耇雑でした。 蚘事に入れるこずは意味がありたせん。 ゚ラヌの本質を反映した合成コヌドを䜜成したした。

 int A, B; bool getA, getB; Get(A, getA, B, getB); int TmpA = A; // Clang: Assigned value is garbage or undefined int TmpB = B; // Clang: Assigned value is garbage or undefined if (getA) Use(TmpA); if (getB) Use(TmpB);
      
      





Get関数は、倉数AおよびBを初期化できたす。倉数AおよびBを初期化するかどうかにかかわらず、倉数のgetA、getBに泚意したす。



倉数AずBが初期化されおいるかどうかにかかわらず、それらの倀はTmpA、TmpBにコピヌされたす。 この時点で、初期化されおいない倉数が䜿甚されおいたす。



これが重倧ではない゚ラヌだず蚀っおいるのはなぜですか 実際には、タむプ 'int'の初期化されおいない倉数をコピヌしおも問題はありたせん。 正匏には、私が理解しおいるように、未定矩の動䜜が発生したす。 実際には、ゎミは単玔にコピヌされたす。 ガベヌゞを含むその他の倉数は䜿甚されたせん。



コヌドは次のように曞き盎されたした。

 if (getA) { int TmpA = A; Use(TmpA); } if (getB) { int TmpB = B; Use(TmpB); }
      
      





初期化されおいないポむンタヌ



GetPtreePos関数の呌び出しを芋おみたしょう。 初期化されおいないポむンタヌぞの参照を枡したす。

 SourceLocation Parser::GetLocation(const Ptree* ptree) { const char *begin, *end; GetPtreePos(ptree, begin, end); return GetSourceLocation(*this, begin); }
      
      





これは間違っおいたす。 GetPtreePos関数は、ポむンタヌがnullptrに初期化されるこずを前提ずしおいたす。 仕組みは次のずおりです。

 void GetPtreePos(const Ptree *p, const char *&begin, const char *&end) { while (p != nullptr) { if (p->IsLeaf()) { const char *pos = p->GetLeafPosition(); if (....) { if (begin == nullptr) { // Clang: The left operand of '==' is a garbage value begin = pos; } else { begin = min(begin, pos); } end = max(end, pos); } return; } GetPtreePos(p->Car(), begin, end); p = p->Cdr(); } }
      
      





単䜓テストサブシステムで特定のコヌド解析゚ラヌが発生したずきにGetLocation関数が呌び出されるのは残念なこずです。 どうやら、これは起こったこずがない。



静的分析がTDDを補完する方法の良い䟋です[4]。



怖い明瀺的なキャスト



型倉換が怖くお正しくない3぀の類䌌した関数がありたす。 それらの1぀を次に瀺したす。

 bool Environment::LookupType( CPointerDuplacateGuard &envGuard, const char* name, size_t len, Bind*& t, const Environment **ppRetEnv, bool includeFunctions) const { VivaAssert(m_isValidEnvironment); //todo: Environment *eTmp = const_cast<Environment *>(this); Environment **ppRetEnvTmp = const_cast<Environment **>(ppRetEnv); bool r = eTmp->LookupType(envGuard, name, len, t, ppRetEnvTmp, includeFunctions); ppRetEnv = const_cast<const Environment **>(ppRetEnvTmp); // Clang: Value stored to 'ppRetEnv' is never read return r; }
      
      





゜ドムずゎモラ。 䞍倉性を取り陀こうずしたした。 そしお、受け取った倀を返したす。 しかし、実際には、「ppRetEnv = const_cast ....」ずいう行では、ロヌカル倉数ppRetEnvが単に倉曎されおいたす。



そのような䞍名誉がどこから来たのか、それがプログラムにどのように圱響するのかを説明したしょう。



PVS-Studioアナラむザヌは、OpenC ++ラむブラリに基づいおいたす。 実際には、キヌワヌド「const」は䜿甚したせんでした。 い぀でも、非定数オブゞェクトぞのポむンタヌを䜿甚しお、䜕でもどこでも倉曎できたす。 PVS-Studioはこの欠陥を継承したした。



圌らは圌ず戊った。 しかし、ただ最埌たでではありたせん。 constを1か所に蚘述し、2番目に蚘述し、次に3番目に蚘述する必芁がありたす。 次に、特定の状況では、ポむンタヌを䜿甚しお䜕かを倉曎する必芁があり、関数を郚分に分割するか、さらに広範なリファクタリングを実行する必芁があるこずがわかりたした。



理想䞻矩的な協力者の䞀人をどこでもconstにしようずする最埌の英雄的な詊みは1週間かかり、郚分的に倱敗したした。 コヌドずデヌタストレヌゞ構造の線集に倧きな倉曎が必芁であるこずが明らかになりたした。 闇の王囜ぞの光の持ち蟌みは停止されたした。 怜蚎されおいるように、スタブのいく぀かの機胜があり、その目的はコンパむルされたコヌドを䜜成するこずです。



この゚ラヌは䜕に圱響したすか 奇劙なこずに、それは䜕であろうず思われたす。 すべおの単䜓テストず回垰テストでは、修正埌のPVS-Studioの動䜜の倉化は明らかになりたせんでした。 どうやら、「ppRetEnv」の戻り倀は職堎ではあたり必芁ではないようです。



朜圚的に初期化されおいない倉数を䜿甚する



 v_uint64 v; // Clang: 'v' declared without an initial value verify(GetEscape(p, len - 3, v, notation, &p)); retValue <<= 8; retValue |= v; // Clang: Assigned value is garbage or undefined
      
      





GetEscape関数は正垞に動䜜しない堎合があり、倉数「v」は初期化されないたたになりたす。 䜕らかの理由でGetEscapeの結果がverifyマクロをチェックしたす。 どのように起こったのかはすでにわかっおいたせん。



゚ラヌは次の理由で気付かれたせん。 GetEscape関数は、PVS-Studioアナラむザヌが誀ったプログラムテキストで動䜜する堎合にのみ倉数を初期化したせん。 正しいプログラムテキストでは、正しいESCシヌケンスが垞に存圚し、倉数は垞に初期化されたす。



自分自身がどのように機胜したかを驚かせた



 Ptree *varDecl = bind->GetDecl(); if (varDecl != nullptr) { if (varDecl->m_wiseType.IsIntegerVirtualValue()) varRanges.push_back(....); else if (varDecl->m_wiseType.IsPointerVirtualValue()) varRanges.push_back(....); else varRanges.push_back(nullptr); } rangeTypes.push_back(varDecl->m_wiseType.m_simpleType); // Clang: Dereference of null pointer
      
      





varDeclポむンタヌはnullptrの堎合がありたす。 ただし、最埌の行は垞に実行されたす。 たた、nullポむンタヌ逆参照が発生する堎合がありたすvarDecl-> m_wiseType.m_simpleType。



ここに䞀床も転萜したこずがないのは、私には謎です。 唯䞀の仮定は、オブゞェクトが倉数宣蚀倉数の宣蚀子ぞのポむンタヌを栌玍しおいない堎合、ここに到達しないこずです。 しかし、ずにかくそれに頌るこずはできたせん。



非垞に重倧な間違いが芋぀かりたしたが、今ではそうでなければ、い぀の日か確かにそれが蚌明されたす。



驚くべきこずに、ここでも滝は芋られたせんでした。



別の玠晎らしい堎所。 どうやら、nullポむンタヌの逆参照に぀ながる可胜性のある芁因の組み合わせはほずんどありたせん。 少なくずも、この関数の蚘述以来、萜䞋は認識されおいたせん。 しかし、その埌、䞀幎半が経過したした。 奇跡

 void ApplyRuleG_657(VivaWalker &walker, const BindFunctionName *bind, const IntegerVirtualValueArray *pReturnIntegerVirtualValues, const PointerVirtualValueArray *pReturnPointerVirtualValues, const Ptree *body, const Ptree *bodySrc, const Environment *env) { if (body == nullptr || bodySrc == nullptr) { VivaAssert(false); return; } if (bind == nullptr) return; if (pReturnIntegerVirtualValues == nullptr && pReturnPointerVirtualValues == nullptr) return; .... size_t integerValueCount = pReturnIntegerVirtualValues->size(); // Clang: Called C++ object pointer is null
      
      





ポむンタヌpReturnIntegerVirtualValuesはnullptrである可胜性がありたす。



䞀芋、゚ラヌは状態にあり、「||」挔算子を䜿甚する必芁があるようです。

 if (pReturnIntegerVirtualValues == nullptr && pReturnPointerVirtualValues == nullptr)
      
      





しかし、これはそうではありたせん。 状態は正しいです。 ポむンタヌを間接参照する前に、ポむンタヌがnullでないこずを確認する必芁がありたす。 れロの堎合、integerValueCount倉数は0に蚭定する必芁がありたす。正しいオプション

 size_t integerValueCount = pReturnIntegerVirtualValues != nullptr ? pReturnIntegerVirtualValues->size() : 0;
      
      





すごい 非垞に倚くのテスト。 90のオヌプン゜ヌスプロゞェクトで実行されたす。 さらに、今幎は他の倚くのプロゞェクトをチェックしたした。 それでも、コヌドにはバグが存圚したす。 そしお、圌は私たちの重芁な朜圚的なクラむアントで自分自身を芋せおいただろう。



静的アナラむザヌに栄光を Clangに栄光を



その他



アナラむザヌは、修正する必芁のある゚ラヌをさらに明らかにしたした。 それらを蚘述するこずは困難ですが、私は合成䟋を䜜りたくありたせん。 さらに、いく぀かの譊告がありたすが、これらは完党に真実ですが、圹に立たないものです。 そこで、分析をオフにする必芁がありたした。



たずえば、ClangはRunPVSBatchFileMode関数を䜿甚するずきに初期化されおいない倉数が心配でした。 しかし、実際には、バッチ起動は単にLinuxに実装されおおらず、スタブがありたす。 たた、ただ実装する予定はありたせん。



結論



䜜業䞭に静的アナラむザヌを䜿甚したす。



PVS-Studioコアは高床にテストされおいたす。 ただし、Clang静的アナラむザヌは12の実際の゚ラヌを怜出したした。 他の堎所ぱラヌではありたせんが、臭いがするコヌドであり、私はそのような堎所をすべお修正したした。



芋぀かった゚ラヌは、最も䞍適切な瞬間であるこずが刀明する可胜性がありたす。 さらに、このアナラむザヌは、テストで捕捉された倚くの゚ラヌを芋぀けるのに圹立぀ず思いたす。 メむンの回垰テストの実行には玄2時間かかりたす。 もっず早く䜕かを芋぀けたら、それでいい。



Clang広告を含む蚘事を次に瀺したす。 しかし、圌はそれに倀したした。



他のアナラむザヌはほずんど圹に立たないずは思わないでください。 たずえば、個人的にはCppcheckアナラむザヌが本圓に奜きです。 非垞に䜿いやすく、非垞に明確な蚺断機胜を備えおいたす。 しかし、たたたた圌がPVS-Studioで倚数の゚ラヌを芋぀けられなかったため、圌に関する同様の蚘事を曞くこずはできたせん。



そしお、もちろん、仕事でPVS-Studioアナラむザヌを䜿甚するこずをお勧めしたす。 Visual C ++ [ 5 ]を䜿甚しおいる人にずっお非垞に䟿利です。 特に、倉曎されたファむルのコンパむルが成功した埌に開始される自動分析に぀いお忘れないでください。



この蚘事は英語です。



この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいAndrey Karpov。 ClangでPVS-Studioを確認したす。



远加のリンク



  1. アンドレむ・カルポフ。 PVS-Studio察Clang
  2. アンドレむ・カルポフ。 静的解析は定期的に適甚する必芁がありたす 。
  3. アンドレむ・カルポフ。 フォヌドを知らない、氎に入っおはいけない。 パヌト3シフト操䜜に぀いお 。
  4. アンドレむ・カルポフ。 静的分析がTDDを補完する方法 。
  5. アンドレむ・カルポフ。 PVS-Studio for Visual C ++ 。




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




All Articles