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

Внедрение анализатора в проект и baseline
Довольно частой проблемой при внедрении статического анализатора в уже существующий проект является то, что внедряется он не на "этапе котлована". Поэтому после первого запуска анализатор ругается на написанный ранее код. И если кодовая база проекта достаточно обширная, то срабатываний будет действительно много:

А теперь представьте, что мы сразу же возьмёмся размечать все эти срабатывания, а также исправлять найденные ошибки... Во-первых, мы потратим довольно много времени на это, а во-вторых, разоримся на психологах для разработчиков :)
Что же делать в таком случае?
Для начала мы должны понимать, что перед использованием статический анализатор нужно настраивать. В анализаторе PVS-Studio, например, сейчас более 1500 диагностических правил суммарно для четырёх языков. Понятное дело, что не все эти диагностики могут быть вам полезны.
Например, есть такая вещь, как стандарты MISRA для C и C++. В нашем анализаторе есть поддержка этих стандартов и соответствующие диагностические правила. MISRA отличается дотошностью в обеспечении безопасности и ругается на самые мелкие проблемы в коде, потому что стандарт изначально был создан для разработки программного обеспечения в области автомобильной промышленности, а сегодня используется в любой сфере, где стоимость ошибки невероятно высока. Поэтому если вы не пишете код в соответствии с этими стандартами, то всё, что вам дадут срабатывания MISRA диагностик, это лишний шум в отчёте.
Примечание. Если вам интересны стандарты MISRA, то у нас есть страница на сайте, посвящённая их поддержке в анализаторе PVS-Studio.
Ну а когда мы прошли вводные, то можно перейти и к основной теме. Нам нет смысла в бешеном темпе размечать и исправлять все эти срабатывания. Статический анализатор внедряли не в вакууме: какой-то код в проекте уже написан, и думаю, мы уверены в том, что он работает.
Поэтому мы можем принять состояние проекта на момент внедрения анализатора как baseline и подавить все срабатывания, которые сейчас имеются. При этом не нужно думать, что мы просто забудем про них: мы лишь откладываем их в технический долг, чтобы постепенно исправлять позже. Просто срабатывания, которые будут появляться после момента внедрения статического анализатора, являются для нас на текущем этапе более приоритетными.
В статическом анализаторе PVS-Studio для таких случаев есть режим suppress, в котором анализатор сохраняет подавленные сообщения в так называемые suppress-файлы. Этот режим не требует внесения изменений в исходный код, а также удобен для массового подавления срабатываний. Анализатор сохраняет сигнатуру срабатывания, в том числе и исходный код, на который оно было выдано, чтобы в будущем без ведома пользователя срабатывание не вернулось в отчёт.
Как же подавить срабатывания анализатора? Например, в плагине PVS-Studio для интегрированной среды разработки Visual Studio есть специальная кнопка в меню просмотра отчёта, с помощью которой можно подавить все срабатывания анализатора:

После нажатия анализатор запишет все срабатывания в основной suppress-файл проекта, при этом они пропадут из окна просмотра отчёта.
Также есть возможность подавления конкретных срабатываний с помощью выпадающего меню:

Подобная функциональность присутствует во всех плагинах PVS-Studio для интегрированных сред разработки: IntelliJ IDEA, CLion, Rider, Visual Studio, Visual Studio Code и Qt Creator.
В плагине для Visual Studio с помощью специального меню Suppress Messages плагина можно посмотреть, какие срабатывания были подавлены:

