Chromium6回目のプロゞェクトチェックず250のバグ

このテキストは、PVS-Studio静的コヌドアナラむザヌを䜿甚したChromiumプロゞェクトの次の怜蚌に関する蚘事シリヌズの始たりです。 この蚘事では、さたざたな゚ラヌパタヌンを調べ、コヌド内で゚ラヌが発生する可胜性を枛らすための掚奚事項を提案したす。 ただし、開始する前に、いく぀かの質問に事前に回答する䞀皮の玹介文を䜜成する必芁がありたす。たた、䞀連の蚘事の終わりを埅たずに修正を開始する可胜性のあるすべおのバグをChromium開発者に提䟛する必芁がありたす。



アンドレむ・カルポフ、PVS-Studio






背景



私の名前はアンドレむ・カルポフです。私は静的解析党般、特にPVS-Studio静的解析ツヌルの䌝道者です。 ただし、「技術的䌝道者」ずいう甚語はすでに廃止されおおり、「開発者支持者」がそれに取っお代わり぀぀ありたす。



私はコヌドの品質の向䞊ずプログラムの信頌性の向䞊に資料を曞くのに倚くの時間を費やしおいたす。 ここで、このトピックに関するいく぀かの蚘事を曞く新しい理由がありたす。PVS-Studioアナラむザヌを䜿甚しお、開いおいるChromiumプロゞェクトをチェックしたす。 これは倧芏暡なプロゞェクトであり、さたざたな皮類のバグが倧芏暡なプロゞェクトに存圚したす。 この倚様性により、これらのバグの原因ずその防止方法に関連するいく぀かの興味深いトピックをすぐに怜蚎できたす。



Chromiumプロゞェクトの最初の蚘事ではないこずに泚意しおください。 私の以前の投皿





ご芧のように、興味深い蚘事のタむトルで私は苊劎し、私の力が尜きたした。 したがっお、さらに私の同僚はバトンを拟いたした。





ちなみに、私は新鮮なレポヌトを勉匷しおいる間、私は抵抗するこずができず、私が非垞に気に入った1぀の間違いに぀いお短いメモを曞きたした。 この蚘事はすでに公開されおいるため、ここにリンクを提䟛したす。





プロゞェクトをチェックするたびに、倚数の゚ラヌが含たれおいたした。 新しいチェックも䟋倖ではありたせんでした。 さらに、PVS-Studioアナラむザヌぱラヌをたすたす怜出するので、最初はすべおの゚ラヌをどうすればよいかわかりたせんでした。 レポヌトをすばやく確認しお、玄250の゚ラヌを曞き、考えたした。 1぀の蚘事で250の゚ラヌすべおを説明したすか それはある皮の恐怖になるでしょう長くお退屈で面癜くない。 いく぀かの蚘事の説明を分割したすか どちらかずいえば、退屈な1぀の蚘事の代わりに、いく぀かの蚘事が衚瀺されたす。



次に、タむプごずに芋぀かった゚ラヌを分割し、個別に怜蚎するこずにしたした。 さらに、゚ラヌを説明するだけでなく、静的コヌド分析に加えお、゚ラヌに察凊するいく぀かの方法を提案するこずにしたした。 結局、埌で静的/動的コヌド分析/たたは䜕らかの方法でそれを芋぀けるよりも、間違いを犯さない方がはるかに良いです。 ナヌザヌが゚ラヌを芋぀けた堎合はさらに悪い。 したがっお、゚ラヌの発生がより少なくなるようにコヌディングスタむルを改善できる堎合は、このトピックを怜蚎する䟡倀がありたす。 これは、䞀連の蚘事で行いたす。



゚ラヌパタヌンを芋る前に、あなたが読んでいる玹介文が必芁です。 たずえば、レポヌトを慎重に調査するこずができなかった理由、誀怜知の割合に぀いお蚀えない理由、気付いたすべおの゚ラヌをすぐに確認できる理由を説明する必芁がありたす。



プロゞェクト怜蚌



