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

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

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

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

Анализатор выдаёт на этот фрагмент следующее предупреждение:

[CWE-571] There are identical sub-expressions to the left and to the right of the '==' operator: OriginalValue == OriginalValue RewriteStatepointsForGC.cpp 758

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

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

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

Так выглядит окно плагина PVS-Studio для Visual Studio 2022. Здесь мы можем видеть разные кнопки сверху, которые нужны для фильтрации, а также список предупреждений. Смысл ошибок в данный момент не важен. Я хочу обратить внимание на количество выданных предупреждений:

И тут заключается первая проблема – количество предупреждений. В этом примере их более 2000, и это много. При знакомстве, когда просто хочется попробовать инструмент, никто не будет анализировать столько предупреждений, потому что это банально тяжело. А ведь тут вполне может быть предупреждение о реальной ошибке, которая выстрелит, и вы потратите на её решение время и нервы.

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

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

А теперь самая важная и по счёту третья проблема – ложные срабатывания. Ложное срабатывание – это предупреждение анализатора об ошибке, которая по факту не является ошибкой.

Давайте представим, что первые десять предупреждений – это ложные срабатывания, а остальные – хорошие предупреждения. Мы начинаем смотреть предупреждения подряд, натыкаемся на эти 10 плохих срабатываний, говорим, что всё это бред какой-то и закрываем отчёт. В это же время 11-е предупреждение будет описанной в самом начале ошибкой, которая приведёт к неприятным последствиям.

Как бороться с проблемами первого отчёта

Для решения описанных ранее проблем я хочу предложить старую добрую работу с весами. Смотрите, у статического анализатора есть диагностические правила, каждое из которых отвечает за определённый паттерн ошибки. Поэтому вы садитесь, анализируете все созданные диагностики и задаём им начальный вес. Наилучшие диагностики получают больший вес, наихудшие – меньший. Что такое наилучшая диагностика? Это диагностика, которая имеет наименьший шанс ложного срабатывания, находит опасную ошибку или является очень интересной. Здесь на ваше усмотрение.

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

  • категория предупреждения;

  • предупреждения на тестах;

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

  • частота появления одной и той же диагностики.

И тут, чтобы было проще понять, что я имею в виду, я буду говорить на примере. Примером является реализация этого механизма в PVS-Studio под названием Best Warnings. Начнём по порядку.

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

Это изменение веса с учётом какой-то изначальной группировки предупреждений. Например, у нас все диагностики делятся на 3 группы: High, Medium, Low. High – наиболее достоверные ошибки, и их вес мы не трогаем. Medium – ошибки средней достоверности, и мы понижаем их вес на определенный коэффициент.

Предупреждения на тестах

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

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

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

Частота появления одной и той же диагностики

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

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

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

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

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

Вот отчёт. И теперь вместо того, чтобы сразу его анализировать, мы нажимаем 2 кнопки. Первая:

И вторая:

После этого отчёт начинает выглядеть вот так:

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

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

Заключение

Давайте подытожим. Добавив механизм работы с весами, решается сразу ряд проблем. Смотреть первый отчёт становится проще, так как количество выданных предупреждений становится реальным для анализа. Сами предупреждения становятся достовернее и разнообразнее. Также уменьшаются затраты по времени на знакомство с инструментом. Например, анализ Wolfenstein с учётом установки анализатора и просмотра предупреждений занял у меня около 10-15 минут.

Если после прочтения вам стало любопытно посмотреть, как это работает вживую, то вам сюда.

Спасибо за внимание.

Комментарии (2)


  1. Gryphon88
    07.12.2021 12:20
    +1

    Я долго ждал эту статью :) Вы пересказали моё знакомство с CppCat, было непонятно, за что хвататься.


    1. IchNikola Автор
      07.12.2021 14:48

      Ну надеюсь сейчас мы улучшили процесс знакомства :)