LinuxバージョンのPVS-Studioを使用したLLVMの確認
LLVMが何であるかを知らない人はほとんどいないと思います。 それにもかかわらず、私はテストされたプロジェクトを簡単に説明する伝統を守ります。
LLVM (低レベル仮想マシン)は、RISCのような命令で仮想マシンを実装するプログラムの分析、変換、および最適化のための汎用システムです。 このバイトコードの最適化コンパイラとして使用して、さまざまなアーキテクチャのコードを解釈したり、その解釈およびJITコンパイル(一部のプラットフォーム)に使用したりできます。 LLVMプロジェクトの一環として、ClangフロントエンドはC、C ++およびObjective-C言語用に開発されました。これにより、ソースコードがLLVMバイトコードに変換され、LLVMを本格的なコンパイラとして使用できます。
公式ウェブサイト: http : //llvm.org/
リビジョン282481が確認され、分析はLinuxで実行されているPVS-Studioの新しいバージョンによって実行されました。 PVS-Studio for Linuxは新しい製品であるため、以下では検証の実行方法について詳しく説明します。 これにより、Linuxでアナライザーを使用することはまったく難しくなく、遅滞なく自分のプロジェクトをチェックすることができることがわかります。
アナライザーのLinuxバージョンは、次のページからダウンロードできます。http : //www.viva64.com/en/pvs-studio-download-linux/
コンパイラの起動を監視する汎用メカニズムを使用して、以前のプロジェクトをテストしました。 今回は、PVS-StudioがJSONコンパイルデータベースから取得する情報を確認するために使用します。 詳細については、ドキュメントセクション「 LinuxでPVS-Studioを実行する方法 」を参照してください。
LLVM 3.9は、CMakeを支持してautoconfを完全に放棄しました。これは、JSON Compilation Databaseのサポートを実際に試す良い理由でした。 これは何ですか これは、Clangユーティリティで使用される形式です。 コンパイラー呼び出しのリストを次の形式で保存します。
[ { "directory": "/home/user/llvm/build", "command": "/usr/bin/c++ .... file.cc", "file": "file.cc" }, .... ]
CMakeプロジェクトの場合、このようなファイルの取得は非常に簡単です。追加オプションを使用してプロジェクトの生成を完了するだけです。
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=On ../llvm
その後、compile_commands.jsonが現在のディレクトリに表示されます。 確認のために彼が必要です。 一部のプロジェクトはコード生成を使用するため、最初にビルドします。
make -j8
これで、すべての分析の準備が整いました。 1行で始まります。
pvs-studio-analyzer analyze -l ~/PVS-Studio.lic -o PVS-Studio.log -j
CMakeを使用しないプロジェクトの場合、Bearユーティリティを使用してcompile_commands.jsonを取得できます。 ただし、環境変数またはクロスコンパイルを積極的に使用する複雑なアセンブリシステムの場合、受信したコマンドがブロードキャストユニットに関する詳細情報を常に提供するとは限りません。
注N1。 LinuxでのPVS-Studioレポートの使用方法 。
注N2。 私たちはお客様と潜在的なユーザーに品質と迅速なサポートを提供します。 したがって、何かが明確でない場合、または何かがうまくいかない場合は、 サポートに連絡してください。 あなたは私たちのサービスを好きになるでしょう。
検証結果
ところで、これは最初のLLVMテストではありません。 以前のレビューに基づいて書かれた記事:
- PVS-Studio対Clang (2011);
- 静的解析は定期的に適用する必要があります (2012)。
残念ながら、誤検知の数と見つかったエラーの密度については何も言えません。 このプロジェクトは大規模であり、多くの警告があり、私はそれらを非常に表面的に研究しました。 私の弁護では、PVS-Studio for Linuxのリリースの準備に多くの時間がかかり、抜粋でのみ記事に取り組むことができたと言えます。
歌詞はすべて終わったので、最も興味深いものに移ります。 PVS-Studioが私に指摘したLLVMコード内の疑わしい場所を考えてください。
非ビートフィールド
コードには次のような列挙があります。
enum Type { ST_Unknown, // Type not specified ST_Data, ST_Debug, ST_File, ST_Function, ST_Other };
私がそう言うかもしれない場合、これは「古典的な列挙」です。 列挙内の各名前には、列挙内の値の順序で特定の場所に対応する整数値が割り当てられます。
- ST_Unknown = 0
- ST_Data = 1
- ST_Debug = 2
- ST_File = 3
- ST_Function = 4
- ST_Other = 5
繰り返しますが、これは単なる列挙であり、マスクのセットではありません。 これらの定数を互いに組み合わせることができる場合、それらは2の累乗になります。
次は、この列挙が誤って使用されているコードを見てみましょう。
void MachODebugMapParser::loadMainBinarySymbols(....) { .... SymbolRef::Type Type = *TypeOrErr; if ((Type & SymbolRef::ST_Debug) || (Type & SymbolRef::ST_Unknown)) continue; .... }
PVS-Studio警告: V616値「0」の「SymbolRef :: ST_Unknown」という名前の定数がビット演算で使用されます。 MachODebugMapParser.cpp 448
定数ST_Unknownはゼロに等しいことを思い出してください。 したがって、式は短縮できます。
if (Type & SymbolRef::ST_Debug)
どうやら、ここで何かが間違っています。 どうやら、このコードを書いたプログラマーは、フラグを表す列挙で作業していると判断しました。 つまり、各定数が1つまたは別のビットに対応することを期待していました。 しかし、これはそうではありません。 コードの正しいチェックは次のようになります。
if ((Type == SymbolRef::ST_Debug) || (Type == SymbolRef::ST_Unknown))
このようなエラーを回避するには、 enumクラスを使用する必要があると思います。 この場合、誤った式は単にコンパイルされません。
ワンタイムサイクル
機能はそれほど複雑ではないので、完全にまとめることにしました。 記事をさらに読む前に、ここで疑わしいものを個別に推測することを提案します。
Parser::TPResult Parser::TryParseProtocolQualifiers() { assert(Tok.is(tok::less) && "Expected '<' for qualifier list"); ConsumeToken(); do { if (Tok.isNot(tok::identifier)) return TPResult::Error; ConsumeToken(); if (Tok.is(tok::comma)) { ConsumeToken(); continue; } if (Tok.is(tok::greater)) { ConsumeToken(); return TPResult::Ambiguous; } } while (false); return TPResult::Error; }
PVS-Studio 警告 : V696 「continue」演算子は、条件が常にfalseであるため、「do {...} while(FALSE)」ループを終了します。 行を確認してください:1642、1649。ParseTentative.cpp 1642
もちろん、LLVM開発者は、ここにエラーがあるかどうかをすぐに理解できます。 私は探偵を演じなければなりません。 このコードを見て、私は次のように推論しました。 この関数は、開き括弧 '<'を読み取ってから、ループで識別子とコンマを読み取ります。 コンマがない場合は、閉じ括弧が必要です。 問題が発生した場合、関数はエラーコードを返します。 次の関数アルゴリズム(疑似コード)が考案されたと思います。
- サイクルの開始:
- 識別子を読み取ります。 これが識別子でない場合は、エラーステータスを返します。
- コンマを読んでください。 コンマの場合、ループの先頭に移動します。
- ええ、コンマはありません。 これが閉じ括弧である場合、すべては問題ありません。関数を終了します。
- それ以外の場合、エラーステータスを返します。
問題は、 continueステートメントを使用してループを再開しようとすることです。 彼は、サイクルの本体の開始ではなく、サイクルを継続するための条件のチェックに制御を移します。 そして、条件yは常にfalseです。 その結果、サイクルはすぐに終了し、アルゴリズムは次のようになります。
- サイクルの開始:
- 識別子を読み取ります。 これが識別子でない場合は、エラーステータスを返します。
- コンマを読んでください。 コンマの場合、ループを終了し、関数からエラーステータスを返します。
- ええ、コンマはありません。 これが閉じ括弧である場合、すべては問題ありません。関数を終了します。
- それ以外の場合、エラーステータスを返します。
したがって、角括弧で囲まれた1つの要素のシーケンスのみが正しい場合があります。 シーケンス内にコンマで区切られた複数の要素がある場合、関数はエラーステータスTPResult :: Errorを返します。
ループの反復が1回しか実行されない別のケースを考えてみましょう。
static bool checkMachOAndArchFlags(....) { .... unsigned i; for (i = 0; i < ArchFlags.size(); ++i) { if (ArchFlags[i] == T.getArchName()) ArchFound = true; break; } .... }
PVS-Studio警告: V612ループ内の無条件の「ブレーク」。 MachODump.cpp 1206
breakステートメントに注意してください。 最初の反復の直後にループを中断します。 breakステートメントは条件に関連している必要があり、正しいコードは次のようになります。
for (i = 0; i < ArchFlags.size(); ++i) { if (ArchFlags[i] == T.getArchName()) { ArchFound = true; break; } }
さらに2つの類似した場所がありますが、記事が大きくなりすぎないように、アナライザーの警告のみを表示します。
- V612ループ内の無条件の「戻り」。 R600OptimizeVectorRegisters.cpp 54
- V612ループ内の無条件の「ブレーク」。 llvm-size.cpp 525
混乱した演算子|| および&&
static bool containsNoDependence(CharMatrix &DepMatrix, unsigned Row, unsigned Column) { for (unsigned i = 0; i < Column; ++i) { if (DepMatrix[Row][i] != '=' || DepMatrix[Row][i] != 'S' || DepMatrix[Row][i] != 'I') return false; } return true; }
PVS-Studio警告: V547式は常に真です。 ここでは、おそらく「&&」演算子を使用する必要があります。 LoopInterchange.cpp 208
この表現は意味がありません。 エラーの本質を強調するためにコードを単純化します:
if (X != '=' || X != 'S' || X != 'I')
変数Xは常に等しくないものになります。 その結果、条件は常に真になります。 おそらく、 「||」の代わりに 「&&」演算子を使用する必要があります。式は意味を持ちます。
この関数は、ローカルオブジェクトへのリンクを返します
SingleLinkedListIterator<T> &operator++(int) { SingleLinkedListIterator res = *this; ++*this; return res; }
PVS-Studio警告: V558関数は、一時ローカルオブジェクトへの参照を返します:res。 LiveInterval.h 679
この関数は、後置インクリメントの従来の実装を表します。
- 現在の状態は一時オブジェクトに保存されます。
- オブジェクトの現在の状態が変化します。
- オブジェクトの古い状態が返されます。
エラーは、関数がリンクを返すことです。 関数を終了すると、一時的なresオブジェクトが破棄されるため、このリンクは無効です。
状況を修正するには、リンクではなく値を返す必要があります。
SingleLinkedListIterator<T> operator++(int) { .... }
再割り当て
変数ZeroDirectiveを再割り当てする前に何らかの方法で使用されると誰も考えないように、関数全体を提供します。
HexagonMCAsmInfo::HexagonMCAsmInfo(const Triple &TT) { Data16bitsDirective = "\t.half\t"; Data32bitsDirective = "\t.word\t"; Data64bitsDirective = nullptr; ZeroDirective = "\t.skip\t"; // <= CommentString = "//"; LCOMMDirectiveAlignmentType = LCOMM::ByteAlignment; InlineAsmStart = "# InlineAsm Start"; InlineAsmEnd = "# InlineAsm End"; ZeroDirective = "\t.space\t"; // <= AscizDirective = "\t.string\t"; SupportsDebugInformation = true; MinInstAlignment = 4; UsesELFSectionDirectiveForBSS = true; ExceptionsType = ExceptionHandling::DwarfCFI; }
PVS-Studio警告: V519 「ZeroDirective」変数には値が連続して2回割り当てられます。 おそらくこれは間違いです。 行を確認してください:25、31。HexagonMCAsmInfo.cpp 31
ZeroDirective変数は、 const char *型の単純なポインターです。 最初は、文字列「\ t.skip \ t」を指し、そのすぐ下に文字列「\ t.space \ t」のアドレスが割り当てられます。 これは奇妙であり、意味がありません。 割り当ての1つが完全に異なる変数を変更する可能性が高いです。
別の再割り当てのケースを検討してください。
template <class ELFT> void GNUStyle<ELFT>::printFileHeaders(const ELFO *Obj) { .... Str = printEnum(e->e_ident[ELF::EI_OSABI], makeArrayRef(ElfOSABI)); printFields(OS, "OS/ABI:", Str); Str = "0x" + to_hexString(e->e_version); // <= Str = to_hexString(e->e_ident[ELF::EI_ABIVERSION]); // <= printFields(OS, "ABI Version:", Str); Str = printEnum(e->e_type, makeArrayRef(ElfObjectFileType)); printFields(OS, "Type:", Str); .... }
PVS-Studio警告: V519「Str」変数には連続して2回値が割り当てられます。 おそらくこれは間違いです。 行を確認してください:2407、2408。ELFDumper.cpp 2408
どうやらタイプミスを扱っているようです。 再割り当ての代わりに、 + =演算子を使用して2行を連結する必要がありました。 正しいコードは次のようになります。
Str = "0x" + to_hexString(e->e_version); Str += to_hexString(e->e_ident[ELF::EI_ABIVERSION]);
再割り当てが発生するコードのスニペットがいくつかあります。 私の意見では、これらの繰り返しの割り当ては危険を伴わないので、関連する警告を単純にリストします。
- V519変数には連続して2回値が割り当てられます。 おそらくこれは間違いです。 チェック行:55、57。coff2yaml.cpp 57
- V519「O」変数には、連続して2回値が割り当てられます。 おそらくこれは間違いです。 行を確認してください:394、395。llvm-pdbdump.cpp 395
- V519「servAddr.sin_family」変数には、連続して2回値が割り当てられます。 おそらくこれは間違いです。 行をチェック:63、64。server.cpp 64
スマートポインターを使用した疑わしい作業
Expected<std::unique_ptr<PDBFile>> PDBFileBuilder::build( std::unique_ptr<msf::WritableStream> PdbFileBuffer) { .... auto File = llvm::make_unique<PDBFile>( std::move(PdbFileBuffer), Allocator); File->ContainerLayout = *ExpectedLayout; if (Info) { auto ExpectedInfo = Info->build(*File, *PdbFileBuffer); .... }
PVS-Studio警告: V522ヌルポインター「PdbFileBuffer」の逆参照が行われる場合があります。 PDBFileBuilder.cpp 106
たとえば、 llvm :: make_uniqueが何であり 、どのように機能するかを研究しなかったため、コードは明確ではありません。 それでも、アナライザーは、一見すると、 PdbFileBufferスマートポインターからのオブジェクトの所有権がFileに移動することを心配しています 。 その後、スマートポインターPdbFileBufferは逆参照されます。理論的には、その時点ですでにnullptrが含まれています。 つまり、次のことは驚くべきことです。
.... llvm::make_unique<PDBFile>(::move(PdbFileBuffer), Allocator); .... .... Info->build(*File, *PdbFileBuffer);
これが誤りである場合は、同じファイル内の3つの場所で修正する必要があります。
- V522ヌルポインター「PdbFileBuffer」の逆参照が行われる場合があります。 PDBFileBuilder.cpp 113
- V522ヌルポインター「PdbFileBuffer」の逆参照が行われる場合があります。 PDBFileBuilder.cpp 120
- V522ヌルポインター「PdbFileBuffer」の逆参照が行われる場合があります。 PDBFileBuilder.cpp 127
条件のタイプミス
static bool areExclusiveRanges(BinaryOperatorKind OpcodeLHS, const APSInt &ValueLHS, BinaryOperatorKind OpcodeRHS, const APSInt &ValueRHS) { .... // Handle cases where the constants are different. if ((OpcodeLHS == BO_EQ || OpcodeLHS == BO_LE || // <= OpcodeLHS == BO_LE) // <= && (OpcodeRHS == BO_EQ || OpcodeRHS == BO_GT || OpcodeRHS == BO_GE)) return true; .... }
PVS-Studio警告: V501 「||」の左側と右側に同一の副次式「OpcodeLHS == BO_LE」があります 演算子。 RedundantExpressionCheck.cpp 174
これは典型的なタイプミスです。 OpcodeLHS変数は、定数BO_LE と 2回比較されます。 BO_LE定数の1つをBO_LTに置き換える必要があるように思えます。 ご覧のとおり、定数の名前は互いに似ており、混同しやすいです。
次の例は、静的解析が他の品質コードの記述方法をどのように補完するかを示しています。 エラーコードを考慮してください:
std::pair<Function *, Function *> llvm::createSanitizerCtorAndInitFunctions( .... ArrayRef<Type *> InitArgTypes, ArrayRef<Value *> InitArgs, ....) { assert(!InitName.empty() && "Expected init function name"); assert(InitArgTypes.size() == InitArgTypes.size() && "Sanitizer's init function expects " "different number of arguments"); ....
}
PVS-Studio警告: V501「==」演算子の左右に同じサブ式「InitArgTypes.size()」があります。 ModuleUtils.cpp 107
コードの信頼性を向上させる良い方法の1つは、 assert()マクロを使用することです。 このマクロと同様のマクロは、プログラムの開発およびデバッグ段階で多くのエラーを識別するのに役立ちます。 ただし、このようなマクロによってもたらされる利点の詳細な説明は行いません。これはこの記事の範囲を超えているためです。
私たちにとって重要なことは、 createSanitizerCtorAndInitFunctions()関数がassert()マクロを使用して入力値の正確さを検証することです。 それはちょうどタイプミスのためで、2番目のassert()は無用です。
幸いなことに、静的アナライザーが役に立ちます。これは、配列のサイズがそれ自体と比較されることに気づきます。 その結果、チェックを修正することができ、時間の経過とともにassert()の正しい条件が他のエラーの防止に役立ちます。
ほとんどの場合、条件はInitArgTypes配列とInitArgs配列のサイズを比較する必要があります 。
assert(InitArgTypes.size() == InitArgs.size() && "Sanitizer's init function expects " "different number of arguments");
リリース()とリセット()の混乱
std :: unique_ptrクラスには、 releaseおよびresetの 2つの子音関数があります。 私の観察が示すように、時々混乱します。 どうやらこれはここで起こった:
std::unique_ptr<DiagnosticConsumer> takeClient() { return std::move(Owner); } VerifyDiagnosticConsumer::~VerifyDiagnosticConsumer() { .... SrcManager = nullptr; CheckDiagnostics(); Diags.takeClient().release(); }
PVS-Studio警告: V530関数 'release'の戻り値を利用する必要があります。 VerifyDiagnosticConsumer.cpp 46
おそらく間違いはなく、いくつかの特別なトリッキーなロジックがここに隠れています。 しかし、リソースの流出のようなものです。 いずれにせよ、このコードは開発者がもう一度チェックすることを妨げません。
過剰条件
bool ARMDAGToDAGISel::tryT1IndexedLoad(SDNode *N) { LoadSDNode *LD = cast<LoadSDNode>(N); EVT LoadedVT = LD->getMemoryVT(); ISD::MemIndexedMode AM = LD->getAddressingMode(); if (AM == ISD::UNINDEXED || LD->getExtensionType() != ISD::NON_EXTLOAD || AM != ISD::POST_INC || LoadedVT.getSimpleVT().SimpleTy != MVT::i32) return false; .... }
警告PVS-Studio: V590この式の検査を検討してください。 表現が過剰であるか、誤植が含まれています。 ARMISelDAGToDAG.cpp 1565
条件は長いので、最も重要なことを強調します。
AM == ISD::UNINDEXED || AM != ISD::POST_INC
この状態は冗長であり、次のように簡略化できます。
AM != ISD::POST_INC
したがって、ここでは、条件または何らかのエラーの単純な冗長性を観察します。 おそらく冗長性は、他の条件を記述したかったことを示しています。 この場所がどれほど危険かを判断するつもりはありませんが、確認する価値はあります。 同時に、開発者の注意をさらに2つのアナライザー警告に引き付けたいと思います。
- V590この表現を調べることを検討してください。 表現が過剰であるか、誤植が含まれています。 ASTReader.cpp 4178
- V590この表現を調べることを検討してください。 表現が過剰であるか、誤植が含まれています。 中括弧AroundStatementsCheck.cpp 46
私のお気に入りの警告V595
CおよびC ++のポインターは、プログラマーにとって無限の頭痛の種です。 あなたはそれらをゼロにチェックします、あなたはチェックしますが、どこか-一度! -また、nullポインターの逆参照。 診断V595は、ゼロと等しいことを示すチェックポインターの発生が遅すぎる状況を明らかにします。 このチェックの前に、ポインターは既に使用されています。 これは、さまざまなアプリケーションのコードで見つかった最も一般的なエラーの1つです( 証明 )。 ただし、C / C ++の防衛では、C#では状況はそれほど良くないと言います。 C#のポインターは参照と呼ばれたという事実から、そのようなエラーは消えませんでした( 証拠 )
LLVMコードに戻って、簡単なエラーオプションを考えてみましょう。
bool PPCDarwinAsmPrinter::doFinalization(Module &M) { .... MachineModuleInfoMachO &MMIMacho = MMI->getObjFileInfo<MachineModuleInfoMachO>(); if (MAI->doesSupportExceptionHandling() && MMI) { .... }
PVS-Studio警告: V595 nullptrに対して検証される前に、「MMI」ポインターが使用されました。 行を確認してください:1357、1359。PPCAsmPrinter.cpp 1357
ケースはシンプルで、すべてがすぐに表示されます。 チェック(... && MMI)は、 MMIポインターがゼロになる可能性があることを示しています。 その場合、プログラムフローはこのチェックに到達しません。 nullポインターの逆参照により、より早く中断されます。
別のコードを検討してください。
void Sema::CodeCompleteObjCProtocolReferences( ArrayRef<IdentifierLocPair> Protocols) { ResultBuilder Results(*this, CodeCompleter->getAllocator(), CodeCompleter->getCodeCompletionTUInfo(), CodeCompletionContext::CCC_ObjCProtocolName); if (CodeCompleter && CodeCompleter->includeGlobals()) { Results.EnterNewScope(); .... }
PVS-Studio警告: V595 nullptrに対して検証される前に、「CodeCompleter」ポインターが使用されました。 行を確認してください:5952、5955。SemaCodeComplete.cpp 5952
CodeCompleterポインターは最初に逆参照され、既に以下はこのポインターがゼロに等しいかどうかのチェックです。 同じファイル内で同じコードがさらに3回見つかります。
- V595 nullptrに対して検証される前に、「CodeCompleter」ポインターが使用されました。 行を確認してください:5980、5983。SemaCodeComplete.cpp 5980
- V595 nullptrに対して検証される前に、「CodeCompleter」ポインターが使用されました。 行を確認してください:7455、7458。SemaCodeComplete.cpp 7455
- V595 nullptrに対して検証される前に、「CodeCompleter」ポインターが使用されました。 行をチェックします:7483、7476。SemaCodeComplete.cpp 7483
これらは単純なケースでしたが、より複雑なコードもあり、その危険性をすぐに言うことはできません。 したがって、開発者はLLVMコードの以下のセクションを個別に確認することをお勧めします。
- V595 nullptrに対して検証される前に、「Receiver」ポインターが使用されました。 行を確認してください:2543、2560。SemaExprObjC.cpp 2543
- V595 nullptrに対して検証される前に、「S」ポインターが使用されました。 行を確認してください:1267、1296。SemaLookup.cpp 1267
- V595 nullptrに対して検証される前に、「TargetDecl」ポインターが使用されました。 行を確認してください:4037、4046。CGExpr.cpp 4037
- V595 nullptrに対して検証される前に、「CurrentToken」ポインターが使用されました。 行を確認してください:705、708。TokenAnnotator.cpp 705
- V595 nullptrに対して検証される前に、「FT」ポインターが使用されました。 行を確認してください:540、554。Expr.cpp 540
- V595 nullptrに対して検証される前に、「II」ポインターが使用されました。 行を確認してください:448、450。IdentifierTable.cpp 448
- V595 nullptrに対して検証される前に、「MF」ポインターが使用されました。 行を確認してください:268、274。X86RegisterInfo.cpp 268
- V595 nullptrに対して検証される前に、「外部」ポインターが使用されました。 行を確認してください:40、45。HeaderSearch.cpp 40
- V595 nullptrに対して検証される前に、「TLI」ポインターが使用されました。 行を確認してください:4239、4244。CodeGenPrepare.cpp 4239
- V595 nullptrに対して検証される前に、 'SU-> getNode()'ポインターが使用されました。 行を確認してください:292、297。ResourcePriorityQueue.cpp 292
- V595 nullptrに対して検証される前に、「BO0」ポインターが使用されました。 行を確認してください:2835、2861。InstCombineCompares.cpp 2835
- V595 nullptrに対して検証される前に、「Ret」ポインターが使用されました。 行を確認してください:2090、2092。ObjCARCOpts.cpp 2090
奇妙なコード
読みにくいコードを引用して申し訳ありません。 辛抱してください、記事の終わりまであまり残っていません。
static bool print_class_ro64_t(....) { .... const char *r; uint32_t offset, xoffset, left; .... r = get_pointer_64(p, offset, left, S, info); if (r == nullptr || left < sizeof(struct class_ro64_t)) return false; memset(&cro, '\0', sizeof(struct class_ro64_t)); if (left < sizeof(struct class_ro64_t)) { memcpy(&cro, r, left); outs() << " (class_ro_t entends past the .......)\n"; } else memcpy(&cro, r, sizeof(struct class_ro64_t)); .... }
PVS-Studio警告: V649同一の条件式を持つ2つの「if」ステートメントがあります。 最初の「if」ステートメントには、関数の戻り値が含まれます。 これは、2番目の「if」ステートメントが無意味であることを意味します。 行を確認してください:4410、4413。MachODump.cpp 4413
チェックに注意してください:
if (.... || left < sizeof(struct class_ro64_t)) return false;
左の変数に含まれる値がクラスのサイズより小さい場合、関数は終了します。 この動作の選択は意味をなさないことがわかります。
if (left < sizeof(struct class_ro64_t)) { memcpy(&cro, r, left); outs() << " (class_ro_t entends past the .......)\n"; } else memcpy(&cro, r, sizeof(struct class_ro64_t));
条件は常にfalseであるため、elseブランチは常に実行されます。 これは非常に奇妙です。 おそらく、プログラムに論理エラーが含まれているか、何らかのタイプミスを処理しています。
同時に、次の場所を確認する必要があります。
- V649同一の条件式を持つ2つの「if」ステートメントがあります。 最初の「if」ステートメントには、関数の戻り値が含まれます。 これは、2番目の「if」ステートメントが無意味であることを意味します。 行を確認してください:4612、4615。MachODump.cpp 4615
その他のささいなこと
RPCテンプレートクラス内で、 SequenceNumberManagerクラスが宣言されています。 このような移動代入演算子があります:
SequenceNumberManager &operator=(SequenceNumberManager &&Other) { NextSequenceNumber = std::move(Other.NextSequenceNumber); FreeSequenceNumbers = std::move(Other.FreeSequenceNumbers); }
PVS-Studio警告: V591非void関数は値を返す必要があります。 RPCUtils.h 719
最後にわかるように、彼らはreturnを書くのを忘れていました:
return *this;
実際、心配することはありません。 コンパイラは、原則として、これらの関数が使用されていない場合、テンプレートクラスの関数本体で動作しません。 ここで、明らかに、これはまさにそうです。 私はチェックしませんでしたが、私は確信しています:そのような移動演算子を呼び出すと、コンパイラはコンパイルエラーまたは大きな警告を生成します。 ここにはひどいものは何もありませんが、この欠陥を指摘することにしました。
new演算子によって返されたポインターの値がゼロに等しいかどうかがチェックされるコードの奇妙なセクションがいくつかありました。メモリを割り当てることができない場合、例外std :: bad_allocをスローする必要があるため、このコードは意味がありません。これらの場所の1つを次に示します。
LLVMDisasmContextRef LLVMCreateDisasmCPUFeatures(....) { .... // Set up the MCContext for creating symbols and MCExpr's. MCContext *Ctx = new MCContext(MAI, MRI, nullptr); if (!Ctx) return nullptr; .... }
PVS-Studio警告: V668メモリが「new」演算子を使用して割り当てられたため、「Ctx」ポインターをnullに対してテストしても意味がありません。メモリ割り当てエラーの場合、例外が生成されます。Disassembler.cpp 76
さらに2つの警告:
- V668メモリーが「新規」演算子を使用して割り振られたため、「DC」ポインターをヌルに対してテストする意味はありません。 メモリ割り当てエラーの場合、例外が生成されます。 Disassembler.cpp 103
- V668メモリが「new」演算子を使用して割り当てられたため、「JITCodeEntry」ポインターをnullに対してテストする意味がありません。 メモリ割り当てエラーの場合、例外が生成されます。 GDBRegistrationListener.cpp 180
コードのこれらのセクションは危険に見えないため、重要ではない警告のセクションで説明することにしました。ほとんどの場合、これら3つのチェックはすべて簡単に削除できます。
おわりに
ご覧のとおり、コンパイラの警告は適切ですが、十分ではありません。PVS-Studioなどの静的分析専用ツールは、アラートを処理する際の診断機能と構成の柔軟性において、常にコンパイラーよりも優れています。実際、これは分析装置の開発者がお金を稼ぐ場所です。
また、静的解析の方法論を適用する主な効果は、静的コードアナライザーを定期的に使用することで達成されることに注意することも重要です。多くのエラーは最も早い段階で検出されるため、プログラムのクラッシュにつながる一連のアクションを詳細に説明するためにデバッグしたりユーザーに頼んだりする必要はありません。コンパイラの警告との完全な類似性を次に示します(実際、これらは同じ警告ですが、よりインテリジェントです)。月に一度ではなく、常にコンパイラの警告が表示されますか?!プロジェクトのコードでPVS-Studio
をダウンロードしてお試しください。
この記事を英語圏の聴衆と共有したい場合は、翻訳へのリンクを使用してください:Andrey karpov。 PVS-Studioの助けを借りて、LLVMプロジェクトのコードのバグを見つける。
記事を読んで質問がありますか?