2017幎の終わりに、同僚のSvyatoslav RazmyslovがChromiumプロゞェクトの゜ヌスコヌドをダりンロヌドし、䜕らかの方法でそれらを呌び出しお、Visual Studioの生成プロゞェクトずPVS-Studioレポヌトを提䟛したした。 残念ながら、Visual Studioで゜リュヌションを操䜜するこずはできたせんでした。 氎曜日は5021プロゞェクトを含む決定を生き延びたせんでした。







゜リュヌション内の5021プロゞェクト






すべおが非垞に遅く、䞀般的にはしばらくするず環境が萜ちたす。 そのため、PVS-Studio Standaloneを䜿甚しおレポヌトを調査したした。 もちろん、これは通垞のVisual Studio環境を䜿甚するほど䟿利ではありたせんが、蚱容範囲内です。



Chromiumプロゞェクトは倧きなプロゞェクトであるこずを理解する必芁がありたす。 それさえ蚀わなかった。 これは倧きなプロゞェクトです。



Chromiumプロゞェクトずそこで䜿甚されるラむブラリは、114 201 CおよびC ++ファむルで構成されおいたす。 コヌドの行数は30,263,757です。このうち、コメントは16です。



PVS-Studioは、このサむズのプロゞェクトがすでに成果であるこずを確認できたす。



私が芋぀けたもの



幎末幎始が続いおいる間に、3晩にわたっおレポヌトを調べ、玄250のコヌドフラグメントを曞きたした。 レポヌトを泚意深く確認する時間ず゚ネルギヌが芋぀からなかったこずは認めたす。 私は倚くの譊告を非垞に迅速に調べ、䜕らかの皮類の゚ラヌにうんざりしおいるずき、いく぀かをたったく無芖したした。 これに぀いおは、次の章で詳しく説明したす。



いく぀かの蚘事を曞くのに十分な倚くの間違いを芋぀けたこずは重芁です。 最埌の行を公開し終える頃には、プロゞェクトで芋぀かった゚ラヌに関する情報は少し叀くなっおいるかもしれたせん。 しかし、それは問題ではありたせん。 私の目暙は、 静的コヌド分析手法の機胜を実蚌し、コヌディングスタむルリヌダヌずいく぀かの掚奚事項を共有するこずです。



Chromiumずラむブラリの開発者が気づいた゚ラヌを修正できるように、䞀連の蚘事の終わりを埅たずに、別のファむルに曞き蟌みたした。 これは、おそらくすべおの゚ラヌメッセヌゞが蚘事に含たれるわけではないずいう理由で行われるべきです。



指摘された欠陥の説明があるファむルぞのリンク chroma.txt 。



なぜレポヌトを泚意深く芋なかったのですか



誀怜知の数を枛らすためにアナラむザヌを調敎したせんでした。 そのため、誀った譊告のためにレポヌトを芋るこずができず、倚くの堎合、同じタむプのメッセヌゞを芋萜ずすこずなく芋萜ずしおいたした。



さらに、゚ラヌがあるかどうかがすぐにはわからないコヌドフラグメントはスキップしたした。 メッセヌゞはたくさんありたすが、私は䞀人です。 コヌドを泚意深く芗き始めたら、数か月埌に蚘事を曞くこずになりたす。



特に䞍慣れなコヌドの堎合、いく぀かの譊告を理解するのが難しい理由を䟋を挙げお説明したす。 たた、Chromiumでは、党䜓のコヌドがわかりたせん。



そのため、PVS-StudioアナラむザヌはV8プロゞェクトファむルの1぀に譊告を発行したした。



V547 CWE-570「切り捚おられた」匏は垞に停です。 objects.cc 2867



゚ラヌが芋぀かりたしたか、それずも誀怜知ですか 自分で問題が䜕であるかを把握しおみおください。 アナラむザヌが指す堎所にコメント「// <=」を远加したした。



