PVS-Studio идёт в облака: GitLab CI/CD

от автора

Рисунок 2

Эта статья является продолжением цикла публикаций об использовании PVS-Studio в облачных системах. На этот раз мы рассмотрим работу анализатора совместно с GitLab CI — продуктом от GitLab Inc. Интеграция статического анализатора в CI систему позволяет выявить баги сразу после этапа сборки проекта и является очень эффективным способом сократить затраты на обнаружение ошибок.

Список других наших статей по интеграции в облачные CI системы:

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

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

PVS-Studio — инструмент статического анализа кода, предназначенный для выявления ошибок и потенциальных уязвимостей в программах, написанных на языках С, C++, C# и Java. Работает в 64-битных системах на Windows, Linux и macOS и может анализировать код, предназначенный для 32-битных, 64-битных и встраиваемых ARM платформ. Если вы впервые будете пробовать статический анализ кода для проверки своих проектов, то рекомендуем познакомиться со статьёй о том, как быстро посмотреть самые интересные предупреждения PVS-Studio и оценить возможности этого инструмента.

Для демонстрации работы статического анализатора в облаке будет использоваться проект OBS. Open Broadcaster Software — свободный и открытый набор программ для записи видео и потокового вещания. OBS предоставляет возможность перехвата с устройств и источников в реальном времени, композицию сцен, декодировку, запись и вещание. Передача данных осуществляется в основном через протокол Real Time Messaging Protocol, и данные могут быть переданы в любой источник, поддерживающий RTMP— в программе имеются готовые предустановки для прямой трансляции на самые популярные стриминговые платформы.

Настройка

Для начала работы с GitLab перейдём на сайт и нажмём кнопку Register:

Рисунок 6

Можно зарегистрироваться, привязав аккаунты таких сервисов, как: GitHub, Twitter, Google, BitBucket, Saleforce или просто заполнив открывшуюся форму. После авторизации GitLab встречает нас предложением создать проект:

Рисунок 7

Список платформ, с которых можно совершить импорт:

Рисунок 33

Для наглядности создадим пустой проект:

Рисунок 17

Далее, нам нужно загрузить свой проект в созданный репозиторий. Это делается при помощи подсказок, которые появляются в окне созданного проекта.

Рисунок 1

При запуске задачи GitLab CI берет инструкции из файла .gitlab-ci.yml. Его можно добавить либо кликнув на кнопку Set up CI/CD, либо просто создав в локальном репозитории и загрузив на сайт. Воспользуемся первым вариантом:

Рисунок 21

Составим минимальную обёртку для скрипта:

image: debian job:   script:

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

- apt-get update && apt-get -y install wget gnupg  - wget -O - https://files.viva64.com/etc/pubkey.txt | apt-key add -  - wget -O /etc/apt/sources.list.d/viva64.list   https://files.viva64.com/etc/viva64.list - apt-get update && apt-get -y install pvs-studio   sendemail

Далее, установим зависимости и утилиты для сборки OBS:

- apt-get -y install build-essential cmake     make pkg-config libx11-dev libgl1-mesa-dev    libpulse-dev libxcomposite-dev    libxinerama-dev libv4l-dev libudev-dev libfreetype6-dev    libfontconfig-dev qtbase5-dev    libqt5x11extras5-dev libx264-dev libxcb-xinerama0-dev    libxcb-shm0-dev libjack-jackd2-dev libcurl4-openssl-dev    libavcodec-dev libqt5svg5 libavfilter-dev    libavdevice-dev libsdl2-dev ffmpeg   qt5-default qtscript5-dev libssl-dev    qttools5-dev qttools5-dev-tools qtmultimedia5-dev    libqt5svg5-dev libqt5webkit5-dev  libasound2    libxmu-dev libxi-dev freeglut3-dev libasound2-dev    libjack-jackd2-dev libxrandr-dev libqt5xmlpatterns5-dev    libqt5xmlpatterns5 coccinelle parallel   libapparmor-dev libcap-dev libseccomp-dev   python3-dev python3-setuptools docbook2x   libgnutls28-dev libselinux1-dev linux-libc-dev   libtool autotools-dev    libio-socket-ssl-perl    libnet-ssleay-perl ca-certificates

Теперь нам нужно создать файл с лицензией анализатора.По умолчанию будет создан файл PVS-Studio.lic в директории ../.config/PVS-Studio. В этом случае файл лицензии можно не указывать в параметрах запуска анализатора, он будет подхвачен автоматически:

- pvs-studio-analyzer credentials $PVS_NAME $PVS_KEY

