Пример, как в PVS-Studio появляются новые диагностики
Пользователи иногда спрашивают, как появляются новые диагностики в статическом анализаторе PVS-Studio. Мы отвечаем, что черпаем вдохновение из разнообразнейших источников: книг, стандартов кодирования, собственных ошибок, писем наших пользователей и так далее. Сегодня мы придумали новую интересную диагностику и решили рассказать историю, как это произошло.
Всё началось с проверки проекта COVID-19 CovidSim Model и статьи про неинициализированную переменную. Проект оказался маленьким и написанным с использованием современного стандарта языка C++. Это значит, что он отлично может пополнить базу тестовых проектов для регрессионного тестирования ядра анализатора PVS-Studio.
Однако, прежде чем пополнить базу новым проектом, полезно просмотреть выдаваемые предупреждения, чтобы выявить новые закономерности (паттерны) ложных срабатываний и выписать их для дальнейшей доработки анализатора. Также это дополнительная возможность заметить, что ещё что-то не так. Например, сообщение неудачно описывает ошибку для определённой конструкции кода.
Программист, которому поручили добавить проект в тестовую базу, подошёл к задаче основательно и решил заглянуть в раздел MISRA-диагностик. Вообще, это делать было необязательно, так как это весьма специфичная группа диагностик, которую нет смысла включать для таких проектов, как CovidSim.
MISRA С и MISRA C++ диагностики предназначены для разработчиков встраиваемых систем, и их суть сводится к ограничению использования небезопасных конструкций программирования. Например, не рекомендуется применять оператор goto (V2502), так как он провоцирует создание сложного кода, в котором легко допустить логическую ошибку. Подробнее с философией стандарта кодирования MISRA можно познакомиться в статье «Что такое MISRA и как её готовить».
Для прикладного программного обеспечения, которым как раз и является проект CovidSim, включать набор MISRA диагностик не имеет смысла. Пользователь просто утонет в огромном количестве малополезных для него сообщений. Например, экспериментируя с этим набором диагностик, мы получали более миллиона предупреждений для некоторых открытых проектов среднего размера. Грубо говоря, с точки зрения MISRA, в каждой третьей строчке кода может быть что-то не так:). Естественно, никто всё это смотреть и тем более править не будет. Проект или сразу разрабатывается с учётом MISRA рекомендаций, или для него этот стандарт кодирования неактуален.
Но мы отвлеклись от темы. Так вот, бегло просматривая MISRA предупреждения, коллега зацепился взглядом за предупреждение V2507, выданное для этого фрагмента кода.
if (radiusSquared > StateT[tn].maxRad2) StateT[tn].maxRad2 = radiusSquared;
{
SusceptibleToLatent(a->pcell);
if (a->listpos < Cells[a->pcell].S)
{
UpdateCell(Cells[a->pcell].susceptible, a->listpos, Cells[a->pcell].S);
a->listpos = Cells[a->pcell].S;
Cells[a->pcell].latent[0] = ai;
}
}
StateT[tn].cumI_keyworker[a->keyworker]++;
Правило V2507 заставляет оборачивать тела условных операторов в фигурные скобки.
В первый момент наш дотошный коллега подумал, что анализатор дал сбой. Ведь здесь есть блок текста в фигурных скобках! Перед нами ложное срабатывание?
Давайте присмотримся. Код только кажется корректным, но таковым не является! Фигурные скобки не относятся к оператору if.
Отформатируем код для наглядности:
if (radiusSquared > StateT[tn].maxRad2)
StateT[tn].maxRad2 = radiusSquared;
{
SusceptibleToLatent(a->pcell);
....
}
Согласитесь, это красивый баг. Он наверняка войдёт в Top10 C++ ошибок, найденных нами в 2021 году.
И что из этого следует? Подход, диктуемый стандартом MISRA, работает! Да, он заставляет писать везде фигурные скобки. Да, это утомительно. Но это оправданная плата за повышение надёжности встроенных приложений, используемых в медицинской технике, автомобилях и других системах высокой ответственности.
Хорошо, у разработчиков, использующих стандарт MISRA, всё нормально. Однако рекомендовать всем подряд использовать фигурные скобки — это плохая идея. Используя такой подход, очень легко довести анализатор до состояния, когда им станет невозможно пользоваться. Будет так много сообщений, что их просто никто не будет смотреть.
Вот мы и добрались для появления идеи для новой диагностики общего назначения. Можно сформулировать следующее правило.
Выдать предупреждение, если для оператора if выполняются следующие условия:
- весь условный оператор if записан в одну строчку и имеет только then-ветку;
- следующий statement после if — это compound statement, и он находится не на той же строке, что и if.
Заранее можно прогнозировать, что получится хорошее правило с низким количеством ложных срабатываний.
Именно так эта идея сейчас оформлена в нашей системе учёта задач. Возможно, в процессе реализации что-то будет сделано по-другому, но это уже неважно. Главное, появится хорошее диагностическое правило, которое начнёт выявлять новый паттерн ошибки. Далее мы распространим его на C# и Java ядра анализатора PVS-Studio.
Только что мы рассмотрели интересный пример, как было сформулировано новое диагностическое правило, которое затем будет реализовано в PVS-Studio. Скажем спасибо проекту CovidSim, стандарту кодирования MISRA и наблюдательности нашего коллеги.
Спасибо за внимание и следуйте за мной в мир С++ и багов :). Twitter. Facebook.
Дополнительный ссылки:
- Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей.
- Под капотом PVS-Studio для Java: разработка диагностик.
- Использование машинного обучения в статическом анализе исходного кода программ.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Example of How New Diagnostics Appear in PVS-Studio.