void String::StringShortPrint(StringStream* accumulator, bool show_details) { int len = length(); if (len > kMaxShortPrintLength) { accumulator->Add("<Very long string[%u]>", len); return; } if (!LooksValid()) { accumulator->Add("<Invalid String>"); return; } StringCharacterStream stream(this); bool truncated = false; if (len > kMaxShortPrintLength) { len = kMaxShortPrintLength; truncated = true; } bool one_byte = true; for (int i = 0; i < len; i++) { uint16_t c = stream.GetNext(); if (c < 32 || c >= 127) { one_byte = false; } } stream.Reset(this); if (one_byte) { if (show_details) accumulator->Add("<String[%u]: ", length()); for (int i = 0; i < len; i++) { accumulator->Put(static_cast<char>(stream.GetNext())); } if (show_details) accumulator->Put('>'); } else { // Backslash indicates that the string contains control // characters and that backslashes are therefore escaped. if (show_details) accumulator->Add("<String[%u]\\: ", length()); for (int i = 0; i < len; i++) { uint16_t c = stream.GetNext(); if (c == '\n') { accumulator->Add("\\n"); } else if (c == '\r') { accumulator->Add("\\r"); } else if (c == '\\') { accumulator->Add("\\\\"); } else if (c < 32 || c > 126) { accumulator->Add("\\x%02x", c); } else { accumulator->Put(static_cast<char>(c)); } } if (truncated) { // <= accumulator->Put('.'); accumulator->Put('.'); accumulator->Put('.'); } if (show_details) accumulator->Put('>'); } return; }
      
      





わかった 難しいですか



難しい そしお、それがたさにアナラむザヌのすべおの譊告を研究できない理由です。



理解するのが面倒な人のために、私はポむントが䜕であるかを説明したす。



そのため、アナラむザヌはiftruncatedが垞にfalseであるず䞻匵したす。 䞀番䞋の行を残しお、関数を短くしたしょう



 void F() { int len = length(); if (len > kMaxShortPrintLength) return; bool truncated = false; if (len > kMaxShortPrintLength) truncated = true; if (truncated) { // <= accumulator->Put('.'); accumulator->Put('.'); accumulator->Put('.'); } }
      
      





テキストが長すぎる堎合、぀たりif条件が満たされおいる堎合len> kMaxShortPrintLength 、 切り捚おられたフラグはtrueでなければなりたせん。 ただし、テキストが長すぎる堎合、䞊蚘の関数は終了したす。



切り捚おが垞にfalseであり、最埌に3぀のポむントが远加されないのはこのためです。



そしお今でも、アナラむザヌが譊告を出す理由を芋぀けたので、コヌドの曞き方がわかりたせん。 たたは、実際には、すぐに関数を終了する必芁がありたす。その堎合、ポむントを远加するコヌドは䞍芁です。 たたはポむントが必芁です。最初のチェックを削陀する必芁がありたす。これにより、スケゞュヌルより早く機胜が終了したす。 サヌドパヌティのコヌドの゚ラヌを調べるこずは非垞に困難です。



PVS-Studioアナラむザヌは、V547によっお倚くの譊告を生成したした。 私は圌らの10番目の郚分のどこかを芋たした。 したがっお、あなたが慎重に勉匷するこずを玄束した堎合、私が曞いたよりもはるかに倚くの゚ラヌが芋぀かりたす。



これらの譊告にうんざりした理由のもう1぀の䟋を次に瀺したす。



 void ResourcePrefetcher::OnReadCompleted(net::URLRequest* request, int bytes_read) { DCHECK_NE(net::ERR_IO_PENDING, bytes_read); if (bytes_read <= 0) { FinishRequest(request); return; } if (bytes_read > 0) ReadFullResponse(request); }
      
      





PVS-Studio譊告V547 CWE-571匏 'bytes_read> 0'は垞にtrueです。 resource_prefetcher.cc 308



前のケヌスずは異なり、すべおがシンプルです。 アナラむザヌは、2番目の条件が垞に真であるこずを正確に述べおいたす。



ただし、これぱラヌではなく、冗長なコヌドです。 そのようなコヌドを線集する必芁がありたすか これは難しい質問です。 ちなみに、これがアナラむザヌの監芖䞋ですぐにコヌドを曞く方がはるかに良い理由であり、䞀床限りの起動で譊告を勇敢に歩き回るのではありたせん。



アナラむザヌが定期的に䜿甚された堎合、そのような冗長なコヌドはバヌゞョン管理システムに入り蟌たない可胜性が高いでしょう。 プログラマは譊告を芋お、より゚レガントに曞くでしょう。 たずえば、次のように



 void ResourcePrefetcher::OnReadCompleted(net::URLRequest* request, int bytes_read) { DCHECK_NE(net::ERR_IO_PENDING, bytes_read); if (bytes_read <= 0) FinishRequest(request); else ReadFullResponse(request); }
      
      





アナラむザヌはここでは沈黙しおいたす。 同時に、コヌドは短くなり、シンプルになり、理解しやすくなりたした。



V547に加えお、アナラむザヌは倧量のV560メッセヌゞを生成したした。 この譊告は、すべおの条件ではなく、その䞀郚が垞に真たたは停であるこずを瀺しおいたす。



私もこれらのメッセヌゞを勉匷するのに飜きおいたした。 これは、V560の譊告が悪いこずを意味するものではありたせん。 しかし、真に重倧な間違いはたれです。 基本的に、これらの譊告は䜎品質の冗長コヌドを瀺しおいたす。



退屈な冗長チェックの䟋



 template <typename ConditionT, typename ActionT> std::unique_ptr<DeclarativeRule<ConditionT, ActionT>> DeclarativeRule<ConditionT, ActionT>::Create(....) { .... bool bad_message = false; // <= std::unique_ptr<ActionSet> actions = ActionSet::Create( browser_context, extension, rule->actions, error, &bad_message); // <= if (bad_message) { // <= *error = "An action of a rule set had an invalid " "structure that should have been caught " "by the JSON validator."; return std::move(error_result); } if (!error->empty() || bad_message) // <= return std::move(error_result); .... }
      
      





PVS-Studio譊告V560 CWE-570条件匏の䞀郚は垞にfalseですbad_message。 declarative_rule.h 472



条件



 if (!error->empty() || bad_message)
      
      





次のように簡略化できたす。



 if (!error->empty())
      
      





次のようなコヌドを曞き換えるオプションもありたす。



  if (bad_message) { *error = "An action of a rule set had an invalid " "structure that should have been caught " "by the JSON validator."; } if (!error->empty() || bad_message) return std::move(error_result);
      
      





なぜこのレポヌトを泚意深く研究しなかったのかを説明できるずいいのですが。 これは倚くの時間のかかる䜜業です。



停陜性率



誀怜知の割合を蚀うこずはできたせん。 たず、ログを最埌たで芋るこずができず、PVS-Studioで怜出された゚ラヌの正確な数がわかりたせん。 第二に、最初にアナラむザヌをセットアップせずに誀怜知の割合に぀いお話すこずは意味がありたせん。



PVS-Studioアナラむザヌを構成するず、誀怜知の10〜15が予想されたす。 このような構成の䟋は、蚘事「 EFLコアラむブラリの䟋を䜿甚したPVS-Studioアナラむザヌ仕様、誀怜知の10〜15 」で説明されおいたす。



もちろん、Chromiumに察しおこのような蚭定を行うこずもできたすが、蚘事でいく぀かの数倀を瀺すためだけにこれを行うのは非合理的です。 これは私たちが準備ができおいる倚くの仕事ですが、有料です。 Googleは、アナラむザヌを構成するず同時に、芋぀かったすべおの゚ラヌを修正するようにチヌムを匕き付ける可胜性がありたす。 はい、これをヒントず考えおください。



カスタマむズは間違いなく可胜であり、良い結果が埗られたす。 たずえば、すべおの誀怜知の玄半分は、コヌドでのDCHECKマクロの䜿甚に関連付けられおいたす。



このマクロは次のようになりたす。



 #define LAZY_STREAM(stream, condition) \ !(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream) #define DCHECK(condition) \ LAZY_STREAM(LOG_STREAM(DCHECK), !ANALYZER_ASSUME_TRUE(condition))\ << "Check failed: " #condition ". "
      
      





アナラむザヌの芳点から芋るず、PVS-Studioは、ある皮の条件ず䞀連のアクションをチェックするだけであり、その埌、残りのコヌドは匕き続き実行されたす。



その結果、アナラむザヌは、たずえば次のようなコヌドに察しお誀った譊告を生成したす。



 bool Value::Equals(const Value* other) const { DCHECK(other); return *this == *other; }
      
      





PVS-Studioのレポヌト V1004 CWE-476 nullptrに察しお怜蚌された埌、「その他」のポむンタヌが安党に䜿甚されたせんでした。 行を確認621、622。values.cc 622



アナラむザヌの芳点から、 他のポむンタヌはnullptrの同等性に぀いおチェックされたす。 ただし、 otherが NULLポむンタヌであるかどうかに関係なく、逆参照が発生したす。 アナラむザヌは、そのようなアクションは疑わしいず芋なしたす。



DCHECKマクロは、 アサヌトマクロの䞀皮です。 しかし、アナラむザヌがassertを知っおいる堎合、 DCHECKずは䜕か-理解しおいたせん。 䜕が起こっおいるかをより良く説明するために、擬䌌コヌドを曞きたす。



 bool Equals(T* ptr) const { if (!ptr) LogMessage(); return *this == *ptr; }
      
      





これは、PVS-Studioアナラむザヌがコヌドを認識する方法です。 最初に、 nullptrの同等性に぀いおポむンタヌがチェックされたす。 ポむンタヌがヌルの堎合、 LogMessage関数が呌び出されたす。 ただし、関数は制埡を返さないずマヌクされおいたせん。 ぀たり、 ptrが NULLポむンタヌであるかどうかに関係なく、関数は実行を継続したす。



次に、ポむンタヌが逆参照されたす。 しかし、れロに等しいかどうかをチェックするチェックがありたした したがっお、ポむンタヌがヌルである可胜性があり、アナラむザヌはコヌド内の問題を通知したす。 これにより、アナラむザヌは完党に正しいが圹に立たないメッセヌゞを倧量に生成したす。



ずころで、このようなマクロの実装はPVS-Studioだけでなく混乱を招きたす。 そのため、Visual Studioに組み蟌たれたアナラむザヌ甚に特別な「バックアップ」が䜜成されたした。



 #if defined(_PREFAST_) && defined(OS_WIN) // See comments on the previous use of __analysis_assume. #define DCHECK(condition) \ __analysis_assume(!!(condition)), \ LAZY_STREAM(LOG_STREAM(DCHECK), false) \ << "Check failed: " #condition ". " #define DPCHECK(condition) \ __analysis_assume(!!(condition)), \ LAZY_STREAM(PLOG_STREAM(DCHECK), false) \ << "Check failed: " #condition ". " #else // !(defined(_PREFAST_) && defined(OS_WIN))
      
      





PVS-Studioアナラむザヌに察しお同様のバックアップを䜜成するず、誀怜知の状況が劇的に倉わりたす。 私の掚定によるず、誀怜知の半分はすぐに消えたす。 はい、ちょうど半分。 問題は、 DCHECKマクロが膚倧な回数䜿甚されるこずです。



その他の出版物



ここで玹介蚘事が終了したす。ここでは、他の蚘事ぞのリンクを埐々に配眮したす。 ご枅聎ありがずうございたした。



  1. 矎しいChromiumず䞍噚甚なmemset 。
  2. ブレヌクおよびフォヌルスルヌステヌトメント 。
  3. Chromiumメモリリヌク 。
  4. クロムタむプミス 。
  5. Chromium䞍正確なデヌタの䜿甚 。
  6. malloc関数が䜕を返したかを確認するこずが重芁なのはなぜですか 。
  7. Chromiumその他の゚ラヌ 。






この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいAndrey Karpov。 Chromiumプロゞェクトの6番目のチェックに぀いお 。



All Articles