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

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

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

Кстати, мы выпустили PVS-Studio 7.08, в котором было полно новых крутых фич. Например:

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

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

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

Для проверки определенных файлов укажите флаг — sourceFiles (-f) и передайте .txt со списком файлов. Это выглядит так:

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

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

Принцип проверки мерж-реквестов

Суть проверки — убедиться, что обнаруженные анализатором проблемы не попадут в ветку master при слиянии. Мы также не хотим каждый раз анализировать весь проект. Более того, при слиянии веток у нас есть список измененных файлов. Поэтому предлагаю добавить проверку мерж-реквеста.

Вот так выглядит мерж-реквест до внедрения статического анализатора:

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

Анализируем changes2 и, если ошибок нет, принимаем мерж-реквест, иначе отклоняем.

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

GitLab

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Теперь можно перейти к анализу. Сначала добавим скрипт для полного анализа. Во флаге -t мы передаем путь к решению, а во флаге -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:
  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:
  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 — это проект с открытым исходным кодом, который используется для преобразования отчета об ошибках анализатора в различные формы, например 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

Как только мы добавим все в файл, нажмите Подтвердить изменения. Чтобы убедиться, что все правильно, перейдите в раздел CI/CDКонвейеры -› Выполняется. Откроется окно виртуальной машины, в конце которого должно быть следующее:

Как только вы получите Задание выполнено — все в порядке, прибыль. Теперь вы можете проверить, что вы сделали.

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

В качестве примера мы создадим простой проект (в master), который будет содержать несколько файлов. После этого изменим только один файл в другой ветке и попробуем сделать мерж-реквест.

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

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

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

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

Как мы видим, анализатор обнаружил ошибку и не разрешил объединить ветки.

Теперь давайте проверим пример без ошибки. Фиксированный код:

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

Результаты анализа мерж-реквеста:

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

Вывод

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

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