Хотите, чтобы статический анализ работал не только на ваших локальных машинах, но и прямо в Pull Request'ах? Чтобы баги ловились до попадания в главную ветку, а не после? В этой статье покажем, как это сделать на конкретном примере пайплайна в GitHub Actions.

Статический анализ в CI
Одним из эффективных способов регулярного применения статического анализа является его внедрение в процесс непрерывной интеграции (Continuous Integration, CI). CI — это практика разработки программного обеспечения, при которой изменения кода регулярно объединяются в общий репозиторий и автоматически проходят сборку и тестирование.
Обычно в CI запускается набор различных тестов, однако не стоит ограничиваться только тестированием. Статический анализ кода позволяет находить ошибки и потенциальные уязвимости ещё до запуска тестов, делая процесс более надёжным.
Среди преимуществ использования статического анализа в CI можно выделить раннюю диагностику проблем, предотвращение попадания ошибок в основную ветку, а также повышение общего качества кода всего проекта.
Более того, сам факт регулярности статического анализа довольно важен. Если будем анализировать изменения единожды, например, перед релизом, то мы:
увеличим время разметки предупреждений;
ухудшим экспертизу предупреждений, поскольку при разборе большого количества срабатываний что-нибудь обязательно потеряется;
усложним разработчикам процесс исправления ошибок, поскольку промежуток времени между попаданием ошибки в исходный код и её обнаружением сильно увеличится. Разработчику по факту придётся заново решать ранее поставленную задачу, изучая код, в котором ошибка была допущена.
Quality Gate
Для эффективного контроля качества анализа кода в CI целесообразно использовать Quality Gate (порог качества) — совокупность критериев, на основании которых делается вывод, можно ли считать изменения в коде "достаточно качественными", чтобы двигаться дальше по пайплайну. Если хотя бы один из критериев нарушен, считается, что порог качества не пройден, и дальнейшие этапы не выполняются.
С помощью Quality Gate мы можем задать чёткие правила качества, обеспечить автоматическое принятие/отклонение кода на основе объективных метрик, а также сделать требования к качеству прозрачными и измеримыми.
Также Quality Gate поможет нам автоматизировано блокировать следующие шаги пайплайна в том случае, когда порог качества не пройден. Зачем запускать тесты, если мы заранее знаем, что код не соответствует нашим критериям качества?
Из этого следует, что выполнение статического анализа стоит поставить в пайплайне первым этапом. Таким образом, порядок этапов обеспечения качества будет логичным, а разработчики смогут оперативно получать обратную связь от CI-системы о своих изменениях.
Триггер запуска
Поскольку мы говорим о непрерывной интеграции (ещё раз обращу внимание на слово непрерывной), то нам необходимо выбрать условие, при котором мы можем автоматически запускать CI.
Хорошим вариантом было бы запускать пайплайн после каждого коммита, но это может создать лишнюю нагрузку на инфраструктуру. Помимо этого, полезно было бы прививать разработчикам привычку запускать проверки изменений локально (здесь идентично как для статического анализа, так и для тестов). Поэтому запускать проверки при каждом движении в каждой ветке будет, мягко говоря, излишним.
Как мне кажется, лучший вариант — запускать пайплайн при открытии запроса на слияние (Pull/Merge Request). То есть в той ситуации, где непосредственно нужно проверить, что код, который мы собираемся интегрировать в основную ветку, будет безопасным и не создаст проблем для приложения.
Готовим статический анализ в GitHub Actions
Рассмотрим, как можно приготовить статический анализ на примере интеграции PVS-Studio в CI-систему GitHub Actions.
Примечание. Работа анализатора PVS-Studio с GitHub Actions подробно описана в соответствующем разделе нашей документации.
Предварительная настройка
Изначально у нас имеется GitHub репозиторий с C# проектом на основе сборочной системы MSBuild. Для начала добавим лицензию PVS-Studio в настройки репозитория. Для этого на вкладке Settings репозитория нужно зайти в раздел Secrets and Variables и в подразделе Actions нажать на New repository secret. Названием указываем PVS_STUDIO_CREDENTIALS
, а значением — имя и ключ лицензии анализатора, разделённые пробелом:

Написание пайплайна
Теперь можно перейти к созданию пайплайна. Для этого необходимо создать YAML
-файл в директории .github/workflows
. В нашем примере файл будет называться build-analyze.yaml
. Рассмотрим по частям, что в нём происходит.
В начале мы описываем имя workflow и условие, при котором он будет запускаться. Ранее мы условились, что статический анализ будет запускаться при появлении в репозитории нового запроса на слияние веток.
name: Build and analyze with PVS-Studio
on:
pull_request:
branches: [main]
Далее переходим к списку job, которые необходимо выполнить. В приведённом случае у нас она будет одна, но, помимо этого, можно добавить, например, модульные тесты.
В этой job'е как условие запуска нужно установить, что пользователь, закоммитивший изменения, не должен быть ботом (бот будет коммитить некоторые вещи для ускорения процесса анализа, немного терпения, мы скоро до этого дойдём). Также нужно дать этой job'е права записи, чтобы мы смогли опубликовать отчёт анализатора, и указать, какую операционную систему будем использовать (в нашем случае — Ubuntu 24.04). После этого уже будут описаны шаги выполнения job'ы.
jobs:
build-analyze:
if: github.actor != 'github-actions[bot]'
permissions: write-all
runs-on: ubuntu-24.04
steps:
Первые два шага будут подготовительными: перед тем, как начать работать с проверкой нововведений, нужно выкачать исходный код из системы контроля версий с помощью готовых пресетов GitHub Actions, а также установить необходимые зависимости. Таковыми в нашем случае являются .NET SDK 9.0, pvs-studio-dotnet
и pvs-studio
, поскольку анализировать мы будем C# проект на основе сборочной системы MSBuild:
- name: Check out repository code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install tools
run: |
run: |
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 add-apt-repository ppa:dotnet/backports
sudo apt update
sudo apt-get install -y dotnet-sdk-9.0
sudo apt install pvs-studio
sudo apt install pvs-studio-dotnet
pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }}
Примечание. О том, как установить анализатор PVS-Studio на Linux, написано в соответствующем разделе нашей документации.
Теперь перейдём к основным шагам. Для начала нам необходимо собрать проект. Если проект не собирается, то и нет смысла его анализировать:
- name: Build
run: |
dotnet build sast-in-ci-example.sln
Если проект собрался, то можно начинать анализ. Для этого запускаем утилиту командной строки pvs-studio-dotnet
:
- name: Analyze
run: |
pvs-studio-dotnet \
-t sast-in-ci-example.sln \
-C .pvsconfig \
-o ./sast-in-ci-example.json \
-r --disableLicenseExpirationCheck -F
Разберёмся в том, какие флаги здесь для чего используются. С помощью флага -t
мы передаём файл решения, который необходимо проанализировать.
С помощью -C
передаём файл конфигурации правил .pvsconfig
. В нашем примере этот файл содержит настройки для исключения некоторых путей из анализа:
//V_EXCLUDE_PATH **AssemblyInfo.cs
//V_EXCLUDE_PATH **AssemblyAttributes.cs
С помощью -o
мы указываем, по какому пути сохранить отчёт анализатора, а флаг -r
включает отображение прогресса анализа, чтобы его можно было увидеть в логах сборки.
Флаг –disableLicenseExpirationCheck
выключает отображение сообщения о скоро истекающей лицензии. В примере это необходимо, поскольку анализ проводился на пробной версии анализатора.
Примечание. Запросить пробную лицензию анализатор PVS-Studio можно здесь.
Флаг -F
включает режим анализа модифицированных файлов. В этом режиме анализатор высчитывает хеши файлов проекта и сравнивает их с ранее посчитанными хешами, которые записаны в специальном .json
-файле в директории .pvs-studio
проекта. Для тех файлов, у которых хеши различаются, запускается анализ, а остальные игнорируются. Таким образом, мы можем проанализировать только те файлы, которые изменились в текущем запросе на слияние веток.
Примечание. Режим анализа модифицированных файлов работает только для проектов на основе сборочной системы MSBuild. Подробнее об этом режиме, а также об альтернативах для других проектов, мы писали ранее в другой статье.
После прохождения анализа нам необходимо сконвертировать отчёт. Этим workflow занимается на следующем шаге:
- name: Convert report
if: always()
run: |
plog-converter sast-in-ci-example.json \
-t json -n relative -R toRelative -r $PWD
plog-converter relative.json \
-t sarif -n pvs-report -r file://
Здесь мы первым вызовом утилиты командной строки plog-converter
заменяем абсолютные пути в изначальном отчёте на относительные, подставляя вместо текущей директории метку source tree root (|?|
).
Вторым вызовом утилиты уже конвертируем отчёт в международный формат SARIF, который используется в CodeQL — инструменте работы со статическими анализаторами внутри GitHub. А также заменяем ранее проставленные метки source tree root на file://
, чтобы получить правильный с точки зрения CodeQL URI.
Примечание. Подробнее прочитать о работе утилиты конвертации отчётов
plog-converter
можно в соответствующем разделе нашей документации.
Следующим этапом нам необходимо загрузить полученный на предыдущем шаге отчёт в CodeQL, чтобы обеспечить взаимодействие анализатора с GitHub, а также чтобы была возможность просмотра отчёта анализатора прямо на странице запроса на слияние:
- name: Publish report
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: pvs-report.sarif
category: PVS-Studio
На этом шаге мы указываем используемый инструмент, а также путь до отчёта анализатора.
Обращу внимание на то, что для двух последних шагов установлено условие запуска if: always()
. Почему так? Анализатор в ситуации, когда будут найдены срабатывания, вернёт ненулевой код возврата, из-за чего шаг анализа будет считаться проваленным. Но при этом нам же необходимо загрузить отчёт в CodeQL, чтобы требовать прохождения Quality Gate для слияния веток.
"Можно было бы параметризировать запуск этих стадий только при падении стадии анализа" — могли подумать вы. Но тут всё тоже не так просто. В случае, когда анализатор не нашёл, на что поругаться, нам всё равно необходимо загрузить пустой отчёт, чтобы GitHub знал, что ошибок нет и можно разрешать слияние.
Следующим шагом мы будем коммитить файл, в котором анализатор сохраняет хеши зависимостей проекта. Это нужно для того, чтобы при следующем запуске были проанализированы только те файлы, которые изменялись в Pull Request'е. Важно, что уже этот шаг будет запускаться только в том случае, когда стадия анализа прошла успешно, то есть мы буквально подтвердим, что в текущих правках нет проблем, и их спокойно можно сливать в главную ветку:
- name: Commit dependency caches
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
run: |
git config user.name 'github-actions[bot]'
git config user.email \
'github-actions[bot]@users.noreply.github.com'
git add .pvs-studio
if ! git diff --cached --quiet; then
git commit \
-m "[skip actions] PVS-Studio dependency caches"
git pull --rebase origin "$BRANCH_NAME"
git push origin HEAD:"$BRANCH_NAME"
else
echo "No changes to commit, skipping push"
fi
Также важно, что эти коммиты происходят от пользователя github-actions[bot], а в сообщении указано [skip actions]
, что увидит GitHub Actions и не запустит повторную проверку после такого коммита.
Полный текст YAML-файла
name: Build and PVS-Studio analysis
on:
pull_request:
branches: [main]
jobs:
build-analyze:
if: github.actor != 'github-actions[bot]'
permissions: write-all
runs-on: ubuntu-24.04
steps:
- name: Check out repository code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install tools
run: |
run: |
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 add-apt-repository ppa:dotnet/backports
sudo apt update
sudo apt-get install -y dotnet-sdk-9.0
sudo apt install pvs-studio
sudo apt install pvs-studio-dotnet
pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }}
- name: Build
run: |
dotnet build sast-in-ci-example.sln
- name: Analyze
run: |
pvs-studio-dotnet -t sast-in-ci-example.sln \
-o ./sast-in-ci-example.json -r \
--disableLicenseExpirationCheck -F \
-C .pvsconfig
- name: Convert report
if: always()
run: |
plog-converter sast-in-ci-example.json \
-t json -n relative -R toRelative -r $PWD
plog-converter relative.json -t sarif -n pvs-report -r file://
- name: Publish report
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: pvs-report.sarif
category: PVS-Studio
- name: Commit dependency caches
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git add .pvs-studio
if ! git diff --cached --quiet; then
git commit -m "[skip actions] PVS-Studio dependency caches"
git pull --rebase origin "$BRANCH_NAME"
git push origin HEAD:"$BRANCH_NAME"
else
echo "No changes to commit, skipping push"
fi
Настройка Ruleset'а
После того, как мы настроили workflow, необходимо создать Ruleset, по которому будет работать главная ветка проекта.
Для этого необходимо перейти в настройки репозитория, а там открыть раздел Rules. В этом разделе необходимо нажать на New ruleset, после чего появится форма создания нового Ruleset'а.
Сверху необходимо выбрать его название, а также установить Enforcement status в значение Active, чтобы после создания наш Ruleset был применён автоматически:

