Azure Service Fabricコード例を使用してDockerでPVS-Studioをセットアップおよび実行する機能











コンテナ化技術は、ソフトウェアの組み立てとテストに積極的に使用されています。 PVS-Studio for Linuxの登場により、ユーザーはDockerを含むこのプラットフォームでプロジェクトをテストする他の方法に静的分析を追加できるようになりました。 この記事では、DockerでPVS-Studioアナライザーを操作する機能について説明します。これにより、分析の品質と使いやすさが向上します。 また、Azure Service Fabricプロジェクトで見つかったエラーもリストします。



はじめに



Dockerは、オペレーティングシステムが特別に作成されたイメージに基づいて隔離された環境でプロセスを開始できるようにするプログラムです。 コンテナ化技術は、ソフトウェアの開発やテストなど、多くのタスクで非常に一般的になっています。 通常、静的解析はプロジェクトアセンブリと同じ環境で実行されるため、Dockerでの使用は既存のコンテナに非常に簡単に実装できます。



LinuxバージョンのPVS-Studio静的アナライザーの統合と起動の例を示します。 ただし、説明されているアナライザーのチューニングオプションは可能ですが、どのプラットフォームでも推奨されます。 最近公開されたmacOSバージョンのアナライザーは、PVS-Studio for Linuxを使用する場合とほぼ同じです。



Azure Service Fabricは、Dockerでのアナライザーの統合と起動のプロジェクトとして選択されました。 Service Fabricは、スケーラブルで信頼性の高い分散アプリケーションを展開および管理するための分散システムプラットフォームです。 Service Fabricは、WindowsおよびLinux、任意のクラウド、任意のデータセンター、任意の地域、さらにラップトップ上で実行されます。



アナライザーの段階的な実装



まず、アナライザーの統合方法を選択するためにプロジェクトがどのように組み立てられるかを見てみましょう。 スクリプトとコマンドを呼び出す順序は次のとおりです。













以下は、プロジェクトファイルが生成されるbuild.shスクリプトのスニペットです。



cmake ${CMakeGenerator} \ -DCMAKE_C_COMPILER=${CC} \ -DCMAKE_CXX_COMPILER=${CXX} \ -DCMAKE_BUILD_TYPE=${BuildType} \ -DBUILD_THIRD_PARTY=${BuildThirdPartyLib} \ ${DisablePrecompileFlag} ${ScriptPath}/$DirName
      
      





プロジェクトを分析するために、 クイックスタート/ CMakeプロジェクトセクションで説明されているドキュメントのメソッドを使用することにしました。

 diff --git a/src/build.sh b/src/build.sh index 290c57d..5901fd6 100755 --- a/src/build.sh +++ b/src/build.sh @@ -179,6 +179,7 @@ BuildDir() -DCMAKE_CXX_COMPILER=${CXX} \ -DCMAKE_BUILD_TYPE=${BuildType} \ -DBUILD_THIRD_PARTY=${BuildThirdPartyLib} \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=On \ ${DisablePrecompileFlag} ${ScriptPath}/$DirName if [ $? != 0 ]; then let TotalErrors+=1
      
      





アナライザーインストールの追加:

 diff --git a/src/build.sh b/src/build.sh index 290c57d..581cbaf 100755 --- a/src/build.sh +++ b/src/build.sh @@ -156,6 +156,10 @@ BuildDir() CXX=${ProjRoot}/deps/third-party/bin/clang/bin/clang++ fi + dpkg -i /src/pvs-studio-6.23.25754.2246-amd64.deb + apt -f install -y + pvs-studio --version +
      
      





