image1.png

Любите GitLab и не любите ошибки? Хотите повысить качество исходного кода? Тогда вы попали по адресу. Сегодня мы расскажем, как настроить C# анализатор PVS-Studio для проверки merge request'ов. Всем единорожного настроения и приятного чтения.

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

Кстати, у нас состоялся релиз PVS-Studio 7.08, в котором мы сделали много всего интересненького. Например:

  • анализатор C# под Linux и macOS;
  • плагин для Rider;
  • новый режим проверки списка файлов.

Режим проверки списка файлов


Раньше для того чтобы проверить определенные файлы, необходимо было передать анализатору .xml со списком файлов. Но так как это не очень удобно, мы добавили возможность передачи .txt, что очень упрощает жизнь.

Для того чтобы проверить определенные файлы, необходимо указать флаг --sourceFiles (-f) и передать .txt со списком файлов. Выглядит это следующим образом:

pvs-studio-dotnet -t path/to/solution.sln -f fileList.txt -o project.json

Если вам интересно настроить проверку коммитов или pull requests, вы также можете сделать это, используя данный режим. Отличие будет заключаться в получении списка файлов для анализа и зависеть от того, какие системы вы используете.

Принцип проверки merge request


Основная суть проверки состоит в том, чтобы проблемы, обнаруженные анализатором, при слиянии не попадали в master ветку. Также мы не хотим каждый раз анализировать проект целиком. Тем более что при слиянии веток у нас есть список измененных файлов. Поэтому предлагаю добавить проверку merge request.

Вот так выглядит merge request до внедрения статического анализатора:

image2.png

То есть все ошибки, которые были в ветке changes, перейдут в мастер ветку. Так как нам бы этого не хотелось, добавляем анализ, и теперь схема выглядит следующим образом:

image3.png

Анализируем changes2 и, если ошибок нет, принимаем merge request, а иначе отклоняем его.

Кстати, если вас интересует анализ коммитов и pull request'ов для C/C++, то вы можете почитать об этом здесь.

GitLab


GitLab — веб-инструмент жизненного цикла DevOps с открытым исходным кодом, представляющий систему управления репозиториями кода для Git с собственной вики, системой отслеживания ошибок, CI/CD пайплайном и другими функциями.

Перед тем как приступить к реализации анализа merge request'ов необходимо зарегистрироваться и загрузить свой проект. Если вы не знаете, как это сделать, то предлагаю статью моего коллеги.

Примечание. Описываемый далее способ настройки окружения – один из возможных. Цель – показать шаги настройки необходимого для анализа окружения и запуска анализатора. Возможно, в вашем случае более оптимальным будет разделение этапов подготовки окружения (добавление репозиториев, установка анализатора) и анализа: например, подготовка Docker образов с необходимым окружением и их использование или какой-нибудь иной способ.

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

image4.png

Для работы анализатору требуется .NET Core SDK 3, поэтому перед установкой анализатора нужно добавить репозитории Microsoft, из которых будут установлены необходимые для анализатора зависимости. Добавление репозиториев Microsoft для различных дистрибутивов Linux описано в соответствующем документе.

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

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

Примечание. Обратите внимание, что для описываемого режима работы (анализ merge requests) нужна Enterprise лицензия. Поэтому, если вы хотите попробовать данный режим работы, в поле "Сообщение" не забудьте указать, что вам нужна именно Enterprise лицензия.

Если происходит merge request, то нам потребуется проанализировать только список измененных файлов, а иначе анализируем все файлы. После анализа нужно сконвертировать логи в нужный нам формат.

Теперь, имея перед глазами алгоритм работы, можно переходить к написанию скрипта. Чтобы это сделать, необходимо изменить файл .gitlab-ci.yml или, если его нет, создать. Для его создания нужно нажать на название вашего проекта -> Set up CI/CD.

image5.png

Вот теперь мы готовы к написанию скрипта. Давайте сначала напишем код, который установит анализатор и введет лицензию:

before_script:
  - apt-get update && apt-get -y install wget gnupg 

  - apt-get -y install git
  - wget https://packages.microsoft.com/config/debian/10/
packages-microsoft-prod.deb -O packages-microsoft-prod.deb
  - dpkg -i packages-microsoft-prod.deb
  - apt-get update
  - apt-get install apt-transport-https
  - apt-get update
  
  - wget -q -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-dotnet

  - pvs-studio-analyzer credentials $PVS_NAME $PVS_KEY
  - dotnet restore "$CI_PROJECT_DIR"/Test/Test.sln