Далее в разделе Target branches нужно указать главную ветку проекта:

А теперь в разделе Branch rules необходимо немного поковыряться, чтобы обезопасить главную ветку репозитория.
Нужно выбрать пункт, требующий от веток быть актуальными перед слиянием. Таким образом мы обеспечим запуск проверок в CI на самой актуальной версии кода.
Далее можно выбрать пункт Block force pushes, который запретит попадание непроверенного ранее кода в main
ветку.
Ну и последним пунктом необходимо выбрать Require code scanning results, чтобы код нельзя было слить в основную ветку без проверки статическим анализом. В небольшом меню необходимо выбрать PVS-Studio как используемый инструмент, а также установить, какой уровень ошибки является критическим. В нашем случае выставим оба значения в All, то есть для тестового проекта критично попадание любой ошибки в исходный код:

Далее остаётся только сохранить настройки.
Технические шоколадки
Изначально в примере, помимо перечисленных выше настроек для слияния веток, было необходимо прохождение всех проверок без падений (параметр Require status checks to pass), но здесь возникла проблемой. Если выставить эту настройку, то после коммита бота GitHub всё ещё будет ждать прохождения workflow, однако тот не запустится из-за того, что в YAML мы указали, что после коммита от бота нам не нужно проверять всё ещё раз.
Чтобы попробовать решить эту проблему, добавим в начало workflow ещё одну стадию, которая будет возвращать exit 0
в случае, когда проверка запущена от коммита бота. Оказалось, что GitHub Actions в целом не хочет запускать проверку для коммита, который произошёл во время предыдущей проверки :(
Тестируем
Поскольку проект тестовый, мы можем и специально допустить ошибку в исходном коде, чтобы проверить, сработает ли наш пайплайн :)
if (a < b && a < b) <=
{
Console.WriteLine(
"Here will be a PVS-Studio warning :)"
);
}
Предупреждение PVS-Studio: V3001 There are identical sub-expressions 'a < b' to the left and to the right of the '&&' operator. Program.cs 4 1
После открытия Pull Request'а из новой ветки с этим фрагментом кода, автоматически запускается проверка, а после её окончания мы увидим вот такое красивое окошко на странице Pull Request'ов:

