﻿# PVS\-Studio идёт в облака: CircleCI

Мы продолжаем цикл статей по использованию статического анализатора PVS\-Studio в облачных CI\-системах\. Сегодня рассматриваем очередной сервис \- CircleCI\. В качестве проекта для анализа в этот раз выступит медиаплеер Kodi, в исходном коде которого постараемся найти интересные места\.


> Для получения актуальной информации перейдите на обновляемую страницу документации "\[Использование в CircleCI\]\(https://pvs\-studio\.ru/ru/docs/manual/0054/\)"\\\.

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

**Примечание\.** С другими статьями по интеграции PVS\-Studio в облачные CI\-системы можно ознакомиться здесь:

* [PVS\-Studio идёт в облака: Azure DevOps](https://pvs-studio.ru/ru/blog/posts/csharp/0670/)
* [PVS\-Studio идёт в облака – запуск анализа на Travis CI](https://pvs-studio.ru/ru/blog/posts/cpp/0636/)

Прежде чем перейдём непосредственно к настройке и разбору предупреждений анализатора, скажем пару слов об используемом и анализируемом ПО\.

[CircleCI](https://circleci.com/) – облачный CI\-сервис для автоматизации сборки, тестирования и публикации программного обеспечения\. Поддерживает сборку проектов как в контейнерах, так и в виртуальных машинах с ОС Windows, Linux и macOS\. 

[Kodi](https://en.wikipedia.org/wiki/Kodi_(software)) – бесплатный кроссплатформенный медиаплеер с открытым исходным кодом\. Позволяет воспроизводить аудио и видеофайлы, расположенные как на домашнем компьютере, так и в локальной сети или сети интернет\. Поддерживает темы оформления и расширение функционала путем установки плагинов\. Доступен для ОС Windows, Linux, macOS и Android\. 

[PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) – статический анализатор кода для поиска ошибок и потенциальных уязвимостей в коде, написанном на C, C\+\+, C\# и Java\. Работает под Windows, Linux и macOS\.

## Настройка

Переходим на главную страницу [CircleCI](https://circleci.com/) и нажимаем кнопку "Sign Up"

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

На следующей странице нам предложат аутентифицироваться с учетной записью GitHub либо Bitbucket\. Выбираем GitHub и попадаем на страницу авторизации приложения CircleCI\.



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



Авторизуем приложение \(зеленая кнопка "Authorize circleci"\) и нас перенаправит на приветственную страницу "Welcome to CircleCI\!"



![0676_CircleCI_ru/image4.png](https://import.viva64.com/docx/blog/0676_CircleCI_ru/image4.png)

На этой странице мы можем сразу настроить, какие проекты будут собираться в CircleCI\. Отмечаем наш репозиторий и нажимаем "Follow"\.

После добавления репозитория, CircleCI автоматически запустит сборку, но т\.к\. в репозитории еще нет файла конфигурации – задачи сборки завершится с ошибкой\.

![0676_CircleCI_ru/image5.png](https://import.viva64.com/docx/blog/0676_CircleCI_ru/image5.png)

Перед добавлением файла с конфигурацией добавим в проект переменные, содержащие лицензионные данные для анализатора\. Для этого на левой панели нажимаем "Settings", потом в группе "ORGANIZATION" выбираем пункт "Projects" и нажимаем на шестерёнку справа от нужного нам проекта\. Откроется окно с настройками\.

![0676_CircleCI_ru/image6.png](https://import.viva64.com/docx/blog/0676_CircleCI_ru/image6.png)

Нас интересует раздел "Environment Variables"\. Переходим в него и создаем переменные _PVS\_USERNAME_ и _PVS\_KEY_, содержащие имя пользователя и лицензионный ключ для анализатора\.

![0676_CircleCI_ru/image7.png](https://import.viva64.com/docx/blog/0676_CircleCI_ru/image7.png)

CircleCI при запуске сборки проекта читает конфигурацию задачи из файла в репозитории по пути \.circleci/config\.yml\. Добавим его\.

Вначале укажем образ виртуальной машины, где будут запускаться анализатор\. Полный список образов доступен по [ссылке](https://circleci.com/docs/2.0/configuration-reference/)\.

```cpp
version: 2
jobs:
  build:
    machine:
      image: ubuntu-1604:201903-01
```

Далее добавим в apt необходимые репозитории и установим зависимости проекта:

```cpp
steps:
    - checkout
    - run: sudo -- sh -c "
add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends 
&& add-apt-repository -y ppa:wsnipex/vaapi 
&& add-apt-repository -y ppa:pulse-eight/libcec 
&& apt-get update"
    - run: sudo apt-get install -y 
automake autopoint build-essential cmake 
curl default-jre gawk gdb gdc gettext git-core 
gperf libasound2-dev libass-dev libbluray-dev 
libbz2-dev libcap-dev libcdio-dev libcec4-dev 
libcrossguid-dev libcurl3 libcurl4-openssl-dev 
libdbus-1-dev libegl1-mesa-dev libfmt3-dev 
libfontconfig-dev libfreetype6-dev libfribidi-dev 
libfstrcmp-dev libgif-dev libgl1-mesa-dev  
libglu1-mesa-dev libiso9660-dev libjpeg-dev 
liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev
libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev
libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev
libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev
libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev
libxrandr-dev libxrender-dev libxslt1-dev libxt-dev
mesa-utils nasm pmount python-dev python-imaging
python-sqlite rapidjson-dev swig unzip uuid-dev yasm
zip zlib1g-dev wget
```

Добавим репозиторий PVS\-Studio и установим анализатор:

```cpp
- run: wget -q -O - https://files.pvs-studio.com/etc/pubkey.txt
             | sudo apt-key add - 
       && sudo wget -O /etc/apt/sources.list.d/viva64.list
             https://files.pvs-studio.com/etc/viva64.list
- run: sudo -- sh -c "apt-get update 
             && apt-get install pvs-studio -y"
```

Соберем зависимости проекта:

```cpp
- run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local
```

Сгенерируем Makefile\-ы в сборочной директории:

```cpp
- run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug ..
```

Следующим шагом настроим и запустим статический анализ нашего проекта\. 

Вначале создадим файл с лицензией анализатора\. Второй командой запускаем трассировку сборки проекта компилятором\.

После трассировки запускаем непосредственно статический анализ\. При использовании ознакомительной лицензии анализатор необходимо запускать с параметром:

_\-\-disableLicenseExpirationCheck_\.

Последней командой файл с результатами работы анализатора конвертируется в html\-отчет:

```cpp
- run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY}
- run: pvs-studio-analyzer trace -- make -j2 -C build/
- run: pvs-studio-analyzer analyze -j2 -l PVS.lic 
          -o PVS-Studio.log --disableLicenseExpirationCheck
- run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log
```

После завершения тестов сохраним отчеты анализатора:

```cpp
- run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/
- store_artifacts:
          path: ./PVS_Result
```

Полный текста файла  \.circleci/config\.yml:

```cpp
version: 2.1
jobs:
  build:
    machine:
      image: ubuntu-1604:201903-01
    steps:
      - checkout
      - run: sudo -- sh -c "
            add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends 
            && add-apt-repository -y ppa:wsnipex/vaapi 
            && add-apt-repository -y ppa:pulse-eight/libcec 
           &&  apt-get update"
      - run: sudo apt-get install -y automake autopoint 
          build-essential cmake curl default-jre gawk gdb
          gdc gettext git-core gperf libasound2-dev libass-dev
          libbluray-dev libbz2-dev libcap-dev libcdio-dev 
          libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev 
          libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev
          libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev
          libgl1-mesa-dev  libglu1-mesa-dev libiso9660-dev libjpeg-dev 
          liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev 
          libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev
          libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev
          libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev
          libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev
          libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils 
          nasm pmount python-dev python-imaging python-sqlite 
          rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget
      - run: wget -q -O - https://files.pvs-studio.com/etc/pubkey.txt 
                   | sudo apt-key add – 
             && sudo wget -O /etc/apt/sources.list.d/viva64.list 
                   https://files.pvs-studio.com/etc/viva64.list
      - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y"
      - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local
      - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug ..
      - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY}
      - run: pvs-studio-analyzer trace -- make -j2 -C build/
      - run: pvs-studio-analyzer analyze -j2 -l PVS.lic 
              -o PVS-Studio.log --disableLicenseExpirationCheck
      - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log
      - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/
      - store_artifacts:
          path: ./PVS_Result
```

Загружаем файл в репозиторий и CircleCI автоматически начнет сборку проекта\.

![0676_CircleCI_ru/image8.png](https://import.viva64.com/docx/blog/0676_CircleCI_ru/image8.png)

После окончания задачи файлы с результатами работы анализатора можно скачать на вкладке "Artifacts"\. 

![0676_CircleCI_ru/image9.png](https://import.viva64.com/docx/blog/0676_CircleCI_ru/image9.png)

## Результаты анализа

Что ж, теперь давайте взглянем на некоторые предупреждения, выданные анализатором в ходе работы\.

**Предупреждение PVS\-Studio**: [V504](https://pvs-studio.ru/ru/docs/warnings/v504/) It is highly probable that the semicolon ';' is missing after 'return' keyword\. AdvancedSettings\.cpp:1476

```cpp
void CAdvancedSettings::SetExtraArtwork(const TiXmlElement* arttypes,
   std::vector<std::string>& artworkMap)
{
  if (!arttypes)
    return
  artworkMap.clear();
  const TiXmlNode* arttype = arttypes->FirstChild("arttype");
  ....
}
```

Судя по форматированию кода, предполагалась следующая логика исполнения:

* если _arttypes_ \- нулевой указатель, завершаем выполнение метода;
* если _arttypes_ \- ненулевой указатель, очищаем вектор _artworkMap_, после чего выполняем ещё какие\-то действия\.

Однако пропущенный символ ';' внёс свои коррективы; как результат \- логика исполнения совсем не соответствует форматированию\. В результате она становится следующей:

* если _arttypes_ \- нулевой указатель, выполняется очистка вектора _artworkMap_ и выход из метода;
* если _arttypes_ \- ненулевой указатель, выполняются дальнейшие действия, но очистки вектора _artworkMap_ при этом не происходит\.

В общем, очень маловероятно, что здесь нет ошибки\. Да и навряд ли кто\-то стал бы писать выражения в духе _return artworkMap\.clear\(\);_ :\)\.

**Предупреждения PVS\-Studio**: 

* [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'lastsector' is always false\. udf25\.cpp:636
* [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'lastsector' is always false\. udf25\.cpp:644
* [V571](https://pvs-studio.ru/ru/docs/warnings/v571/) Recurring check\. The 'if \(lastsector\)' condition was already verified in line 636\. udf25\.cpp:644



```cpp
int udf25::UDFGetAVDP( struct avdp_t *avdp)
{
  ....
  uint32_t lastsector;
  ....
  lastsector = 0; // <=
  ....
  for(;;) {
    ....
    if( lastsector ) { // <= V547
      lbnum = lastsector;
      terminate = 1;
    } else {
      //! @todo Find last sector of the disc (this is optional).
      if( lastsector ) // <= V547
        lbnum = lastsector - 256;
      else
        return 0;
    }
  }
  ....
}
```

Обратите внимание на места, отмеченные _// <\=_\. В переменную _lastsector_ записывают значение 0, а после два раза используют её в качестве условного выражения оператора _if_\. Так как ни в цикле, ни между этими присваиваниями значение переменной не изменяется, then\-ветви обоих операторов _if_ выполняться не будут\. 

Возможно, правда, что необходимая функциональность попросту ещё не реализована \(обратите внимание на _@todo_\)\.

Кстати, как вы могли заметить, анализатор выдал сразу 3 предупреждения на этот код\. Иногда, однако, даже нескольких предупреждений бывает недостаточно, и пользователи продолжают считать, что анализатор ошибается\.\.\. Подробнее об этом писал коллега в заметке "[Один день из поддержки пользователей PVS\-Studio](https://pvs-studio.ru/ru/blog/posts/cpp/0671/)" :\)\.

**Предупреждение PVS\-Studio**: [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'values\.size\(\) \!\= 2' is always false\.   GUIControlSettings\.cpp:1174

```cpp
bool CGUIControlRangeSetting::OnClick()
{
  ....
  std::vector<CVariant> values;
  SettingConstPtr listDefintion = settingList->GetDefinition();
  switch (listDefintion->GetType())
  {
    case SettingType::Integer:
      values.push_back(m_pSlider->
             GetIntValue(CGUISliderControl::RangeSelectorLower));
      values.push_back(m_pSlider->
             GetIntValue(CGUISliderControl::RangeSelectorUpper));
      break;
    case SettingType::Number:
      values.push_back(m_pSlider->
         GetFloatValue(CGUISliderControl::RangeSelectorLower));
      values.push_back(m_pSlider->
         GetFloatValue(CGUISliderControl::RangeSelectorUpper));
      break;
    default:
      return false;
  }
  if (values.size() != 2)
    return false;
  SetValid(CSettingUtils::SetList(settingList, values));
  return IsValid();
}
```

В данном случае проверка _values\.size\(\) \!\= 2_ является избыточной, так как результатом условного выражения всегда будет _false_\. В самом деле, если исполнение зайдёт в одну из _case_\-ветвей оператора _switch_, в вектор будут добавлены 2 элемента, а так как он был пустым, то и его размер станет равен двум; иначе \(при исполнении ветви _default_\) будет осуществлён выход из метода\.

**Предупреждение PVS\-Studio**: [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'prio \=\= 0x7fffffff' is always true\.    DBusReserve\.cpp:57

```cpp
bool CDBusReserve::AcquireDevice(const std::string& device)
{
  ....
  int prio = INT_MAX;
  ....
  res = 
    dbus_bus_request_name(
      m_conn,
      service.c_str(),
      DBUS_NAME_FLAG_DO_NOT_QUEUE | 
      (prio == INT_MAX ? 0 : DBUS_NAME_FLAG_ALLOW_REPLACEMENT), // <=
      error);
  ....
}
```

Переменная _prio_ инициализируется значением _INT\_MAX_, после чего она же используется в тернарном операторе в сравнении _prio \=\= INT\_MAX_\. Однако между местом инициализации и использования её значение не изменяется, следовательно, значение выражения _prio \=\= INT\_MAX_ \- _true_, а тернарный оператор всегда будет возвращать значение 0\. 

**Предупреждения PVS\-Studio**:

* [V575](https://pvs-studio.ru/ru/docs/warnings/v575/) The potential null pointer is passed into 'memcpy' function\. Inspect the first argument\. Check lines: 39, 38\.  DVDOverlayImage\.h:39
* [V575](https://pvs-studio.ru/ru/docs/warnings/v575/) The potential null pointer is passed into 'memcpy' function\. Inspect the first argument\. Check lines: 44, 43\.  DVDOverlayImage\.h:44

```cpp
CDVDOverlayImage(const CDVDOverlayImage& src)
    : CDVDOverlay(src)
{
  Data = (uint8_t*)malloc(src.linesize * src.height);
  memcpy(data, src.data, src.linesize * src.height); // <=
  if(src.palette)
  {
    palette = (uint32_t*)malloc(src.palette_colors * 4);
    memcpy(palette, src.palette, src.palette_colors * 4); // <=
  }
  ....
}
```

Оба предупреждения имеют один и тот же паттерн \- указатель, полученный как результат вызова функции _malloc_, используется дальше в функции _memcpy_ без предварительной проверки на _NULL_\.

У кого\-то может возникнуть желание возразить по поводу данных предупреждений в следующем ключе: _malloc_ никогда не вернёт нулевой указатель, а если и вернёт, то пусть приложение лучше упадёт\. Это тема для длинной дискуссии, но так или иначе предлагаю ознакомиться с заметкой моего коллеги \- "[Почему важно проверять, что вернула функция malloc](https://pvs-studio.ru/ru/blog/posts/cpp/0558/)"\.

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

**Предупреждение PVS\-Studio**: [V522](https://pvs-studio.ru/ru/docs/warnings/v522/) There might be dereferencing of a potential null pointer 'entry'\. Check lines: 985, 981\. emu\_msvcrt\.cpp:985

```cpp
struct dirent *dll_readdir(DIR *dirp)
{
  ....
  struct dirent *entry = NULL;
  entry = (dirent*) malloc(sizeof(*entry));
  if (dirData->curr_index < dirData->items.Size() + 2)
  {
    if (dirData->curr_index == 0)
      strncpy(entry->d_name, ".\0", 2);
  ....
}
```

Ситуация аналогична описанной выше\. Полученный как результат вызова _malloc_ указатель записывается в переменную _entry_, после чего она используется без проверки на _NULL_ \(_entry\-\>d\_name_\)\.

**Предупреждение PVS\-Studio**: [V773](https://pvs-studio.ru/ru/docs/warnings/v773/) Visibility scope of the 'progressHandler' pointer was exited without releasing the memory\. A memory leak is possible\.  PVRGUIChannelIconUpdater\.cpp:94

```cpp
void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const
{
  ....
  CPVRGUIProgressHandler* progressHandler = 
      new CPVRGUIProgressHandler(g_localizeStrings.Get(19286)); 
  for (const auto& group : m_groups)
  {
    const std::vector<PVRChannelGroupMember> members = group->GetMembers();
    int channelIndex = 0;
    for (const auto& member : members)
    {
      progressHandler->UpdateProgress(member.channel->ChannelName(), 
            channelIndex++, members.size());
      ....
  }
  progressHandler->DestroyProgress();
}
```

Указатель _progressHandler_ содержит значение, полученное в результате вызова _operator new_\. Однако для этого указателя не вызывается _operator delete_, что приводит к возникновению утечки памяти\.

**Предупреждение PVS\-Studio**: [V557](https://pvs-studio.ru/ru/docs/warnings/v557/) Array overrun is possible\. The 'idx' index is pointing beyond array bound\. PlayerCoreFactory\.cpp:240

```cpp
std::vector<CPlayerCoreConfig *> m_vecPlayerConfigs;
bool CPlayerCoreFactory::PlaysVideo(const std::string& player) const
{
  CSingleLock lock(m_section);
  size_t idx = GetPlayerIndex(player);
  if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size())
    return false;
  return m_vecPlayerConfigs[idx]->m_bPlaysVideo;
}
```

Оператор _if_ накладывает ограничения на размер вектора _m\_vecPlayerConfigs_ за счёт условного выражения и выхода из метода в случае его истинности\. В итоге, если исполнение кода дошло до последнего оператора _return_, размер вектора _m\_vecPlayerConfigs_ находится в указанном диапазоне: \[1; idx\]\. Однако парой строк ниже находится обращение по индексу _idx_: _m\_vecPlayerConfigs\[idx\]\-\>m\_bPlaysVideo_\. В итоге, если _idx_ будет равен размеру вектора, произойдёт обращение за границу допустимого диапазона\.

И напоследок взглянем на пару предупреждений на код библиотеки **Platinum**\.

**Предупреждение PVS\-Studio**: [V542](https://pvs-studio.ru/ru/docs/warnings/v542/) Consider inspecting an odd type cast: 'bool' to 'char \*'\. PltCtrlPoint\.cpp:1617

```cpp
NPT_Result PLT_CtrlPoint::ProcessSubscribeResponse(...)
{
  ....
  bool subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE");
  ....
  NPT_String prefix = NPT_String::Format("
    PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for 
    service \"%s\" (result = %d, status code = %d)", 
    (const char*)subscription?"S":"Uns",   // <=
    (const char*)service->GetServiceID(),
    res,
    response?response->GetStatusCode():0);
  ....
}
```

В данном случае перепутан приоритет операций\. К _const char\*_ приводится не результат вычисления тернарного оператора \(_subscription ? "S" : "Uns"_\), а переменная _subscription_\. Как минимум это выглядит странно\.

**Предупреждение PVS\-Studio**: [V560](https://pvs-studio.ru/ru/docs/warnings/v560/) A part of conditional expression is always false: c \=\= '\\t'\. NptUtils\.cpp:863

```cpp
NPT_Result NPT_ParseMimeParameters(....)
{
  ....
  case NPT_MIME_PARAMETER_PARSER_STATE_NEED_EQUALS:
    if (c <  ' ') return NPT_ERROR_INVALID_SYNTAX; // END or CTLs are invalid
    if (c == ' ' || c == '\t') continue; // ignore leading whitespace
  ....
}
```

Код пробела \- 0x20, код табуляции \- 0x09\. Следовательно, подвыражение _c \=\= '\\t'_ будет всегда иметь значение _false_, так как этот случай уже покрыт выражением _c < ' '_ \(при истинности которого будет осуществлён выход из функции\)\. 

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

Как видно из этой статьи, на очередной CI\-системе \(CircleCI\) удалось настроить проверку проекта с использованием PVS\-Studio\. Предлагаю и вам [загрузить и попробовать](https://pvs-studio.ru/ru/pvs-studio/download/) анализатор на своём проекте\. Если же возникнут какие\-то вопросы по настройке или использованию анализатора \- смело [пишите нам](https://pvs-studio.ru/ru/about-feedback/), с радостью поможем\.

И, конечно, безбажного кода вам, друзья\. :\)