Так как установка и активация должна происходить перед всеми другими скриптами, то используем специальную метку before_script. Немного поясню данный фрагмент.

Подготовка к установке анализатора:

  - wget https://packages.microsoft.com/config/debian/10/
packages-microsoft-prod.deb -O packages-microsoft-prod.deb
  - dpkg -i packages-microsoft-prod.deb
  - apt-get update
  - apt-get install apt-transport-https
  - apt-get update

Добавление репозиториев PVS-Studio и анализатора:

  - wget -q -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-dotnet

Активация лицензии:

  - pvs-studio-analyzer credentials $PVS_NAME $PVS_KEY

$PVS_NAME — имя пользователя.

$PVS_KEY — ключ продукта.

Восстановление зависимостей проекта, где $CI_PROJECT_DIR – полный путь до директории проекта:

  - dotnet restore "$CI_PROJECT_DIR"/Path/To/Solution.sln

Для корректного анализа проект должен успешно собираться, а его зависимости должны быть восстановлены (например, должны быть загружены необходимые NuGet пакеты).

Задать переменные окружения, содержащие лицензионную информацию, можно, нажав на Setting, а после — на CI / CD.

image6.png

В открывшимся окне находим пункт Variables, справа нажимаем на кнопку Expand и добавляем переменные. В результате должно получиться следующее:

image7.png

Теперь можно переходить к анализу. Сначала добавим скрипт для полного анализа. В флаг -t передаем путь до solution, в флаг -o пишем путь до файла, в который будут записаны результаты анализа. Также нас интересует код возврата. В данном случае нам интересно, чтобы работа прекращалась, когда код возврата содержит информацию о том, что в ходе анализа были выданы предупреждения. Вот как выглядит данный фрагмент:

job:
  script:
  - exit_code=0
  - pvs-studio-dotnet -t "$CI_PROJECT_DIR"/Test/Test.sln -o 
PVS-Studio.json || exit_code=$?
  - exit_code=$((($exit_code & 8)/8))
  - if [[ $exit_code == 1 ]]; then exit 1; else exit 0; fi

Коды возврата работают по принципу битовой маски. Например, если в результате анализа были выданы предупреждения, то код возврата будет равен 8. Если лицензия истечёт в течение месяца, то код возврата будет равен 4. Если же в ходе анализа были обнаружены ошибки, а также лицензия истекает в течение месяца, в код возврата будут записаны оба значения: складываем числа вместе и получаем итоговый код возврата — 8+4=12. Таким образом, проверяя соответствующие биты, можно получать информацию о различных состояниях во время анализа. Более подробно коды возврата описываются в разделе "Коды возврата pvs-studio-dotnet (Linux / macOS)" документа "Проверка проектов Visual Studio / MSBuild / .NET Core из командной строки с помощью PVS-Studio".

В данном случае нас интересуют все коды возврата, где фигурирует 8.

  - exit_code=$((($exit_code & 8)/8))

Мы получим 1, когда код возврата содержит интересующий нас бит числа, а иначе получим 0.

Настало время добавить анализ merge request. Перед тем как это сделать, подготовим место для скрипта. Нам необходимо, чтобы он выполнялся только тогда, когда происходит merge request. Выглядит это вот так:

merge:
  script:
  only:
  - merge_requests

Перейдем к самому скрипту. Я столкнулся с тем, что виртуальная машина ничего не знает про origin/master. Поэтому помогаем ей немного:

  - git fetch origin

Теперь получим разницу веток и сохраняем результат в txt файл:

  - git diff --name-only origin/master $CI_COMMIT_SHA > pvs-fl.txt

Где $CI_COMMIT_SHA – хеш последнего коммита.

Далее запускаем анализ списка файлов, используя флаг -f. В него передаем полученный раннее .txt файл. Ну и по аналогии с полным анализом смотрим коды возврата:

  - exit_code=0
  - pvs-studio-dotnet -t "$CI_PROJECT_DIR"/Test/Test.sln -f 
pvs-fl.txt -o PVS-Studio.json || exit_code=$?
  - exit_code=$((($exit_code & 8)/8))
  - if [[ $exit_code == 1 ]]; then exit 1; else exit 0; fi

Полный скрипт для проверки merge request будет выглядеть вот так:

merge:
  script:
  - git fetch origin
  - git diff --name-only origin/master $CI_COMMIT_SHA > pvs-fl.txt
  - exit_code=0
  - pvs-studio-dotnet -t "$CI_PROJECT_DIR"/Test/Test.sln -f 