Чуть ниже также написано о том, что слияние веток невозможно из-за найденных анализатором PVS-Studio предупреждений:

Нажав на Code scanning results, мы попадём на страницу, где будут отображаться все результаты анализа:

В нашем случае здесь всего одно срабатывание, которое ранее было специально добавлено.
Другие варианты
В статье мы рассмотрели вариант использования статического анализатора PVS-Studio в CI с помощью GitHub Actions. Однако такой вариант может подходить не всем, поэтому уделим немного внимания и другим возможным вариантам.
Среди облачных систем непрерывной интеграции, помимо GitHub Actions, анализатор PVS-Studio также может быть интегрирован в CircleCI, Travis CI, GitLab и Azure DevOps.
Среди традиционных CI систем PVS-Studio поддерживает интеграцию в Jenkins и TeamCity.
Также не стоит забывать и о веб-дашбордах, с помощью которых можно работать с отчётами статических анализаторов. Среди таких инструментов PVS-Studio поддерживает работу с SonarQube, DefectDojo и CodeChecker.
Примечание. О том, как запустить CodeChecker и импортировать в него результаты анализа PVS-Studio, мы писали в другой статье чуть ранее. Прочитать её можно здесь.
La commedia è finita
Как видите, интеграция статического анализа в CI не такая уж сложная задача. Главное здесь — не воспринимать статический анализ как что-то дополнительное или "для галочки", а встроить его в привычный процесс разработки, сделав его неотъемлемой частью работы над качеством кода.
Правильно настроенный анализатор, автоматически запускающийся на этапе Pull Request'а, позволяет обнаруживать ошибки до того, как они попадут в основную ветку, и обеспечивает разработчиков своевременной обратной связью. А это, в свою очередь, делает команду более уверенной в качестве продукта, который она выпускает.
Статический анализатор не сделает всю работу за вас, но он сэкономит ваши время и нервы. А значит, оставит больше сил на то, чтобы писать полезный, а не проблемный код.
О регулярном статическом анализе с точки зрения безопасной разработки, кстати, мы разговаривали с коллегами из Inseq в совместном вебинаре, запись которого можно найти здесь.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Valerii Filatov. Static analysis for pull requests. Another step towards regularity.