Основное назначение статического анализатора — обнаруживать и сообщать об ошибках в коде, чтобы потом можно было их исправить. Однако сообщать об ошибках не так просто, как может показаться. Те, кто только начинает работать со статическим анализом, и даже опытные разработчики, могут столкнуться с рядом проблем. В этой статье я расскажу об этих проблемах и о том, как с ними бороться.

Что такое статический анализ?

Статический анализ — это технология, позволяющая находить ошибки и потенциальные уязвимости в исходном коде без его фактического выполнения. Вот пример того, как это работает. Не так давно мир встретил релиз LLVM 13.0.0, и мы проверили его на наличие ошибок. И, конечно же, мы нашли любопытные ошибки в этом проекте. Вот один из них:

bool operator==(const BDVState &Other) const {
  return OriginalValue == OriginalValue && BaseValue == Other.BaseValue &&
    Status == Other.Status;
}

Анализатор выдает следующее предупреждение на код выше:

[CWE-571] Слева и справа от оператора == есть идентичные подвыражения: OriginalValue == OriginalValue RewriteStatepointsForGC.cpp 758

Как видите, кто-то сделал опечатку в первом сравнении — и написал OriginalValue вместо Other.OriginalValue. Это классическая ошибка в методах сравнения. И это может привести к разным и неприятным результатам.

Проблемы в первом отчете

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

Это окно плагина PVS-Studio в Visual Studio 2022. Окно содержит набор кнопок вверху, а также отображает список предупреждений. То, что говорят предупреждения, сейчас не имеет значения. Хочу обратить ваше внимание на другое — на количество показанных предупреждений:

И здесь кроется первая проблема — количество предупреждений. В данном примере их более 2000. Это много! Когда кто-то просто прощупывает почву, он не хочет проходить через столько предупреждений — это утомительно. Между тем, этот список может содержать предупреждение о крупной и вполне реальной ошибке, которая может выстрелить вам в ногу в ближайшем будущем и вынудит потратить уйму времени, сил и клеток мозга на ее исправление.

Ну, предположим, вы все-таки решили просмотреть этот длинный список. И тут вы столкнетесь с проблемой номер вторая — одинаковые предупреждения:

С точки зрения анализатора все в порядке. Он нашел много одинаковых ошибок в коде и сообщает только об этом. Однако сейчас речь идет о первом использовании анализатора. Чего они хотят — так это видеть разнообразие. Смотреть на 4 одинаковых предупреждения не круто.

Теперь пришло время для третьей проблемы — и самой важной. Ложные срабатывания. Ложное срабатывание — это предупреждение, сообщающее о коде, не содержащем ошибки.

Предположим, что первые десять предупреждений являются ложными срабатываниями, а остальные предупреждения являются законными и полезными. Человек начинает просматривать предупреждения одно за другим, видит эти 10 ложных срабатываний, делает вывод, что этот список — чепуха, и закрывает отчет. И все это при том, что предупреждение номер 11 — это ошибка, которую мы описали в начале — та, которая стреляет вам в ногу, когда вы меньше всего этого ожидаете.

Как решить проблемы первого отчета

Для решения описанных выше проблем я хотел бы предложить вам старый добрый подход с весами. Видите, у статического анализатора есть диагностические правила. Каждый отвечает на определенный шаблон ошибки. Итак, сначала вы садитесь, анализируете все диагностики и устанавливаете их первоначальный вес. Лучшая диагностика получает больший вес, худшая — меньший. Какая лучшая диагностика? Это диагностика, характеризующаяся одним из следующих признаков: она имеет наименьшую вероятность ложного срабатывания, обнаруживает серьезную ошибку или очень своеобразна. Это на вас.

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

  • категория предупреждения;
  • предупреждения для тестов;
  • количество выданных предупреждений на строку в указанном файле;
  • как часто запускается конкретная диагностика.

Чтобы было понятнее, я приведу пример. Вот как мы реализовали этот механизм — мы назвали его Лучшие предупреждения — в PVS-Studio. Я начну с самого начала.

Категория предупреждения

Это изменение веса основано на некоторой исходной системе, которая группирует предупреждения. Например, мы разбиваем нашу диагностику на три группы: Высокий, Средний и Низкий. Высокие стойки для самых надежных предупреждений, их вес мы не изменяем. Группа Medium содержит предупреждения средней надежности. К ним мы применяем коэффициент, который снижает их вес.

Предупреждения для тестов

В эту группу входят предупреждения, выданные для тестов или автоматически сгенерированных файлов. Иногда анализатор находит в тестах реальные ошибки, и это очень круто. Однако в большинстве случаев предупреждения, выдаваемые для такого кода, являются ложными срабатываниями из-за специфики такого кода. Вот почему предупреждения для тестов лучше оставить на потом. Нет необходимости показывать их тому, кто только начинает работать с анализатором.

Количество предупреждений, выданных на строку

Иногда одна строка кода вызывает несколько диагностик. Опыт показывает, что такие строки обычно содержат ошибку. Следовательно, логично добавить веса таким предупреждениям.

Как часто запускается конкретная диагностика

Если срабатывания диагностики занимают более N% всех предупреждений, это обычно означает предупреждения диагностики достаточно низкого значения. Такая реакция обычно связана со стилем кодирования или с небрежно написанными макросами в коде C или C++.

Все описанные выше категории решают проблему номер три. Чтобы решить вторую проблему и получить множество предупреждений, нужно взять все предупреждения диагностики и найти среди них самое тяжелое. Оставьте его вес без изменений и уменьшите вес остальных. Сделайте то же самое для каждой диагностики. В результате вы получаете различные предупреждения.

Теперь осталась только первая проблема — слишком много предупреждений. Чтобы решить эту проблему, отсортируйте все задачи по весу и покажите число N самых тяжелых. Таким образом, вы анализируете отчет вместо своих клиентов и предоставляете им самую суть того, что нашел анализатор.

Как это все выглядит в PVS-Studio

Наши плагины для Visual Studio теперь содержат кнопку, которую вы можете нажать, чтобы выполнить все вышеперечисленное. Он запускает скрипт, который мы создали. Вернемся к приведенному выше примеру и запустим его через этот механизм:

Вот отчет. Теперь, вместо того, чтобы рассматривать его сразу, мы нажимаем две кнопки. Один:

И два:

После этого отчет выглядит следующим образом:

Как видите, осталось только 10 предупреждений. Их намного легче пройти, чем 2000 :)

Мы также позаботились о том, чтобы список содержал различные предупреждения. Вы можете увидеть множество диагностических кодов, и мы решили разрешить не более 2 одинаковых предупреждений.

Заключение

Принимая во внимание все вышеперечисленное, весовой механизм решает сразу ряд задач. Просматривать первый отчет легче, потому что объем предупреждений уменьшается и их становится удобно просматривать. Предупреждения становятся более надежными и разнообразными. Клиенты экономят время и могут быстрее оценить инструмент. Например, на установку анализатора и просмотр предупреждений у меня ушло всего около 10–15 минут.

Чувствуете любопытство? Нажмите здесь, чтобы увидеть, как это работает в реальной жизни!

Спасибо за чтение!