pvs-fl.txt -o PVS-Studio.json || exit_code=$?
  - exit_code=$((($exit_code & 8)/8))
  - if [[ $exit_code == 1 ]]; then exit 1; else exit 0; fi
  only:
  - merge_requests

Остается только добавить конвертацию лога после того, как отработали все скрипты. Используем метку after_script и утилиту plog-converter:

after_script:
  - plog-converter -t html -o eLog ./PVS-Studio.json

Утилита plog-converter — это open source проект, который используется для преобразования отчета об ошибках анализатора в различные формы, например, HTML. Более подробное описание утилиты приводится в подразделе "Утилита Plog Converter" соответствующего раздела документации.

Кстати, если вы хотите удобно работать с .json отчётом локально из IDE, то предлагаю наш плагин для IDE Rider. Более подробно его использование описано в соответствующем документе.

Для удобства вот .gitlab-ci.yml целиком:

image: debian

before_script:
  - apt-get update && apt-get -y install wget gnupg 

  - apt-get -y install git
  - wget https://packages.microsoft.com/config/debian/10/
packages-microsoft-prod.deb -O packages-microsoft-prod.deb
  - dpkg -i packages-microsoft-prod.deb
  - apt-get update
  - apt-get install apt-transport-https
  - apt-get update
  
  - wget -q -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-dotnet

  - pvs-studio-analyzer credentials $PVS_NAME $PVS_KEY
  - dotnet restore "$CI_PROJECT_DIR"/Test/Test.sln

merge:
  script:
  - git fetch origin
  - git diff --name-only origin/master $CI_COMMIT_SHA > pvs-fl.txt
  - exit_code=0
  - pvs-studio-dotnet -t "$CI_PROJECT_DIR"/Test/Test.sln -f 
pvs-fl.txt -o PVS-Studio.json || exit_code=$?
  - exit_code=$((($exit_code & 8)/8))
  - if [[ $exit_code == 1 ]]; then exit 1; else exit 0; fi
  only:
  - merge_requests

job:
  script:
  - exit_code=0
  - pvs-studio-dotnet -t "$CI_PROJECT_DIR"/Test/Test.sln -o 
PVS-Studio.json || exit_code=$?
  - exit_code=$((($exit_code & 8)/8))
  - if [[ $exit_code == 1 ]]; then exit 1; else exit 0; fi
  
after_script:
  - plog-converter -t html -o eLog ./PVS-Studio.json

Как только добавили все в файл, нажимаем на Commit changes. Для того чтобы посмотреть, что все правильно, заходим в CI/CD -> Pipelines -> Running. Откроется окно виртуальной машины, в конце которой должно быть следующее:

image8.png

Увидели Job succeeded – успех, все прекрасно. Теперь можно и протестировать сделанное.

Примеры работы


Для примера работы создадим простой проект (в master) в котором будет несколько файлов. После этого в другой ветке изменим только один файл и попробуем сделать merge request.

Рассмотрим два случая: когда измененный файл содержит ошибку и когда нет. Сначала пример с ошибкой.

Допустим, в master ветке есть файл Program.cs, который не содержит ошибок, а в другой ветке разработчик добавил ошибочный код и хочет сделать merge request. Какую именно ошибку он допустил – не столь важно, главное, что она есть. Например, забыл оператор throw (да, так ошибаются):

void MyAwesomeMethod(String name)
{
  if (name == null)
    new ArgumentNullException(....);
  // do something
  ....
}

Посмотрим на результат анализа примера с ошибкой. Также чтобы убедиться в том, что только один файл был проанализирован, я добавил флаг -r в строку запуска pvs-studio-dotnet:

image9.png

Мы видим, что анализатор нашел ошибку и не позволил сделать слияние веток.

Проверяем пример без ошибки. Исправляем код:

void MyAwesomeMethod(String name)
{
  if (name == null)
    throw new ArgumentNullException(....);
  // do something
  ....
}

Результаты анализа merge request:

image10.png

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

Заключение


Отсеивать плохой код до слияния веток — это очень удобно и приятно. Поэтому, если вы пользуетесь CI/CD, попробуйте встроить статический анализатор для проверки. Тем более что делается это достаточно просто.

Спасибо за внимание.


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Nikolay Mironov. Analysis of merge requests in GitLab using PVS-Studio for C#.

Комментарии (1)