srcディレクトリはプロジェクトの一部であり、 / srcにマウントされます。 PVS-Studio.cfgアナライザーの構成ファイルにもマークを付けました。 次に、アナライザーを次のように呼び出すことができます。



 diff --git a/src/build.sh b/src/build.sh index 290c57d..2a286dc 100755 --- a/src/build.sh +++ b/src/build.sh @@ -193,6 +193,9 @@ BuildDir() cd ${ProjBinRoot}/build.${DirName} + pvs-studio-analyzer analyze --cfg /src/PVS-Studio.cfg \ + -o ./service-fabric-pvs.log -j4 + if [ "false" = ${SkipBuild} ]; then if (( $NumProc <= 0 )); then NumProc=$(($(getconf _NPROCESSORS_ONLN)+0))
      
      





プロジェクトをビルドする前にアナライザーを開始しました。 これは正しい決定ではありませんが、スクリプトにはプロジェクトアセンブリを開始するための多くの条件があるため、タスクを少し簡略化し、事前にプロジェクトをコンパイルしました。 プロジェクトの構造をよく理解している開発者は、プロジェクトの構築にアナライザーを統合する必要があります。



これで、次のコマンドを使用してプロジェクトを組み立てて分析できます。



 sudo ./runbuild.sh -release -j4
      
      





最初の分析結果は、多数のマクロ、存在しないファイル、ソースコードファイルへの誤ったパスなどに関する警告で失望します。 次のセクションでは、分析を大幅に改善するいくつかの設定を追加したPVS-Studio.cfgファイルの内容について説明します



追加のアナライザーのセットアップ



ソースディレクトリへの相対パス



別のコンピューターで1つのレポートを表示するために、アナライザーはファイルへの相対パスを含むレポートを生成できます。 コンバーターを使用して別のコンピューターで復元できます。



正しいファイルパスを使用してコンテナからレポートを抽出するには、アナライザーの同様の構成を実行する必要があります。 プロジェクトのルートディレクトリはルートにマウントされるため、アナライザーのパラメーターは次のようになります。



 sourcetree-root=/
      
      





存在しないファイルの警告



リポジトリにない/外部ディレクトリは、コンテナで展開されます。 ほとんどの場合、いくつかのプロジェクトの依存関係がコンパイルされており、分析から単純に除外できます。



 exclude-path=/external
      
      





コンパイラファイル、テスト、およびライブラリの警告



Dockerでは、コンパイラを非標準の場所に配置し、そのライブラリをレポートに含めることができます。 それらも除外する必要があります。 これを行うには、 / depsディレクトリとテストのあるディレクトリをスキャンから除外します。



 exclude-path=/deps exclude-path=/src/prod/test
      
      





失敗したマクロから生じる数千の誤検知との戦い



アナライザーは、コメントを使用したさまざまな診断の構成をサポートしています。 それらについては、 ここここで読むことができます



設定はプロジェクトコードに配置するか、別のファイルに配置できます。



 rules-config=/src/service-fabric.pvsconfig
      
      





service-fabric.pvsconfigファイルの内容:



 #V501 //-V:CODING_ERROR_ASSERT:501 //-V:TEST_CONFIG_ENTRY:501 //-V:VERIFY_IS_TRUE:501 //-V:VERIFY_ARE_EQUAL:501 //-V:VERIFY_IS_FALSE:501 //-V:INTERNAL_CONFIG_ENTRY:501 //-V:INTERNAL_CONFIG_GROUP:501 //-V:PUBLIC_CONFIG_ENTRY:501 //-V:PUBLIC_CONFIG_GROUP:501 //-V:DEPRECATED_CONFIG_ENTRY:501 //-V:TR_CONFIG_PROPERTIES:501 //-V:DEFINE_SECURITY_CONFIG_ADMIN:501 //-V:DEFINE_SECURITY_CONFIG_USER:501 //-V:RE_INTERNAL_CONFIG_PROPERTIES:501 //-V:RE_CONFIG_PROPERTIES:501 //-V:TR_INTERNAL_CONFIG_PROPERTIES:501 #V523 //-V:TEST_COMMIT_ASYNC:523 #V640 //-V:END_COM_INTERFACE_LIST:640
      
      





カスタムマークアップの数行は、レポートからマクロから数千の警告を削除します。



その他の設定