Примечание. Подробнее о подавлении срабатываний в плагинах для интегрированных сред разработки и для других интеграций анализатора можно прочитать в соответствующем разделе нашей документации.
Подавление ложноположительных срабатываний
Ещё одна ситуация, в которой мы можем захотеть убрать срабатывание из отчёта, это возникновение ложного срабатывания. Нужно понимать, что, выдавая предупреждения на код, статический анализатор руководствуется вероятностным подходом, и ситуация, когда анализатор ошибся, тоже может произойти.
Примечание. Иногда вместо борьбы с ложными срабатываниями лучшим решением будет просто отключить анализ конкретных файлов. Например, для автогенерируемых файлов анализатор может выдать много различных срабатываний, убрав которые, мы избавимся от лишнего шума в отчёте.
Подавить ложное срабатывание в анализаторе PVS-Studio можно с помощью специальных комментариев. Допустим, в таком фрагменте анализатор выдал срабатывание:
size_t n = 100;
for (unsigned i = 0;
i < n; // здесь получаем V104
i++)
{
// ...
}
Если мы уверены, что это срабатывание ошибочное и готовы убрать его из отчёта, необходимо добавить вот такой комментарий с номером диагностического правила:
size_t n = 100;
for (unsigned i = 0;
i < n; //-V104
i++)
{
// ...
}
Если необходимо подавить срабатывания сразу нескольких диагностик на одну и ту же строку, то делается это столь же просто:
struct Small { int *pointer; };
struct Big { int *array[20]; };
int Add(const Small &a, Big b) //-V835 //-V813
{
return *a.pointer + *b.array[10];
}
Также можно ставить дополнительный хэш-код к метке False Alarm. При изменении строки, в которой мы установим такой хэш-код, выданные на неё предупреждения анализатора не будут отмечены как ложные срабатывания, так как хэш-код изменённой строки будет отличаться от кода из метки.
Для включения этой функции необходимо добавить в файл конфигурации правил .pvsconfig
следующий флаг:
//V_ENABLE_FALSE_ALARMS_WITH_HASH
В коде метка с хэш-кодом выглядит следующим образом:
//-V817 //-VH"3652460326"
Примечание. Об этом режиме и других возможностях файлов конфигурации диагностик можно прочитать в соответствующем разделе нашей документации.
Более сложная ситуация возникает с подавлением срабатываний в макросах. Ведь анализатор будет выдавать срабатывания в тех местах, где макрос используется, то есть там, где происходит подстановка его тела в код:
#define TEST_MACRO \
int a = 0; \
size_t b = 0; \
b = a;
void func1()
{
TEST_MACRO // здесь V1001
}
void func2()
{
TEST_MACRO // и здесь V1001
}
В таком случае можно воспользоваться специальной пометкой, которая укажет анализатору, что во всех случаях использования этого макроса срабатывания являются ложноположительными:
//-V:TEST_MACRO:1001
#define TEST_MACRO \
int a = 0; \
size_t b = 0; \
b = a;
void func1()
{
TEST_MACRO
}
void func2()
{
TEST_MACRO
}
Также можно просто убрать эти срабатывания. Для этого нужно оставить вот такой комментарий:
//-V::1001:TEST_MACRO
....
Как и в прошлом примере, для макросов возможно указать сразу несколько диагностических правил:
//-V:TEST_MACRO:1001, 105, 201
Вариант с расстановкой комментариев в исходном коде вручную может быть не самым удобным, поэтому плагины PVS-Studio для интегрированных сред разработки позволяют упростить этот процесс до нажатия всего одной кнопки Mark selected messages as False Alarms в контекстном меню:

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

Если вдруг возникнет необходимость вернуться к срабатываниям, помеченным как ложноположительные, в настройках есть опция DisplayFalseAlarms, позволяющая включать помеченные срабатывания в отчёт анализатора.
Подобная возможность, как и массовое подавление срабатываний, доступна во всех плагинах PVS-Studio для интегрированных сред разработки.
Примечание. Подробнее об этих и других методах борьбы с ложноположительными срабатываниями можно прочитать в соответствующем разделе нашей документации.
Также не стесняйтесь сообщать нам в техническую поддержку о ложных срабатываниях. Для нас важно качество работы анализатора, и мы занимаемся исправлением возникающих ложных срабатываний, если это возможно. Связаться с нами можно с помощью специальной формы обратной связи на сайте.
Отключение диагностических правил на фрагменте кода
Иногда нужно отключить диагностику, причём не глобально, а в конкретном блоке кода. Для этого анализатор PVS-Studio для C и C++ проектов поддерживает специальные директивы #pragma
:
#pragma pvs(push)
— сохраняет текущие настройки включения/отключения диагностик;#pragma pvs(disable: XXXX, YYYY, ...)
— выключает диагностики с номерами из списка;#pragma pvs(enable: XXXX, YYYY, ...)
— включает диагностики с номерами из списка;#pragma pvs(pop)
— восстанавливает предыдущие настройки.
Пример:
void func(int* p1, int* p2)
{
if (!p1 || !p2)
return;
#pragma pvs(push)
#pragma pvs(disable: 547)
if (p1) // V547 не будет выдана
do_something();
#pragma pvs(pop)
if (p2) // V547 Expression 'p2' is always true.
do_other();
}
Чтобы избежать предупреждений о неизвестных #pragma
, передайте компилятору флаг:
GCC/Clang:
-Wno-unknown-pragmas
MSVC:
-wd4068
Заключение
Мы поговорили о том, как внедрить статический анализатор в уже существующий проект без лишней головной боли с помощью механизма baseline, который позволяет игнорировать старые срабатывания и сосредоточиться на новых. Также мы обсудили, что анализатор не идеален и может выдавать ложноположительные предупреждения, но в PVS-Studio для этого предусмотрены удобные способы подавления срабатываний как массово, так и локально.
Если ранее вы сомневались в том, стоит ли внедрять статический анализатор в свой проект, вы всегда можете попробовать статический анализатор PVS-Studio на своём коде, получив бесплатную лицензию. Переделывая известную поговорку: "Лучше один раз попробовать, чем сто раз услышать или прочитать".
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Valerii Filatov. Seamless static analysis integration and overcoming false positives.