Здесь PVS_NAME и PVS_KEY – названия переменных, значения которых мы указываем в настройках. Они будут хранить логин и лицензионный ключ PVS-Studio. Чтобы установить их значения, перейдём: Settings > CI/CD > Variables.

Рисунок 23

Соберём проект, используя cmake:

- cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=On /builds/Stolyarrrov/obscheck/ - make -j4

Потом запустим анализатор:

- pvs-studio-analyzer analyze -o PVS-Studio.log 

PVS-Studio.log будет хранить результаты анализа. Получившийся файл с отчётом не предназначен для чтения и, чтобы привести его в доступный человеческому глазу вид, нам нужна утилита plog-converter. Данная программа конвертирует лог анализатора в различные форматы. Для удобства чтения совершим преобразование в html формат:

- plog-converter -t html PVS-Studio.log -o PVS-Studio.html

Отчёт можно выгрузить при помощи артефактов, но мы воспользуемся альтернативным способом и отправим файл с результатами работы анализатора на почту при помощи утилиты sendemail:

- sendemail -t $MAIL_TO   -u "PVS-Studio report, commit:GITLAB_COMMIT"    -m "PVS-Studio report, commit:GITLAB_COMMIT"    -s $GMAIL_PORT   -o tls=auto   -f $MAIL_FROM    -xu $MAIL_FROM    -xp $MAIL_FROM_PASS    -a PVS-Studio.log PVS-Studio.html

Полный .gitlab-ci.yml:

image: debian job:   script:     - apt-get update && apt-get -y install wget gnupg      - wget -O - https://files.viva64.com/etc/pubkey.txt | apt-key add -      - wget -O /etc/apt/sources.list.d/viva64.list        https://files.viva64.com/etc/viva64.list     - apt-get update && apt-get -y install pvs-studio       sendemail     - apt-get -y install build-essential cmake         pkg-config libx11-dev libgl1-mesa-dev        libpulse-dev libxcomposite-dev        libxinerama-dev libv4l-dev libudev-dev libfreetype6-dev        libfontconfig-dev qtbase5-dev        libqt5x11extras5-dev libx264-dev libxcb-xinerama0-dev        libxcb-shm0-dev libjack-jackd2-dev libcurl4-openssl-dev        libavcodec-dev libqt5svg5 libavfilter-dev        libavdevice-dev libsdl2-dev ffmpeg       qt5-default qtscript5-dev libssl-dev        qttools5-dev qttools5-dev-tools qtmultimedia5-dev        libqt5svg5-dev libqt5webkit5-dev  libasound2        libxmu-dev libxi-dev freeglut3-dev libasound2-dev        libjack-jackd2-dev libxrandr-dev libqt5xmlpatterns5-dev        libqt5xmlpatterns5 coccinelle parallel       libapparmor-dev libcap-dev libseccomp-dev       python3-dev python3-setuptools docbook2x       libgnutls28-dev libselinux1-dev linux-libc-dev       libtool autotools-dev        make libio-socket-ssl-perl        libnet-ssleay-perl ca-certificates     - pvs-studio-analyzer credentials $PVS_NAME $PVS_KEY     - cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=On /builds/Stolyarrrov/obscheck/     - make -j4     - pvs-studio-analyzer analyze -o PVS-Studio.log      - plog-converter -t html PVS-Studio.log -o PVS-Studio.html     - sendemail -t $MAIL_TO       -u "PVS-Studio report, commit:GITLAB_COMMIT"        -m "PVS-Studio report, commit:GITLAB_COMMIT"        -s $GMAIL_PORT       -o tls=auto       -f $MAIL_FROM        -xu $MAIL_FROM        -xp $MAIL_FROM_PASS        -a PVS-Studio.log PVS-Studio.html

Нажмём на кнопку commit changes. Если мы всё сделали правильно, нам высветится надпись: This GitLab CI configuration is valid. Чтобы отследить прогресс выполнения, перейдём во вкладку CI/CD > Pipelines.

Рисунок 5

Нажмём на running. Мы увидим окно терминала виртуальной машины, на которой выполняется наш файл конфигурации. Спустя некоторое время получаем сообщение: job succeeded.

Рисунок 29

Значит, время перейти на почту и открыть html файл с предупреждениями.

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

Давайте теперь для демонстрации сути статического анализа кода посмотрим на некоторые предупреждения из отчёта, которые указывают на ошибки в проекте Open Broadcaster Software. Целью статьи является описание принципов взаимодействия PVS-Studio и GitLab CI/CD, поэтому выписаны только некоторые интересные фрагменты кода с ошибками. Мы готовы предоставить авторам проекта временную лицензию, и при желании они могут провести более тщательный анализ проекта. Или они могут воспользоваться одним из вариантов бесплатного лицензирования PVS-Studio.

