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

Олег Андреев
Статей: 2

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

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

Для получения актуальной информации перейдите на обновляемую страницу документации "Использование в Travis CI".

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

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

Travis CI – сервис для сборки и тестирования программного обеспечения, использующего GitHub в качестве хранилища. Travis CI не требует изменения программного кода для использования сервиса, все настройки происходят в файле .travis.yml, расположенном в корне репозитория.

В качестве тестового проекта для проверки с помощью PVS-Studio мы возьмем LXC (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 переходим по ссылке и аутентифицируемся, используя GitHub-аккаунт.

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

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

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

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

Для добавления в Travis CI нашего репозитория переходим в настройки профиля по ссылке и нажимаем кнопку "Activate".

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

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

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

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

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

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

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

В секции "Environment Variables" создадим переменные PVS_USERNAME и PVS_KEY, содержащие, соответственно, имя пользователя и лицензионный ключ для статического анализатора. Если у вас нет постоянной лицензии PVS-Studio, то вы можете запросить триальную лицензию.

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

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

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

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

Для сборки и тестирования мы будем использовать виртуальную машину на базе Ubuntu Trusty, ее описание можно посмотреть по ссылке.

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

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_USERNAME и $PVS_KEY берутся из настроек проекта.

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

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

- pvs-studio-analyzer trace -- make -j4

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

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

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

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

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

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

- 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

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

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:

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/*

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

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 получил уведомление о наличии изменений в проекте и автоматически запустил сборку.

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

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

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

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

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

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

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

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-битной целочисленной переменной после побитовой инверсии. Старшие биты этой маски будут нулевыми.

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

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

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

*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 An unconditional 'return' within a loop. conf.c 3477

#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 Array underrun is possible. The value of 'bytes - 1' index could reach -1. network.c 2570

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

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 можно получить здесь.
  • Про создание, настройку и использование контейнеров с установленным статическим анализатором PVS-Studio можно почитать здесь.
  • Документация TravisCI.