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

На данный момент облачные CI\-системы \- очень востребованный сервис\. В этой статье мы расскажем, как, с помощью уже существующих средств, доступных в PVS\-Studio, можно интегрировать анализ исходного кода с облачной CI платформой, на примере сервиса Travis CI\.


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

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

Почему мы рассматриваем сторонние облака и не делаем собственное? Есть ряд причин, и главная из них, что организация SaaS — это достаточно дорогая и непростая процедура\. На самом деле, непосредственно интегрировать анализ PVS\-Studio со сторонней облачной платформой \(будь то открытые платформы наподобие CircleCI, Travis CI, GitLab, или какое\-то специализированное enterprise\-решение, используемое только в одной конкретной компании\) – задача достаточно простая и тривиальная\. То есть можно сказать, что _PVS\-Studio уже доступен "в облаках"_\. Совсем другой вопрос в организации и предоставлении инфраструктуры для такой работы в режиме 24/7\. Это задача совсем другого порядка, и PVS\-Studio пока что не планирует предоставлять свою собственную облачную платформу непосредственно для запуска на ней анализа\.

## Информация об используемом ПО

[Travis CI](https://travis-ci.org/) – сервис для сборки и тестирования программного обеспечения, использующего GitHub в качестве хранилища\. Travis CI не требует изменения программного кода для использования сервиса, все настройки происходят в файле _\.travis\.yml_, расположенном в корне репозитория\. 

В качестве тестового проекта для проверки с помощью PVS\-Studio мы возьмем [LXC](https://linuxcontainers.org/) \(Linux Containers\)\. Это система виртуализации на уровне операционной системы для запуска нескольких экземпляров операционной системы Linux на одном узле\. 

Проект маленький, но для демонстрации более чем достаточен\. Вывод команды cloc:

|Language|files|blank|comment|code|
|---|---|---|---|---|
|C|124|11937|6758|50836|
|C/C\\\+\\\+ Header|65|1117|3676|3774|



**Примечание:** Разработчики LXC уже используют Travis CI, поэтому мы возьмем их конфигурационный файл в качестве основы и отредактируем его для наших целей\.

## Настройка

Для начала работы с Travis CI переходим [по ссылке](https://travis-ci.com/) и аутентифицируемся, используя GitHub\-аккаунт\.

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

В открывшемся окне нужно авторизовать Travis CI\.

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

После авторизации происходит перенаправление на приветственную страницу** **"First time here? Lets get you started\!"**,** где кратко описано, что необходимо дальше сделать для начала работы:

* активировать репозитории;
* добавить файл \.travis\.yml в репозиторий;
* запустить первую сборку\.

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

Начнем выполнять эти пункты\.

Для добавления в Travis CI нашего репозитория переходим в настройки профиля [по ссылке](https://travis-ci.com/account/repositories) и нажимаем кнопку "Activate"\.

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

После нажатия откроется окно с выбором репозиториев, к которым приложению Travis CI будет предоставлен доступ\.

**Примечание:** для предоставления доступа к репозиторию у учетной записи должны быть права администратора на него\.

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

Выбираем нужный репозиторий, подтверждаем выбор кнопкой "Approve & Install", и нас перенаправит обратно на страницу настройки профиля\. 

Сразу создадим переменные, которые будем использовать для создания файла лицензии анализатора и отсылки его отчетов\. Для этого перейдем на страницу настроек \- кнопка "Settings" справа от нужного репозитория\.

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

Откроется окно настроек\.

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

Краткое описание настроек:

* Секция "General" – настройка триггеров автозапуска задачи;
* Секция "Auto Cancelation" – позволяет настроить автоотмену сборки;
* Секция "Environment Variables" – позволяет определить переменные окружения, содержащие как открытую, так и конфиденциальную информацию, такие как учетные данные, ssh\-ключи;
* Секция "Cron Jobs" – настройка расписания запуска задачи\.

В секции "Environment Variables" создадим переменные _PVS\_USERNAME_ и _PVS\_KEY_, содержащие, соответственно, имя пользователя и лицензионный ключ для статического анализатора\. Если у вас нет постоянной лицензии PVS\-Studio, то вы можете [запросить триальную лицензию](https://pvs-studio.ru/ru/pvs-studio/download/)\.

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

Тут же создадим переменные _MAIL\_USER_ и _MAIL\_PASSWORD_, содержащие имя пользователя и пароль от почтового ящика, который мы будем использовать для отсылки отчетов\.

![0636_TravisCI_ru/image10.png](https://import.viva64.com/docx/blog/0636_TravisCI_ru/image10.png)

При запуске задачи Travis CI берет инструкции из файла \.travis\.yml, лежащего в корне репозитория\.

Используя Travis CI, мы можем запускать статический анализ как непосредственно в виртуальной машине, так и использовать для этого предварительно настроенный контейнер\. Результаты этих подходов ничем не отличаются друг от друга, но использование предварительно настроенного контейнера может пригодиться, например, если у нас уже есть контейнер с каким\-то специфическим окружением, внутри которого собирается и тестируется программный продукт, и нет желания восстанавливать это окружение в Travis CI\. 

Создадим конфигурацию для запуска анализатора в виртуальной машине\.

Для сборки и тестирования мы будем использовать виртуальную машину на базе Ubuntu Trusty, ее описание можно посмотреть [по ссылке](https://docs.travis-ci.com/user/reference/trusty/)\.

Первым делом указываем, что проект написан на С и перечисляем компиляторы, которые будем использовать для сборки:

```cpp
language: c
compiler:
 - gcc
 - clang
```

**Примечание**: при указании более одного компилятора, задачи будут запускаться параллельно для каждого из них\. Подробнее можно прочитать [в документации](https://docs.travis-ci.com/user/build-matrix/)\.

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

```cpp
before_install:
 - sudo add-apt-repository ppa:ubuntu-lxc/daily -y
 - 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
 - sudo apt-get update -qq
 - sudo apt-get install -qq coccinelle parallel 
       libapparmor-dev libcap-dev libseccomp-dev
       python3-dev python3-setuptools docbook2x
       libgnutls-dev libselinux1-dev linux-libc-dev pvs-studio
       libio-socket-ssl-perl libnet-ssleay-perl sendemail 
       ca-certificates
```

Перед сборкой проекта необходимо подготовить окружение:

```cpp
script:
 - ./coccinelle/run-coccinelle.sh -i
 - git diff --exit-code
 - export CFLAGS="-Wall -Werror"
 - export LDFLAGS="-pthread -lpthread"
 - ./autogen.sh
 - rm -Rf build
 - mkdir build
 - cd build
 - ../configure --enable-tests --with-distro=unknown
```

Далее нам необходимо создать файл с лицензией и запустить анализ проекта\. 

Первой командой создаем файл с лицензией для анализатора\. Данные для переменных _$PVS\_USERNAME_ и _$PVS\_KEY_ берутся из настроек проекта\. 

```cpp
- pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
```

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

```cpp
- pvs-studio-analyzer trace -- make -j4
```

После запускаем статический анализ\. 

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

```cpp
 - pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic 
   -o PVS-Studio-${CC}.log 
   –-disableLicenseExpirationCheck
```

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

```cpp
- plog-converter -t html PVS-Studio-${CC}.log 
                 -o PVS-Studio-${CC}.html
```

Так как TravisCI не позволяет изменять формат почтовых уведомлений, то для отсылки отчетов на последнем шаге воспользуемся пакетом sendemail:

```cpp
- sendemail -t mail@domain.com 
            -u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" 
            -m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" 
            -s smtp.gmail.com:587 
            -xu $MAIL_USER 
            -xp $MAIL_PASSWORD 
            -o tls=yes 
            -f $MAIL_USER 
            -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html
```

Полный текст конфигурационного файла для запуска анализатора в виртуальной машине:

```cpp
language: c
compiler:
 - gcc
 - clang
before_install:
 - sudo add-apt-repository ppa:ubuntu-lxc/daily -y
 - 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
 - sudo apt-get update -qq
 - sudo apt-get install -qq coccinelle parallel 
         libapparmor-dev libcap-dev libseccomp-dev
         python3-dev python3-setuptools docbook2x 
         libgnutls-dev libselinux1-dev linux-libc-dev pvs-studio
         libio-socket-ssl-perl libnet-ssleay-perl sendemail 
         ca-certificates

script:
 - ./coccinelle/run-coccinelle.sh -i
 - git diff --exit-code
 - export CFLAGS="-Wall -Werror"
 - export LDFLAGS="-pthread -lpthread"
 - ./autogen.sh
 - rm -Rf build
 - mkdir build
 - cd build
 - ../configure --enable-tests --with-distro=unknown
 - pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
 - pvs-studio-analyzer trace -- make -j4
 - pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic 
     -o PVS-Studio-${CC}.log 
     --disableLicenseExpirationCheck
 - plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html

 - sendemail -t mail@domain.com 
             -u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" 
             -m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" 
             -s smtp.gmail.com:587 
             -xu $MAIL_USER 
             -xp $MAIL_PASSWORD 
             -o tls=yes 
             -f $MAIL_USER 
             -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html
```

Для запуска статического анализатора в контейнере, предварительно создадим его, используя следующий Dockerfile:

```cpp
FROM docker.io/ubuntu:trusty

ENV CFLAGS="-Wall -Werror"
ENV LDFLAGS="-pthread -lpthread"

RUN apt-get update && apt-get install -y software-properties-common wget \
    && wget -q -O - https://files.pvs-studio.com/etc/pubkey.txt | 
        sudo apt-key add - \
    && wget -O /etc/apt/sources.list.d/viva64.list
       https://files.pvs-studio.com/etc/viva64.list \
    && apt-get update \
    && apt-get install -yqq coccinelle parallel 
       libapparmor-dev libcap-dev libseccomp-dev
       python3-dev python3-setuptools docbook2x
       libgnutls-dev libselinux1-dev linux-libc-dev
       pvs-studio git libtool autotools-dev automake
       pkg-config clang make libio-socket-ssl-perl 
       libnet-ssleay-perl sendemail ca-certificates \
    && rm -rf /var/lib/apt/lists/*
```

В этом случае конфигурационный файл может выглядеть так:

```cpp
before_install:
- docker pull docker.io/oandreev/lxc

env:
 - CC=gcc
 - CC=clang

script:
 - docker run 
    --rm 
    --cap-add SYS_PTRACE 
    -v $(pwd):/pvs 
    -w /pvs 
    docker.io/oandreev/lxc
    /bin/bash -c " ./coccinelle/run-coccinelle.sh -i
                  && git diff --exit-code
                  && ./autogen.sh
                  && mkdir build && cd build
                  && ../configure CC=$CC
                  && pvs-studio-analyzer credentials 
                     $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
                  && pvs-studio-analyzer trace -- make -j4
                  && pvs-studio-analyzer analyze -j2 
                     -l PVS-Studio.lic 
                     -o PVS-Studio-$CC.log 
                     --disableLicenseExpirationCheck
                  && plog-converter -t html 
                     -o PVS-Studio-$CC.html
                     PVS-Studio-$CC.log 
                      
                  && sendemail -t mail@domain.com 
             -u 'PVS-Studio $CC report, commit:$TRAVIS_COMMIT' 
             -m 'PVS-Studio $CC report, commit:$TRAVIS_COMMIT' 
             -s smtp.gmail.com:587 
             -xu $MAIL_USER -xp $MAIL_PASSWORD
             -o tls=yes -f $MAIL_USER
             -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html"
```

Как можно увидеть, в данном случае мы ничего не делаем внутри виртуальной машины, и абсолютно все действия по сборке и тестированию проекта происходят внутри контейнера\. 

**Примечание:** при запуске контейнера необходимо указывать параметр _\-\-cap\-add SYS\_PTRACE_ либо _\-\-security\-opt seccomp:unconfined_, так как для трассировки компиляции используется системный вызов ptrace\.

Загружаем конфигурационный файл в корень репозитория и видим, что Travis CI получил уведомление о наличии изменений в проекте и автоматически запустил сборку\. 

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

![0636_TravisCI_ru/image11.png](https://import.viva64.com/docx/blog/0636_TravisCI_ru/image11.png)

После окончания тестов мы получим на почту 2 письма: одно – с результатами статического анализа для сборки проекта с использованием gcc, и второе – соответственно, clang\.

## Коротко про результаты проверки

В целом проект достаточно чистый, анализатор выдал всего 24 критических и 46 средних предупреждений\. Для демонстрации работы рассмотрим пару интересных уведомлений:

### Избыточные условия в if

[V590](https://pvs-studio.ru/ru/docs/warnings/v590/) Consider inspecting the 'ret \!\= \(\- 1\) && ret \=\= 1' expression\. The expression is excessive or contains a misprint\. attach\.c 107

```cpp
#define EOF -1

static struct lxc_proc_context_info *lxc_proc_get_context_info(pid_t pid)
{
  ....
  while (getline(&line, &line_bufsz, proc_file) != -1)
  {
    ret = sscanf(line, "CapBnd: %llx", &info->capability_mask);
    if (ret != EOF && ret == 1) // <=
    {
      found = true;
      break;
    }
  }
  ....
}
```

Если _ret \=\= 1_, то он точно не равен \-1 \(EOF\)\. Избыточная проверка, можно убрать _ret \!\= EOF_\.

Таких предупреждений было выдано еще два:

* V590 Consider inspecting the 'ret \!\= \(\- 1\) && ret \=\= 1' expression\. The expression is excessive or contains a misprint\. attach\.c 579
* V590 Consider inspecting the 'ret \!\= \(\- 1\) && ret \=\= 1' expression\. The expression is excessive or contains a misprint\. attach\.c 583

### Потеря старших битов

[V784](https://pvs-studio.ru/ru/docs/warnings/v784/) The size of the bit mask is less than the size of the first operand\. This will cause the loss of higher bits\. conf\.c 1879

```cpp
struct mount_opt
{
  char *name;
  int clear;
  int flag;
};

static void parse_mntopt(char *opt, unsigned long *flags,
                         char **data, size_t size)
{
  struct mount_opt *mo;

  /* If opt is found in mount_opt, set or clear flags.
   * Otherwise append it to data. */

  for (mo = &mount_opt[0]; mo->name != NULL; mo++)
  {
    if (strncmp(opt, mo->name, strlen(mo->name)) == 0)
    {
      if (mo->clear)
      {
        *flags &= ~mo->flag;    // <=
      }
      else
      {
        *flags |= mo->flag;
      }
      return;
    }
  }
  ....
}
```

Под Linux'ом _long_ \- это 64\-битная целочисленная переменная, _mo\-\>flag_ \- 32\-битная целочисленная переменная\. Использование _mo\-\>flag_ в качестве битовой маски приведет к потере 32 старших бит\. Выполняется неявное приведение битовой маски к 64\-битной целочисленной переменной после побитовой инверсии\. Старшие биты этой маски будут нулевыми\.

Продемонстрируем на примере:

```cpp
unsigned long long x;
unsigned y;
....
x &= ~y;
```

![0636_TravisCI_ru/image12.png](https://import.viva64.com/docx/blog/0636_TravisCI_ru/image12.png)

Правильный вариант кода:

```cpp
*flags &= ~(unsigned long)(mo->flag);
```

Анализатор выдал еще одно подобное предупреждение:

* V784 The size of the bit mask is less than the size of the first operand\. This will cause the loss of higher bits\. conf\.c 1933

### Подозрительный цикл

[V612](https://pvs-studio.ru/ru/docs/warnings/v612/) An unconditional 'return' within a loop\. conf\.c 3477

```cpp
#define lxc_list_for_each(__iterator, __list) \
  for (__iterator = (__list)->next; __iterator != __list; \
          __iterator = __iterator->next)

static bool verify_start_hooks(struct lxc_conf *conf)
{
  char path[PATH_MAX];
  struct lxc_list *it;

  lxc_list_for_each (it, &conf->hooks[LXCHOOK_START]) {
    int ret;
    char *hookname = it->elem;

    ret = snprintf(path, PATH_MAX, "%s%s",
             conf->rootfs.path ? conf->rootfs.mount : "",
             hookname);
    if (ret < 0 || ret >= PATH_MAX)
      return false;

    ret = access(path, X_OK);
    if (ret < 0) {
      SYSERROR("Start hook \"%s\" not found in container",
         hookname);
      return false;
    }

    return true; // <=
  }

  return true;
}
```

Запускают цикл и на первой итерации его прерывают\. Возможно, так и задумывалось, но тогда цикл можно опустить\.

### Выход за границы массива

[V557](https://pvs-studio.ru/ru/docs/warnings/v557/) Array underrun is possible\. The value of 'bytes \- 1' index could reach \-1\. network\.c 2570

```cpp
static int lxc_create_network_unpriv_exec(const char *lxcpath,
                                          const char *lxcname,
                                          struct lxc_netdev *netdev, 
                                          pid_t pid,
                                          unsigned int hooks_version)
{
  int bytes;
  char buffer[PATH_MAX] = {0};
  ....
  bytes = lxc_read_nointr(pipefd[0], &buffer, PATH_MAX);
  if (bytes < 0)
  {
    SYSERROR("Failed to read from pipe file descriptor");
    close(pipefd[0]);
  }
  else
  {
    buffer[bytes - 1] = '\0';
  }
  ....
}
```

Из pipe'а читаются байты в буфер\. В случае ошибки, функция _lxc\_read\_nointr_ вернет отрицательное значение\. Если все прошло успешно, то последним элементом записывают нуль\-терминал\. Однако, если будет прочитано 0 байт, то произойдет выход за границу буфера, что ведет к неопределенному поведению\.

Анализатор выдал еще одно подобное предупреждение:

* V557 Array underrun is possible\. The value of 'bytes \- 1' index could reach \-1\. network\.c 2725

### Переполнение буфера

[V576](https://pvs-studio.ru/ru/docs/warnings/v576/) Incorrect format\. Consider checking the third actual argument of the 'sscanf' function\. It's dangerous to use string specifier without width specification\. Buffer overflow is possible\. lxc\_unshare\.c 205

```cpp
static bool lookup_user(const char *oparg, uid_t *uid)
{
  char name[PATH_MAX];
  ....
  if (sscanf(oparg, "%u", uid) < 1)
  {
    /* not a uid -- perhaps a username */
    if (sscanf(oparg, "%s", name) < 1) // <=
    {
      free(buf);
      return false;
    }
    ....
  }
  ....
}
```

Использование _sscanf_ в данном случае может являться опасным, поскольку если длина буфера _oparq_ окажется больше длины буфера _name_, произойдет выход за границу при формировании буфера _name_\.

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

Как мы увидели, настроить проверку статическим анализатором кода нашего проекта в облаке – достаточно простая задача\. Для этого необходимо всего лишь добавить один файл в репозиторий и потратить минимальное время на настройку CI\-системы\. В результате же мы получим инструмент, позволяющий выявлять проблемный код на этапе написания, и не позволяющий ошибкам попадать на следующие этапы тестирования, где их исправление займет больше времени и ресурсов\.

Конечно, использование PVS\-Studio совместно с облачными платформами не ограничивается только Travis CI\. По аналогии с описанным в статье способом, с минимальными отличиями, анализ PVS\-Studio можно интегрировать и с другими популярными облачными CI решениями, такими, как CircleCI, GitLab и т\.п\.

## Полезные ссылки

* Дополнительную информацию про запуск PVS\-Studio в Linux и macOS можно получить [здесь](https://pvs-studio.ru/ru/docs/manual/0036/)\.
* Про создание, настройку и использование контейнеров с установленным статическим анализатором PVS\-Studio можно почитать [здесь](https://pvs-studio.ru/ru/docs/manual/0047/)\.
* [Документация TravisCI](https://docs.travis-ci.com/)\.