Внедрение статического анализатора в проект для некоторых людей выглядит непреодолимой преградой. Почему-то очень распространено мнение, что объём выданных результатов анализа при первом запуске настолько велик, что рассматривается только два варианта: не связываться с этим или переключить всех людей на исправление предупреждений. В этой статьей попробуем развеять этот миф, проведя внедрение и настройку анализатора на проекте GTK.
GTK – кроссплатформенная библиотека элементов интерфейса. Недавно состоялся релиз GTK 4, что стало хорошим инфоповодом изучить качество кода проекта с помощью статического анализатора кода PVS-Studio. Такая деятельность для нас является регулярной, и нам часто приходится настраивать анализатор с нуля на многих проектах перед исследованием качества кода. В этой заметке я поделюсь опытом быстрой настройки PVS-Studio на C++ проекте.
Получаем свой первый отчёт анализатора и видим следующие результаты для диагностик общего назначения:
4 (Fails) + 1102 (High) + 1159 (Medium) + 3093 (Low) = 5358 предупреждений.
Далее быстро прокручиваем отчёт, выявляем всплески неинтересных предупреждений и принимаем решение для дальнейшей настройки анализатора.
Рассмотрим такое предупреждение:
V530 [CWE-252] The return value of function 'g_strrstr_len' is required to be utilized. strfuncs.c 1803
/* Testing functions bounds */
static void
test_bounds (void)
{
....
g_strrstr_len (string, 10000, "BUGS");
g_strrstr_len (string, 10000, "B");
g_strrstr_len (string, 10000, ".");
g_strrstr_len (string, 10000, "");
....
}
Это код тестов, причём не относящихся непосредственно к GTK, поэтому составляем список директорий для исключения из анализа и перезапускаем PVS-Studio.
При следующем запуске из анализа будут исключены следующие директории:
gtk/_build/
gtk/subprojects/
gtk/tests/
gtk/testsuite/
Открываем отчёт и получаем следующий результат:
2 (Fails) + 819 (High) + 461 (Medium) + 1725 (Low) = 3007 предупреждений.
Ещё один положительный эффект, который мы получили после такой настройки, — это ускорение анализа.
Макросы, пожалуй, - одна из основных причин аномального количества срабатываний у некоторых диагностик. Поверхностно смотрим наш отчёт и замечаем множество подобных предупреждений:
V501 There are identical sub-expressions '* (& pipe->ref_count)' to the left and to the right of the '^' operator. gdkpipeiostream.c 65
static GdkIOPipe *
gdk_io_pipe_ref (GdkIOPipe *pipe)
{
g_atomic_int_inc (&pipe->ref_count);
return pipe;
}
Вносить изменения в макросы обычно сложнее всего - вряд ли за это кто-то возьмётся в компании. Первое время точно нет. Поэтому воспользуемся механизмом отключения диагностик на них. Быстро просмотрев отчёт, составляем такой файл настроек:
#V501
//-V:g_atomic_int_:501
#V547
//-V:GTK_IS_:547
//-V:GDK_IS_:547
//-V:G_IS_:547
//-V:G_VALUE_HOLDS:547
#V568
//-V:g_set_object:568
Всего несколько строчек, которые покрывают большинство проблемных макросов для V501, V547 и V568.
Смотрим результат:
2 (Fails) + 773 (High) + 417 (Medium) + 1725 (Low) = 2917 предупреждений.
Некоторые диагностики изначально выдают неподходящие предупреждения для конкретного проекта. Рассмотрим предупреждение V1042:
V1042 [CWE-1177] This file is marked with copyleft license, which requires you to open the derived source code. main.c 12
Это очень полезная диагностика для проектов с закрытым кодом, чтобы случайно не включить в них код с недопустимой лицензией. Но для GTK это неинтересная диагностика, поэтому отключим её и получим скорректированный результат:
2 (Fails) + 164 (High) + 417 (Medium) + 1725 (Low) = 2308 предупреждений.
В проекте имеются 2 предупреждения типа Fails:
Эта диагностика предупреждает, что срабатывания на этих файлах могут указывать не на те строки кода, которые нужно. Обычно разница составляет 1-2 строки, и происходит это из-за неверного раскрытия макросов компилятором. По нашему опыту, чаще всего за этим был замечен компилятор MSVC.
Эти предупреждения можно просто проигнорировать.
Итоговый результат такой:
164 (High) + 417 (Medium) + 1725 (Low) = 2306 предупреждений.
Наверняка, там ещё есть, что настроить, но я уже решил задачу, которую ставил перед собой: быстро получить отчёт анализатора, в котором легко найти ошибки. Например, предупреждение с номером V501 теперь всего одно во всём отчёте, и оно хорошее:
V501 There are identical sub-expressions 'G_PARAM_EXPLICIT_NOTIFY' to the left and to the right of the '|' operator. gtklistbase.c 1151
static void
gtk_list_base_class_init (GtkListBaseClass *klass)
{
....
properties[PROP_ORIENTATION] =
g_param_spec_enum ("orientation",
P_("Orientation"),
P_("The orientation of the orientable"),
GTK_TYPE_ORIENTATION,
GTK_ORIENTATION_VERTICAL,
G_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY | // <=
G_PARAM_EXPLICIT_NOTIFY); // <=
....
}
Это отличный результат! И показатели других диагностик тоже значительно выросли. Мизерными настройками удалось уменьшить отчёт анализатора на целых 57%. Соответственно, показатель верных предупреждений к ложным тоже значительно вырос.
Кстати, это одна из причин, по которой в интернете отсутствуют сравнения результатов разных анализаторов кода. Потому что нет единого мнения, стоит ли показывать работу анализатора как есть, или всё же надо провести первичную настройку. А у всех анализаторов свои механизмы для этого, что многократно усложняет сложность задачи сравнения.
А теперь время передать эстафету моему коллеге Андрею Карпову.
Даже такая простая и быстрая фильтрация результатов, описанная в этой статье, существенно облегчает работу с отчётом. Например, взяв этот отчёт, я в течение одного вечера смог бегло его просмотреть и выписать фрагменты кода с ошибками, в количестве достаточном для написания статьи. Собственно, её написанием я и займусь на новогодних праздниках.
Конечно, моя задача проще и сильно отличается от процесса настройки и внедрения анализатора в реальный проект. Мне достаточно поверхностно пробежаться по списку предупреждений и выписать явные ошибки, игнорируя ложные срабатывания или непонятные предупреждения в сложных участках кода. При реальном использовании понадобится больше времени, чтобы настроить анализатор, точечно подавить ложные срабатывания, улучшить макросы и так далее. Но на самом деле, это не страшно. Например, в статье про проверку проекта EFL Core Libraries я показал, что можно достаточно легко настроить анализатор, чтобы он выдавал всего 10-15% ложных предупреждений. Согласитесь, неплохо, когда на каждые 1-2 ложных срабатывания вы исправляете 8-9 ошибок.
Ну и не забывайте, что вам всегда доступен механизм массового подавления срабатываний анализатора. Это позволяет начать быстро использовать анализатор даже в большом проекте. Все предупреждения объявляются техническим долгом и пока не показываются. И команда начинает работать только с предупреждениями, относящимися к новому или изменённому коду. Подробнее про это предлагаю почитать в статье "Как внедрить статический анализатор кода в legacy проект и не демотивировать команду".
Спасибо за внимание и приходите через пару недель читать статью про найденные ошибки.