
Features of setting up and running PVS-Studio in Docker using the Azure Service Fabric code example

Containerization technologies are actively used for the assembly and testing of software. With the advent of PVS-Studio for Linux, users now have the opportunity to add static analysis to other methods of testing their project on this platform, including Docker. The article will describe the features of working with the PVS-Studio analyzer in Docker, which will improve the quality of analysis and ease of use. It will also list errors found in the Azure Service Fabric project.
Introduction
Docker is a program that allows the operating system to run processes in an isolated environment based on specially created images. Containerization technology has become very common for many tasks, including software development and testing. Static analysis is usually performed in the same environment as the project assembly, so its use in Docker is very easy to implement in existing containers.
Examples of integration and launch of the PVS-Studio static analyzer will be given for the Linux version. But the described analyzer tuning options are possible and even recommended on any platform. The macOS version of the analyzer , which was recently presented to the public, is generally identical in using PVS-Studio for Linux.
Azure Service Fabric was selected as the project for integration and launch of the analyzer in Docker. Service Fabric is a distributed system platform for deploying and managing scalable and highly reliable distributed applications. Service Fabric runs on Windows and Linux, in any cloud, any data center, any region, and even on a laptop.
Phased implementation of the analyzer
First, let's see how the project is assembled in order to choose the analyzer integration method. The order of calling scripts and commands is as follows:

The following is a snippet of the build.sh script where the project file is generated:
cmake ${CMakeGenerator} \
-DCMAKE_C_COMPILER=${CC} \
-DCMAKE_CXX_COMPILER=${CXX} \
-DCMAKE_BUILD_TYPE=${BuildType} \
-DBUILD_THIRD_PARTY=${BuildThirdPartyLib} \
${DisablePrecompileFlag} ${ScriptPath}/$DirName
To analyze the project, I decided to use the method from the documentation described in the Quick Start / CMake project section :
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
Adding an analyzer installation:
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
+
The src directory is part of the project and is mounted in / src . I also marked out the configuration file of the PVS-Studio.cfg analyzer . Then the analyzer can be called as follows:
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))
I started the analyzer before building the project. This is not the right decision, but there are a lot of conditions in the script under which the assembly of the project starts, so I simplified my task a bit and compiled the project in advance. Developers who better know the structure of their project should integrate the analyzer after building the project.
Now you can assemble and analyze the project with the following command:
sudo ./runbuild.sh -release -j4
The first analysis results frustrate with warnings on numerous macros, nonexistent files, incorrect paths to source code files, etc. In the next section, I will talk about the contents of the PVS-Studio.cfg file , where I added several settings that greatly improved the analysis.
Additional analyzer setup
Relative path to the source directory.
To view one report on different computers, the analyzer can generate a report with relative paths to files. You can restore them on another computer using the converter.
A similar configuration of the analyzer must be performed in order to extract a report from the container with the correct file paths. The project root directory is mounted in root, so the analyzer parameter will look like this:
sourcetree-root=/
Warnings for nonexistent files
The container expands the / external directory , which is not in the repository. Most likely, some project dependencies are compiled in it and you can simply exclude them from the analysis:
exclude-path=/external
Warnings for Compiler Files, Tests, and Libraries
In Docker, the compiler can be placed in a non-standard place and its libraries can be reported. They also need to be excluded. To do this, the / deps directory and the test directory are excluded from the scan :
exclude-path=/deps
exclude-path=/src/prod/test
Fighting thousands of false positives arising from unsuccessful macros
The analyzer supports the configuration of various diagnostics using comments. You can read about them here and here .
Settings can be placed in the project code or put in a separate file, as I did:
rules-config=/src/service-fabric.pvsconfig
The contents of the service-fabric.pvsconfig file:
#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
Several lines of custom markup remove thousands of warnings for macros from the report.
Other settings
The path to the license file and the inclusion of only general-purpose diagnostics (to speed up the analysis):
lic-file=/src/PVS-Studio.lic
analysis-mode=4
The whole PVS-Studio.cfg file
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=/
May be needed in other projects
Another way to verify the project requires the strace system utility . Most likely, it will be absent in the container and you need to add the installation step of this utility from the repository to the script.
A compiler with a non-standard name, for example, a cross-compiler, can be placed in a container. I already wrote that the compiler directory should be excluded from the analysis, but in this case, you will also have to pass the name of the new compiler to the analyzer:
pvs-studio-analyzer analyze ... --compiler COMPILER_NAME...
You can duplicate a flag to specify multiple compilers.
View report on Linux or Windows
To view the analyzer report in Linux, you can add the report generation command in the required format to the script.
For example, to view in QtCreator:
plog-converter -t tasklist -r "~/Projects/service-fabric" \
./service-fabric-pvs.log -o ./service-fabric-pvs.tasks
Or in the browser:
plog-converter -t fullhtml -r "~/Projects/service-fabric" \
./service-fabric-pvs.log -o ./
To view the report in Windows, you can simply open the .log file in the Standalone utility , which is included in the distribution kit for Windows.
Sample Errors from Azure Service Fabric
Classic typos