ライセンスファイルへのパスと汎用診断のみを含める(分析を高速化する):



 lic-file=/src/PVS-Studio.lic analysis-mode=4
      
      





PVS-Studio.cfgファイル全体

 lic-file=/src/PVS-Studio.lic rules-config=/src/service-fabric.pvsconfig exclude-path=/deps exclude-path=/external exclude-path=/src/prod/test analysis-mode=4 sourcetree-root=/
      
      





他のプロジェクトで必要になる場合があります



プロジェクトを検証する別の方法には、 straceシステムユーティリティが必要です。 ほとんどの場合、コンテナには存在しないため、このユーティリティのインストール手順をリポジトリからスクリプトに追加する必要があります。



クロスコンパイラなどの非標準名のコンパイラは、コンテナに配置できます。 コンパイラのディレクトリを分析から除外する必要があることは既に書きましたが、この場合、新しいコンパイラの名前もアナライザに渡す必要があります。

 pvs-studio-analyzer analyze ... --compiler COMPILER_NAME...
      
      





フラグを複製して、複数のコンパイラを指定できます。



LinuxまたはWindowsでレポートを表示する



Linuxでアナライザーレポートを表示するには、レポート生成コマンドを必要な形式でスクリプトに追加できます。



たとえば、QtCreatorで表示するには:



 plog-converter -t tasklist -r "~/Projects/service-fabric" \ ./service-fabric-pvs.log -o ./service-fabric-pvs.tasks
      
      





またはブラウザで:



 plog-converter -t fullhtml -r "~/Projects/service-fabric" \ ./service-fabric-pvs.log -o ./
      
      





Windowsでレポートを表示するには、Windows用の配布キットに含まれているスタンドアロンユーティリティで.logファイルを開くだけです。



Azure Service Fabricのサンプルエラー



古典的なタイプミス













V501 CWE-571「==」演算子の左右に同じ副次式があります。iter-> PackageName == iter-> PackageName DigestedApplicationDescription.cpp 247



 ErrorCode DigestedApplicationDescription::ComputeAffectedServiceTypes(....) { .... if (iter->PackageName == iter->PackageName && originalRG != this->ResourceGovernanceDescriptions.end() && targetRG != targetDescription.ResourceGovernanceDes....end()) { .... } .... }
      
      





変数iter-> PackageNameはiter2-> PackageNameまたはcodePackagesと比較する必要があります。



V501 CWE-571「&&」演算子の左側と右側に同一のサブ式「(dataSizeInRecordIoBuffer> 0)」があります。 OverlayStream.cpp 4966



 VOID OverlayStream::AsyncMultiRecordReadContextOverlay::FSMContinue( __in NTSTATUS Status ) { ULONG dataSizeInRecordMetadata = 0; ULONG dataSizeInRecordIoBuffer = 0; .... if ((dataSizeInRecordIoBuffer > 0) && (dataSizeInRecordIoBuffer > 0)) { .... } .... }
      
      





Copy-Pasteにより、 dataSizeInRecordMetadataバッファサイズはチェックされません。



V534 CWE-691「for」演算子内で間違った変数が比較されている可能性があります。 「ix0」の確認を検討してください。 RvdLoggerVerifyTests.cpp 2395



 NTSTATUS ReportLogStateDifferences(....) { .... for (ULONG ix0=0; ix0 < RecoveredState._NumberOfStreams; ix0++) { KWString streamId(....); ULONG ix1; for (ix1 = 0; ix0 < LogState._NumberOfStreams; ix1++) { ... } .... } .... }
      
      





ネストされたループの状態では、おそらくix0ではなく変数ix1をチェックする必要があります。



V570 「statusDetails_」変数はそれ自体に割り当てられます。 ComposeDeploymentStatusQueryResult.cpp 49



 ComposeDeploymentStatusQueryResult & ComposeDeploymentStatusQueryResult::operator = ( ComposeDeploymentStatusQueryResult && other) // <= { if (this != & other) { deploymentName_ = move(other.deploymentName_); applicationName_ = move(other.applicationName_); dockerComposeDeploymentStatus_ = move(other....); statusDetails_ = move(statusDetails_); // <= } return *this; }
      
      





ほとんどの場合、 彼らother.statusDetails_からstatusDetails_フィールドの値を取得したかったのですが、タイプミスをしました。



V606所有者なしトークン「false」。 CryptoUtility.Linux.h 81



 template <typename TK, typename TV> static bool MapCompare(const std::map<TK, TV>& lhs, const std::map<TK, TV>& rhs) { if (lhs.size() != rhs.size()) { false; } return std::equal(lhs.begin(), lhs.end(), rhs.begin()); }
      
      





キーワードが見つからないため、コードが最適ではなくなったという事実に至りました。 タイプミスのため、コレクションのサイズのクイックチェックは、作成者が意図したとおりに機能しません。



V607 CWE-482 所有者のない表現。 EnvironmentOverrideDescription.cpp 60



 bool EnvironmentOverridesDescription::operator == (....) const { bool equals = true; for (auto i = 0; i < EnvironmentVariables.size(); i++) { equals = EnvironmentVariables[i] == other.EnvironmentVariables[i]; if (!equals) { return equals; } } this->CodePackageRef == other.CodePackageRef; // <= if (!equals) { return equals; } return equals; }
      
      





タイプミスは前の例と似ていますが、より深刻なエラーにつながります。 比較の1つの結果は保存されません。 正しいコードは次のようになります。



 equals = this->CodePackageRef == other.CodePackageRef; if (!equals) { return equals; }
      
      





関数の誤った使用



V521 CWE-480「、」演算子を使用したこのような式は危険です。 式が正しいことを確認してください。 ReplicatedStore.SecondaryPump.cpp 1231



 ErrorCode ReplicatedStore::SecondaryPump::ApplyOperationsWithRetry(....) { .... if (errorMessage.empty()) { errorMessage = L"error details missing: LSN={0}", operationLsn; Assert::TestAssert("{0}", errorMessage); } .... }
      
      





アナライザーは、 errorMessage変数にメッセージを生成するための奇妙なコードを検出しました。 隣接するコードフラグメントから判断すると、次のように記述する必要があります。



 WriteInfo(errorMessage, L"error ....: LSN={0}", operationLsn);
      
      





V547 CWE-570式 'nwrite <0'は常にfalseです。 符号なしの型の値が<0になることはありません。File.cpp 1941



 static void* ScpWorkerThreadStart(void* param) { .... do { size_t nwrite = fwrite(ptr, 1, remaining, destfile); if (nwrite < 0) { pRequest->error_.Overwrite(ErrorCode::FromErrno(errno)); break; } else { remaining -= nwrite; ptr += nwrite; pRequest->szCopied_ += nwrite; } } while (remaining != 0); .... }
      
      





fwrite()関数の戻り値の誤ったチェック。 この機能のドキュメントはcppreference.comおよびcplusplus.comにあります。



V547 CWE-571式 'len> = 0'は常にtrueです。 符号なしの型の値は常に> = 0です。Types.cpp121



 size_t BIO_ctrl_pending(BIO *b); template <typename TBuf> TBuf BioMemToTBuf(BIO* bio) { char* data = NULL; auto len = BIO_ctrl_pending(bio); Invariant(len >= 0); .... }
      
      





OpenSSLライブラリからの関数の戻り値の誤ったチェック。 これは、重大な間違いまたは脆弱性である可能性があります。



ポインターとメモリについて



V603 CWE-665オブジェクトは作成されましたが、使用されていません。 コンストラクターを呼び出す場合は、「this-> JsonBufferManager2 :: JsonBufferManager2(....)」を使用する必要があります。 JsonReader.h 48



 class JsonBufferManager2 { template<typename T> friend struct JsonBufferManagerTraits; public: JsonBufferManager2() { JsonBufferManager2(nullptr, 0); } .... }
      
      





おそらく、あるコンストラクターから別のコンストラクターを呼び出したいと考えていました。 しかし実際には、 JsonBufferManager2クラスの一時オブジェクトが作成され 、すぐに破棄されます。 このタイプのエラーについては、記事「 フォードを知らない、水に入らない:パート1 」で詳しく説明されています 。 同じ記事では、あるコンストラクターを別のコンストラクターから呼び出す方法について説明しています。



V568 「sizeof()」演算子がクラスへのポインタのサイズを評価するのは奇妙ですが、「thisPtr」クラスオブジェクトのサイズは評価しません。 TimerQueue.cpp 443



 void TimerQueue::SigHandler(int sig, siginfo_t *si, void*) { TimerQueue* thisPtr = (TimerQueue*)si->si_value.sival_ptr; auto written = write(thisPtr->pipeFd_[1], &thisPtr, sizeof(thisPtr)); Invariant(written == sizeof(thisPtr)); // <= }
      
      





正しいsizeof ()がwrite()関数渡されましたが、読み取り関数の結果は、ほとんどの場合、記録されたオブジェクトのサイズと比較する必要があります。



 Invariant(written == sizeof(*thisPtr));
      
      





V595 CWE-476 nullptrに対して検証される前に、「globalDomain」ポインターが使用されました。 行を確認してください:196、197。PlacementReplica.cpp 196



 void PlacementReplica::ForEachWeightedDefragMetric(....) const { .... size_t metricIndexInGlobalDomain = totalMetricIndexInGloba.... - globalDomain->MetricStartIndex; if (globalDomain != nullptr && globalDomain->Metrics[metricIndexInGlobalDomain].Weight > 0) { if (!processor(totalMetricIndexInGlobalDomain)) { break; } } }
      
      





globalDomainポインターを使用する際の典型的な間違い:最初に逆参照し、次にチェックします。



V611 CWE-762メモリは「new T []」演算子を使用して割り当てられましたが、「delete」演算子を使用して解放されました。 このコードを調べることを検討してください。 「delete [] groups;」を使用することをお勧めします。 PAL.cpp 4733



 NET_API_STATUS NetUserGetLocalGroups(....) { string unameA = utf16to8(UserName).substr(0, ACCT_NAME_MAX); int ngroups = 50; gid_t *groups = new gid_t[ngroups]; gid_t gid; .... delete groups; return NERR_Success; }
      
      





配列に割り当てられたメモリが間違った方法で解放される多くの場所がありました。 delete []を使用する必要があります。



Windowsのコンテナーでアナライザーを実行する



この場合、アナライザーの起動は、たとえば実際のコンピューターのJenkinsでの分析の自動化とそれほど変わりません。 私たちはDockerを使用してPVS-Studio for Windowsをテストしています。 アナライザーをインストールするだけで十分です。



 START /w PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES \ /NORESTART /COMPONENTS=Core,Standalone
      
      





プロジェクトの分析を開始します。



 "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" ...
      
      





おわりに



この記事の焦点は、興味深いコンテナー化テクノロジーにありました。これは、静的分析をプロジェクトに統合する上での障害にはなりません。 そのため、PVS-Studioで見つかった警告はこの記事では短縮されましたが、ブラウザーの形式( service-fabric-pvs-studio-html.7z)で完全にダウンロードできます。



誰もが自分のプロジェクトでPVS-Studioをダウンロードして試すことをお勧めします。 このアナライザーは、Windows、Linux、およびmacOSで動作します!









英語を話す聴衆とこの記事を共有したい場合は、翻訳へのリンクを使用してください:Svyatoslav Razmyslov。 Azure Service Fabricコードの例でのDockerでのPVS-Studio設定および実行の機能。



記事を読んで質問がありますか?
多くの場合、記事には同じ質問が寄せられます。 ここで回答を収集しました: PVS-Studioバージョン2015に関する記事の読者からの質問への回答 。 リストをご覧ください。



All Articles