﻿# Особенности настройки и запуска PVS\-Studio в Docker на примере кода Azure Service Fabric

Технологии контейнеризации активно используются для сборки и тестирования программного обеспечения\. С появлением PVS\-Studio для Linux, пользователям стала доступна возможность добавить статический анализ к другим методам тестирования своего проекта на этой платформе, в том числе в Docker\. В статье будут описаны особенности работы с анализатором PVS\-Studio в Docker, которые повысят качество анализа и удобство использования\. А также будут приведены ошибки, найденные в проекте Azure Service Fabric\.

![0567_DockerServiceFabric_ru/image1.png](https://import.viva64.com/docx/blog/0567_DockerServiceFabric_ru/image1.png)

## Введение

Docker — программа, позволяющая операционной системе запускать процессы в изолированном окружении на базе специально созданных образов\. Технология контейнеризации стала очень распространённой для многих задач, включая разработку и тестирование программного обеспечения\. Статический анализ обычно выполняется в том же окружении, что и сборка проекта, поэтому его использование в Docker очень просто реализуется в уже существующих контейнерах\.

Примеры интеграции и запуска статического анализатора [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) будут приведены для Linux версии\. Но описанные возможности настройки анализатора возможны и даже рекомендуются на любой платформе\. Версия анализатора под [macOS](https://pvs-studio.ru/ru/blog/posts/cpp/0566/), которая недавно была представлена общественности, вообще идентична в использовании PVS\-Studio для Linux\.

В качестве проекта для интеграции и запуска анализатора в Docker выбран Azure Service Fabric\. [Service Fabric](https://github.com/Microsoft/service-fabric) \- это платформа для распределенных систем, предназначенная для развертывания и управления масштабируемыми и высоконадежными распределенными приложениями\. Service Fabric работает на Windows и Linux, в любом облаке, любом дата\-центре, любом регионе и даже на ноутбуке\.

## Поэтапное внедрение анализатора

Для начала посмотрим, как выполняется сборка проекта, чтобы выбрать способ интеграции анализатора\. Порядок вызова скриптов и команд выглядит следующим образом:

![0567_DockerServiceFabric_ru/image2.png](https://import.viva64.com/docx/blog/0567_DockerServiceFabric_ru/image2.png)

Ниже представлен фрагмент скрипта _build\.sh_, где генерируется проектный файл:

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

Для анализа проекта я решил воспользоваться способом из документации, описанном в разделе [Быстрый старт/CMake\-проект](https://pvs-studio.ru/ru/docs/manual/0036/):

```cpp
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
```

Добавление установки анализатора:

```cpp
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_\. Тогда вызов анализатора можно выполнить следующим образом:

```cpp
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))
```

Запуск анализатора я сделал перед сборкой проекта\. Это не является правильным решением, но в скрипте очень много условий, при которых запускается сборка проекта, поэтому я немного упростил себе задачу и скомпилировал проект заранее\. Разработчикам, которые лучше знают структуру своего проекта, следует интегрировать анализатор **после** сборки проекта\.

Теперь собрать и проанализировать проект можно следующей командой:

```cpp
sudo ./runbuild.sh -release -j4
```

Первые результаты анализа расстраивают предупреждениями на многочисленные макросы, несуществующие файлы, неверные пути до файлов исходного кода т\.п\. В следующем разделе я расскажу о содержимом файла _PVS\-Studio\.cfg_, где я добавил несколько настроек, значительно улучшивших анализ\.

## Дополнительная настройка анализатора

**Относительный путь до каталога с исходниками**

Для просмотра одного отчёта на разных компьютерах анализатор умеет генерировать отчёт с относительными путями к файлам\. Восстановить их можно на другом компьютере с помощью конвертера\.

Аналогичную настройку анализатора необходимо выполнить, чтобы извлечь из контейнера отчёт с правильными путями к файлам\. Корневой каталог проекта монтируется в root, поэтому параметр анализатора будет выглядеть следующим образом:

```cpp
sourcetree-root=/
```

**Предупреждения на несуществующие файлы**

В контейнере разворачивается каталог _/external_, который отсутствует в репозитории\. Скорее всего, в нём компилируются какие\-то зависимости проекта и их можно просто исключить из анализа:

```cpp
exclude-path=/external
```

**Предупреждения на файлы компилятора, тестов и библиотек**

В Docker компилятор может размещаться в нестандартном месте и его библиотеки могут попадать в отчёт\. Их тоже необходимо исключить\. Для этого из проверки исключается каталог _/deps_ и заодно каталог с тестами:

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

**Борьба с тысячами ложных срабатываний, возникающих из\-за неудачных макросов**

Анализатор поддерживает настройку разных диагностик с помощью комментариев\. Про них можно почитать [здесь](https://pvs-studio.ru/ru/docs/manual/0017/) и [здесь](https://pvs-studio.ru/ru/docs/manual/0040/)\.

Настройки можно размещать в коде проекта или вынести в отдельный файл, как это сделал я:

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

Содержимое файла service\-fabric\.pvsconfig:

```cpp
#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
```

Несколько строк особой разметки удаляют из отчёта тысячи предупреждений на макросы\.

**Другие настройки**

Путь к файлу лицензии и включение только диагностик общего назначения \(для ускорения анализа\):

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

**Весь файл PVS\-Studio\.cfg**

```cpp
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](https://man7.org/linux/man-pages/man1/strace.1.html)\. Скорее всего, в контейнере она будет отсутствовать и в скрипт необходимо добавить шаг установки этой утилиты из репозитория\.

В контейнер могут подложить компилятор с нестандартным названием например, кросс\-компилятор\. Я уже писал, что директорию компилятора необходимо исключать из анализа, но в этом случае дополнительно придётся передать анализатору и имя нового компилятора: 

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

Можно дублировать флажок для указания нескольких компиляторов\.

## Просмотр отчёта в Linux или Windows

Для просмотра отчёта анализатора в Linux, можно добавить в скрипт команду генерации отчёта в нужном формате\.

Например, для просмотра в QtCreator:

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

Или в браузере:

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

Для просмотра отчёта в Windows можно просто открыть _\.log_ файл в утилите [Standalone](https://pvs-studio.ru/ru/docs/manual/0033/), которая входит в дистрибутив для Windows\.

## Примеры ошибок из Azure Service Fabric

### Классические опечатки

![0567_DockerServiceFabric_ru/image3.png](https://import.viva64.com/docx/blog/0567_DockerServiceFabric_ru/image3.png)

[V501](https://pvs-studio.ru/ru/docs/warnings/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

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

Переменная _iter\-\>PackageName_ должна сравниваться с _iter2\-\>PackageName_ или _codePackages_\.

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) CWE\-571 There are identical sub\-expressions '\(dataSizeInRecordIoBuffer \> 0\)' to the left and to the right of the '&&' operator\. OverlayStream\.cpp 4966

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

Из\-за Copy\-Paste не проверяется размер буфера _dataSizeInRecordMetadata_\.

[V534](https://pvs-studio.ru/ru/docs/warnings/v534/) CWE\-691 It is likely that a wrong variable is being compared inside the 'for' operator\. Consider reviewing 'ix0'\. RvdLoggerVerifyTests\.cpp 2395

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

    for (ix1 = 0; ix0 < LogState._NumberOfStreams; ix1++)
    {
      ...
    }
    ....
  }
  ....
}
```

Вероятно, в условии вложенного цикла должна проверяться переменная _ix1_, а не _ix0_\.

[V570](https://pvs-studio.ru/ru/docs/warnings/v570/) The 'statusDetails\_' variable is assigned to itself\. ComposeDeploymentStatusQueryResult\.cpp 49

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

  return *this;
}
```

Скорее всего, значение поля _statusDetails\__ хотели взять из _other\.statusDetails\__, но допустили опечатку\.

[V606](https://pvs-studio.ru/ru/docs/warnings/v606/) Ownerless token 'false'\. CryptoUtility\.Linux\.h 81

```cpp
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());
}
```

Пропущенное ключевое слово _return_ привело к тому, что код стал не оптимален\. Из\-за опечатки быстрая проверка на размер коллекций не работает так, как задумывал автор\.

[V607](https://pvs-studio.ru/ru/docs/warnings/v607/) CWE\-482 Ownerless expression\. EnvironmentOverrideDescription\.cpp 60

```cpp
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;
}
```

Опечатка похожа на предыдущий пример, но приводит к более серьёзной ошибке\. Результат одного из сравнений никогда не сохраняется\. Правильный код должен быть таким:

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

### Неверное использование функций

[V521](https://pvs-studio.ru/ru/docs/warnings/v521/) CWE\-480 Such expressions using the ',' operator are dangerous\. Make sure the expression is correct\. ReplicatedStore\.SecondaryPump\.cpp 1231

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

  Assert::TestAssert("{0}", errorMessage);
 }
 ....
}
```

Анализатор обнаружил странный код для формирования сообщения в переменной _errorMessage_\. Судя по соседним фрагментам кода, здесь необходимо написать так:

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

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) CWE\-570 Expression 'nwrite < 0' is always false\. Unsigned type value is never < 0\. File\.cpp 1941

```cpp
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](https://en.cppreference.com/w/cpp/io/c/fwrite) и [cplusplus\.com](http://www.cplusplus.com/reference/cstdio/fwrite/)\.

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) CWE\-571 Expression 'len \>\= 0' is always true\. Unsigned type value is always \>\= 0\. Types\.cpp 121

```cpp
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](https://pvs-studio.ru/ru/docs/warnings/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

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

Вероятно, из одного конструктора хотели вызвать другой\. Но на самом деле создаётся временный объект класса _JsonBufferManager2_ и тут же уничтожается\. Подробнее этот тип ошибки описан в статье "[Не зная брода, не лезь в воду: часть первая](https://pvs-studio.ru/ru/blog/posts/cpp/0127/)"\. В этой же статье рассказано, как можно вызвать один конструктор из другого\.

[V568](https://pvs-studio.ru/ru/docs/warnings/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

```cpp
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));           // <=
}
```

В функцию _write\(\)_ передан правильный _sizeof\(\)_, а вот результат функции чтения, скорее всего, должен сравниваться с размером записанного объекта:

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

[V595](https://pvs-studio.ru/ru/docs/warnings/v595/) CWE\-476 The 'globalDomain' pointer was utilized before it was verified against nullptr\. Check lines: 196, 197\. PlacementReplica\.cpp 196

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

Классическая ошибка при работе с указателем _globalDomain_: сначала разыменование, потом проверка\.

[V611](https://pvs-studio.ru/ru/docs/warnings/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

```cpp
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 для Windows\. Достаточно выполнить установку анализатора:

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

и запустить анализ своего проекта:

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

## Заключение

Акцент статьи был сделан на интересной технологии контейнеризации, которая не является препятствием для интеграции статического анализа в свой проект\. Поэтому найденные предупреждения PVS\-Studio были сокращены в статье, но полностью доступны для скачивания в формате для браузера: [service\-fabric\-pvs\-studio\-html\.7z](http://cppfiles.com/service-fabric-pvs-studio-html.7z)\.

Предлагаю всем желающим скачать и попробовать [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/download/) на своём проекте\. Анализатор работает на Windows, Linux и macOS\!