V501 CWE-571 There are identical sub-expressions to the left and to the right of the '==' operator: iter-> PackageName == iter-> PackageName DigestedApplicationDescription.cpp 247
ErrorCode
DigestedApplicationDescription::ComputeAffectedServiceTypes(....)
{
....
if (iter->PackageName == iter->PackageName &&
originalRG != this->ResourceGovernanceDescriptions.end() &&
targetRG != targetDescription.ResourceGovernanceDes....end())
{
....
}
....
}
The variable iter-> PackageName should be compared with iter2-> PackageName or codePackages .
V501 CWE-571 There are identical sub-expressions '(dataSizeInRecordIoBuffer> 0)' to the left and to the right of the '&&' operator. OverlayStream.cpp 4966
VOID
OverlayStream::AsyncMultiRecordReadContextOverlay::FSMContinue(
__in NTSTATUS Status
)
{
ULONG dataSizeInRecordMetadata = 0;
ULONG dataSizeInRecordIoBuffer = 0;
....
if ((dataSizeInRecordIoBuffer > 0) &&
(dataSizeInRecordIoBuffer > 0))
{
....
}
....
}
Due to Copy-Paste, the dataSizeInRecordMetadata buffer size is not checked .
V534 CWE-691 It is likely that a wrong variable is being compared inside the 'for' operator. Consider reviewing '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++)
{
...
}
....
}
....
}
Probably, in the condition of the nested loop, the variable ix1 , not ix0 , should be checked .
V570 The 'statusDetails_' variable is assigned to itself. 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;
}
Most likely, they wanted to take the value of the statusDetails_ field from other.statusDetails_ , but they made a typo.
V606 Ownerless token 'false'. CryptoUtility.Linux.h 81
template
static bool MapCompare(const std::map& lhs,
const std::map& rhs)
{
if (lhs.size() != rhs.size()) { false; }
return std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
The missing keyword return led to the fact that the code became not optimal. Because of a typo, a quick check on the size of collections does not work as the author intended.
V607 CWE-482 Ownerless expression. 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;
}
A typo is similar to the previous example, but leads to a more serious error. The result of one of the comparisons is never saved. The correct code should be like this:
equals = this->CodePackageRef == other.CodePackageRef;
if (!equals) { return equals; }
Incorrect use of functions
V521 CWE-480 Such expressions using the ',' operator are dangerous. Make sure the expression is correct. ReplicatedStore.SecondaryPump.cpp 1231
ErrorCode
ReplicatedStore::SecondaryPump::ApplyOperationsWithRetry(....)
{
....
if (errorMessage.empty())
{
errorMessage = L"error details missing: LSN={0}", operationLsn;
Assert::TestAssert("{0}", errorMessage);
}
....
}
The analyzer detected strange code for generating a message in the errorMessage variable . Judging by the neighboring code fragments, it is necessary to write here:
WriteInfo(errorMessage, L"error ....: LSN={0}", operationLsn);
V547 CWE-570 Expression 'nwrite <0' is always false. Unsigned type value is never <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);
....
}
Incorrect check of the return value of the fwrite () function . Documentation for this feature can be found at cppreference.com and cplusplus.com .
V547 CWE-571 Expression 'len> = 0' is always true. Unsigned type value is always> = 0. Types.cpp 121
size_t BIO_ctrl_pending(BIO *b);
template
TBuf BioMemToTBuf(BIO* bio)
{
char* data = NULL;
auto len = BIO_ctrl_pending(bio);
Invariant(len >= 0);
....
}
Incorrect check of the return value of the function from the OpenSSL library. This could very well be a serious mistake or even vulnerability.
About pointers and memory
V603 CWE-665 The object was created but it is not being used. If you wish to call constructor, 'this-> JsonBufferManager2 :: JsonBufferManager2 (....)' should be used. JsonReader.h 48
class JsonBufferManager2
{
template
friend struct JsonBufferManagerTraits;
public:
JsonBufferManager2()
{
JsonBufferManager2(nullptr, 0);
}
....
}
Probably, they wanted to call another from one constructor. But in fact, a temporary object of the JsonBufferManager2 class is created and then destroyed. This type of error is described in more detail in the article “ Do not know the ford, do not get into the water: part one ”. The same article describes how you can call one constructor from another.
V568 It's odd that 'sizeof ()' operator evaluates the size of a pointer to a class, but not the size of the 'thisPtr' class object. 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)); // <=
}
The correct sizeof () was passed to the write () function , but the result of the read function, most likely, should be compared with the size of the recorded object:
Invariant(written == sizeof(*thisPtr));
V595 CWE-476 The 'globalDomain' pointer was utilized before it was verified against nullptr. Check lines: 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;
}
}
}
A classic mistake when working with the globalDomain pointer : dereferencing first, then checking.
V611 CWE-762 The memory was allocated using 'new T []' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use '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;
}
There were many places where the memory allocated for the array is freed in the wrong way. You need to use delete [] .
Running the analyzer in containers with Windows
In this case, starting the analyzer is not very different from automating analysis, for example, in Jenkins on a real computer. We ourselves use Docker to test PVS-Studio for Windows. It is enough to install the analyzer:
START /w PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES \
/NORESTART /COMPONENTS=Core,Standalone
and start an analysis of your project:
"C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" ...
Conclusion
The focus of the article was on interesting containerization technology, which is not an obstacle to the integration of static analysis in your project. Therefore, the found warnings of PVS-Studio were shortened in the article, but are fully available for download in a format for the browser: service-fabric-pvs-studio-html.7z .
I suggest everyone to download and try PVS-Studio on their project. The analyzer works on Windows, Linux and macOS!
If you want to share this article with an English-speaking audience, then please use the link to the translation: Svyatoslav Razmyslov. Features of PVS-Studio setting and running in Docker on the example of Azure Service Fabric code.
Have you read the article and have a question?
Often our articles are asked the same questions. We collected the answers here: Answers to questions from readers of articles about PVS-Studio, version 2015 . Please see the list.