Также каждый желающий может самостоятельно получить триальный ключ для изучения возможностей PVS-Studio и проверки своих проектов.

Итак, давите посмотрим на некоторые примеры найденных ошибок в Open Broadcaster Software.

Предупреждение N1

V547 Expression ‘back_size’ is allways true. circlebuf.h (138)

struct circlebuf  {   ....   size_t capacity; }; static inline void circlebuf_place(struct circlebuf *cb,        size_t position,....,const void *data, size_t size) {   ....   size_t data_end_pos;   data_end_pos = position + size;   if (data_end_pos > cb->capacity)    {     size_t back_size = data_end_pos - cb->capacity;     if (back_size)     {       memcpy((uint8_t *)cb->data + position, data, loop_size);     }   .... }

Обратите внимание на строку: if (data_end_pos > cb->capacity), выполнение условия будет обозначать, что переменная back_size, определённая в строчке ниже, всегда будет больше нуля, так как происходит вычитание заведомо меньшего из заведомо большего, а значит, условие, находящееся ещё на строчку ниже, всегда будет true. Избыточное условие не так безобидно, когда под ним находится код, модифицирующий данные.

Предупреждения N2, N3

V629 Consider inspecting the ‘1 << indent’ expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. profiler.c (610)

static void profile_print_entry(uint64_t active, unsigned indent, ....) {   ....   active &= (1 << indent) - 1;   .... }

Подозрительным здесь выглядит смешение операций над 32-битными и 64-битными типами. Сначала вычисляют маску, используя 32-битные типы (выражение (1 << indent) — 1), а затем она неявно расширяется до 64-битного типа в выражении active &= …. Скорее всего при вычислении маски также предполагалось использование 64-битных типов.

Корректный вариант кода:

active &= ((uint64_t)(1) << indent) - 1;

Или:

active &= (1ull << indent) - 1;

Также, copy-paste данного блока кода есть ниже, о чём анализатор тоже выдал предупреждение: V629 Consider inspecting the ‘1 << indent’ expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. profiler.c (719)

Предупреждение N4

V761 Four identical blocks of text were found. ‘obs-audio-controls.c’ (353)

static float get_true_peak(....) {   ....   peak = _mm_max_ps(peak, abs_ps(intrp_samples));   SHIFT_RIGHT_2PS(new_work, work);   VECTOR_MATRIX_CROSS_PS(intrp_samples, work, m3, m1, p1, p3);    peak = _mm_max_ps(peak, abs_ps(intrp_samples));   SHIFT_RIGHT_2PS(new_work, work);   VECTOR_MATRIX_CROSS_PS(intrp_samples, work, m3, m1, p1, p3);    peak = _mm_max_ps(peak, abs_ps(intrp_samples));   SHIFT_RIGHT_2PS(new_work, work);   VECTOR_MATRIX_CROSS_PS(intrp_samples, work, m3, m1, p1, p3);    peak = _mm_max_ps(peak, abs_ps(intrp_samples));   SHIFT_RIGHT_2PS(new_work, work);   VECTOR_MATRIX_CROSS_PS(intrp_samples, work, m3, m1, p1, p3);   .... }

Четыре одинаковых блока. Почти всегда такой код свидетельствует о copy-paste ошибке. Скорее всего данные функции должны были быть вызваны с разными аргументами. Даже если нет, такой код выглядит странно. Хорошим решением было бы написать блок один раз и обернуть его в цикл:

for(size_t i = 0; i < 3; i++) {   peak = _mm_max_ps(peak, abs_ps(intrp_samples));   SHIFT_RIGHT_2PS(new_work, work);   VECTOR_MATRIX_CROSS_PS(intrp_samples, work, m3, m1, p1, p3); }

Предупреждение N5

V560 A part of conditional expression is always false: ‘!modifiers’. obs-hotkey.c (662)

typedef struct obs_key_combination obs_key_combination_t; struct obs_key_combination  {   uint32_t modifiers;   obs_key_t key; }; static inline void load_binding(....) {   obs_key_combination_t combo = {0};   uint32_t *modifiers = &combo.modifiers;   load_modifier(modifiers, data, "shift", INTERACT_SHIFT_KEY);   load_modifier(modifiers, data, "control", INTERACT_CONTROL_KEY);   load_modifier(modifiers, data, "alt", INTERACT_ALT_KEY);   load_modifier(modifiers, data, "command", INTERACT_COMMAND_KEY);   if (!modifiers && (combo.key == OBS_KEY_NONE ||                       combo.key >= OBS_KEY_LAST_VALUE))   {     ....   }   .... }

Определение функции load_modifier:

static inline void load_modifier(uint32_t *modifiers,                                   obs_data_t *data,                                   const char *name,                                   uint32_t flag) {   if (obs_data_get_bool(data, name))     *modifiers |= flag; }

Как мы видим, modifiers — указатель, который инициализируется адресом поля modifiers структуры combo. Так как его значение не изменяется до места проверки, он так и останется ненулевым. Более того — между местом инициализации и проверки, указатель используется при вызовах функции load_modifier, где разыменовывается. Соответственно, проверка !modifiers не имеет смысла, так как в результате работы оператора && мы всегда будем получать false при вычислении логического выражения. Думаю, что программист хотел проверить целочисленное значение, лежащие по адресу, которое хранится в указателе modifiers, но забыл разыменовать этот указатель.

Т.е. мне кажется, что проверка должна быть такой:

if (!*modifiers && ....) Или такой: if (!combo.modifiers && ....)

Предупреждение N6

V575 The potential null pointer is passed into ‘strncpy’ function. Inspect the first argument. Check lines: 2904, 2903. rtmp.c (2904)

static int PublisherAuth(....) {   ....   ptr = malloc(r->Link.app.av_len + pubToken.av_len);   strncpy(ptr, r->Link.app.av_val, r->Link.app.av_len);   .... }

Зачастую, данный код является небезопасным, поскольку не учитывает, что malloc может вернуть нулевой указатель. Если malloc вернёт NULL, в данном случае возникнет неопределённое поведение, так как первый аргумент функции strncpy будет иметь значение NULL.

Подробнее про то, почему важно проверять возвращаемое значение функции malloc, можно почитать в соответствующей статье.

Предупреждения N7, N8, N9

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

class OBSProjector : public OBSQTDisplay  {   ....   float sourceX, sourceY, ....;   .... } .... void OBSProjector::OBSRenderMultiview(....) {   OBSProjector *window = (OBSProjector *)data;   ....   auto calcBaseSource = [&](size_t i)    {     switch (multiviewLayout)      {     case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:       window->sourceX = (i % 6) * window->scenesCX;       window->sourceY =       window->pvwprgCY + (i / 6) * window->scenesCY;       break;     case MultiviewLayout::VERTICAL_LEFT_8_SCENES:       window->sourceX = window->pvwprgCX;       window->sourceY = (i / 2) * window->scenesCY;       if (i % 2 != 0)       {         window->sourceX += window->scenesCX;       }       break;     case MultiviewLayout::VERTICAL_RIGHT_8_SCENES:       window->sourceX = 0;       window->sourceY = (i / 2) * window->scenesCY;       if (i % 2 != 0)       {         window->sourceX = window->scenesCX;       }       break;     case MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES:       if (i < 4)        {         window->sourceX = (float(i) * window->scenesCX);         window->sourceY = 0;       } else        {         window->sourceX =        (float(i - 4) * window->scenesCX);        window->sourceY = window->scenesCY;       }       break;     default:// MultiviewLayout::HORIZONTAL_TOP_8_SCENES:       if (i < 4)        {         window->sourceX = (float(i) * window->scenesCX);         window->sourceY = window->pvwprgCY;       } else       {         window->sourceX =         (float(i - 4) * window->scenesCX);         window->sourceY =         window->pvwprgCY + window->scenesCY;       }     }   }   .... }

Предупреждения анализатора:

  • V636 The ‘i / 6’ expression was implicitly cast from ‘size_t’ type to ‘float’ type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;. window-projector.cpp (330)
  • V636 The ‘i / 2’ expression was implicitly cast from ‘size_t’ type to ‘float’ type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;. window-projector.cpp (334)
  • V636 The ‘i / 2’ expression was implicitly cast from ‘size_t’ type to ‘float’ type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;. window-projector.cpp (340)

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

Заключение

Как мы видим, интегрировать статический анализатор PVS-Studio в свой проект на GitLab довольно просто. Для этого достаточно написать всего один файл конфигурации и поместить его в свой облачный репозиторий. Благодаря тому, что у GitLab есть собственная интегрированная виртуальная машина, нам даже не нужно тратить много времени для настройки CI системы. Проверка кода же позволит выявлять проблемы сразу после сборки, что поможет устранять их тогда, когда сложность и стоимость правок ещё малы.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Vladislav Stolyarov. PVS-Studio in the Clouds: GitLab CI/CD.


ссылка на оригинал статьи https://habr.com/ru/company/pvs-studio/blog/476628/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *