Продолжаем изучать использование PVS-Studio на практике для бесплатных (и не только) проектов. В этой статье мы рассмотрим основной этап взаимодействия с инструментом — работу с отчётом! Подробно рассмотрим первый опыт разбора срабатываний, изучим интерфейс и посмотрим на ошибки в проекте osu!

Введение

PVS-Studio — это инструмент для поиска потенциальных ошибок и уязвимостей в коде на C, C++, C# и Java. Он работает на Windows, Linux и macOS и имеет множество сценариев интеграции: плагина в IDE, игровые движки, системы качества кода и многое другое. В коммерческом сегменте PVS-Studio — платное B2B-решение, которым пользуются многие команды и компании по всему миру.

Это третья часть из цикла статей про использование PVS-Studio с бесплатной лицензией. Мы продолжаем знакомство с инструментом и сегодня на практике рассмотрим основной этап работы со статическим анализатором — изучение отчёта и разбор ошибок!

Примечание. Если вам интересно, как получить бесплатную лицензию и настроить PVS-Studio перед первым анализом проекта, то обязательно ознакомьтесь с первой и второй частью цикла статей.

Первый разбор ошибок

Мы остановились в прошлой статье на том, что провели первичный анализ кода на open source проекте osu! и получили отчёт анализатора. Казалось бы, основная работа инструмента завершена, но нет. Самое интересное только начинается :)

Но правила игры немного меняются: теперь основная ответственность лежит на разработчике. От него зависит, правильно ли будут интерпретированы результаты анализа или нет, как будут исправлены выявленные проблемы. А анализатор в свою очередь будет выступать уже помощником, а не основным персонажем.

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

Интерфейс и Best срабатывания

Чтобы отобразить самые интересные срабатывания, нужно нажать на кнопку Best в интерфейсе отчёта. В результате останутся 10 отобранных сообщений анализатора.

Новых пользователей обилие кнопок в интерфейсе может немного напрягать, но поверьте, он достаточно простой. Давайте его рассмотрим поближе. На скриншоте мы отметили места, на которые сейчас можно обратить внимание, это: номер диагностического правила (1), сообщение анализатора (2), файл с ошибкой и строка, на которой было предупреждение (3 и 4).

Разбор ошибок

Пора бы уже перейти к главному — разбору срабатываний! Выберем первое сообщение из 10, полученных благодаря Best механизму.

Примечание. Учтите, что это не ТОП по срабатываниям анализатора, а лишь список наиболее интересных. Места и порядок тут значения не имеют.

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

Давайте разберём его поподробнее:

public partial class TestSceneBeatmapDifficultyCache
{
  ....
  AddStep(....)
  {
    var modRateAdjust = (ModRateAdjust)lookup.OrderedMods.SingleOrDefault(....);
    return new StarDifficulty
     (BASE_STARS + modRateAdjust?.SpeedChange.Value ?? 0, 0);
  }
}
Предупреждение PVS-Studio

V3123 Perhaps the '??' operator works in a different way than it was expected. Its priority is lower than priority of other operators in its left part. TestSceneBeatmapDifficultyCache.cs 66

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

Документация откроется прямо в IDE:

Там мы сможем найти описание проблемы, пример ошибки и её исправления.

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

BASE_STARS + (modRateAdjust?.SpeedChange.Value ?? 0)

Но мог ошибиться, не приняв во внимание особенности работы оператора ??, и получил такую:

(BASE_STARS + modRateAdjust?.SpeedChange.Value) ?? 0

Но почему мы считаем именно так? А всё просто: если взглянуть на код, а не только на отрывок, отмеченный анализатором (что и требуется делать при изучении потенциальной ошибки), мы сможем обнаружить важный контекст:

public partial class TestSceneBeatmapDifficultyCache
{
  public const double BASE_STARS = 5.55;
  ....
  AddStep(....)
  {
    var modRateAdjust = (ModRateAdjust)lookup.OrderedMods.SingleOrDefault(....);
    return new StarDifficulty
     (BASE_STARS + modRateAdjust?.SpeedChange.Value ?? 0, 0);
  }
  ....
  AddUntilStep($"star difficulty -> {BASE_STARS + 1.5}", 
    () =>   starDifficultyBindable.Value.Stars == BASE_STARS + 1.5);
  ....
  AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", 
    () =>   starDifficultyBindable.Value.Stars == BASE_STARS + 1.25);
  ....
  AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", 
    () =>   starDifficultyBindable.Value.Stars == BASE_STARS + 1.75);

}

Уже можно заметить, что BASE_STARS является константой и подобного паттерна, как в потенциальной ошибке, дальше тоже нет. Учитывая наличие константы BASE_STARS, было бы странно обнулять сумму, а не значение modRateAdjust?.SpeedChange.Value в случае, если проверка modRateAdjust? не пройдет.

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

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

Подавление ложного срабатывания

Разберём на примере аналогичного срабатывания (диагностика V3123) из того же проекта:

public class SamePatternsGroupedHitObjects
{
  public IReadOnlyList<SameRhythmHitObjectGrouping> Groups { get; }

  public SamePatternsGroupedHitObjects? Previous { get; }

  public double GroupInterval => 
    Groups.Count > 1 ? Groups[1].Interval :   Groups[0].Interval;

  public double IntervalRatio => 
    GroupInterval / Previous?.GroupInterval ?? 1.0d;
}
Предупреждение PVS-Studio

V3123 Perhaps the '??' operator works in a different way than it was expected. Its priority is lower than priority of other operators in its left part. SamePatternsGroupedHitObjects.cs 28

Сейчас сложно сказать, есть ли тут ошибка, о которой говорит нам анализатор. Но всё встаёт на свои места, если отобразить комментарий к этому коду:

public class SamePatternsGroupedHitObjects
{
  ....

  /// The ratio of GroupInterval between this 
and the previous SamePatternsGroupedHitObjects. 
  ///In the case where there is no previous 
SamePatternsGroupedHitObjects, this will have a value of 1.

  public double IntervalRatio => 
    GroupInterval / Previous?.GroupInterval ?? 1.0d;
}

Благодаря этому фактору мы можем понять, что же именно хотел разработчик. Если Previous?.GroupInterval отсутствует (null), то все выражение будет 1. Или, если добавить скобочки, то получится так:

public double IntervalRatio => 
  (GroupInterval / Previous?.GroupInterval) ?? 1.0d;

И мы можем понять одну вещь: ошибки нет! Да, конечно, это не так очевидно, если смотреть только на код. Без комментария понять не получилось бы... Но факт остаётся фактом: проблемы нет.

Что же это значит?.. Анализатор плохо работает? Отнюдь! Ложноположительные срабатывания — это не проблема, а особенность технологии, с которой надо уметь взаимодействовать и понимать, как отличить истинное срабатывание от ложного. При разработке диагностических правил мы стараемся минимизировать возможность возникновения ложных срабатываний, отсекаем очевидные и не очень случаи, учитываем различные известные паттерны использования тех или иных конструкций. Но иногда анализатору просто не хватает контекста. Как раз в этом может помочь настройка инструмента, о которой мы говорили в предыдущей статье.

А что же делать нам? Не просто же игнорировать срабатывание... Конечно же, нет! Мы берём и отмечаем его ложным, нажав правой кнопкой мыши по сообщению анализатора и выбрав первый пункт в меню:

Примечание. Этот способ отмечает срабатывание комментарием в исходном коде. Можно поступить иначе и нажать кнопку Add selected message to Suppression file, таким образом вы поместите их в файл, созданный анализатором, без модификации исходного кода.

Этот механизм не удаляет срабатывание из списка сообщений, а отмечает его ложным и скрывает из отчёта. Видимость для таких срабатываний можно вернуть, если включить настройку DisplayFalseAlarm в пункте Specific Analyzer Setting:

Примечание. После того, как вы "уберёте" сообщение из вкладки Best, место этого срабатывания займёт другое.

Затем так же смотрим оставшиеся срабатывания анализатора!

Понравился опыт? Хотите продолжить? Тогда идём дальше!

Дальнейшее взаимодействие

Окей, все 10 срабатываний разобраны и, возможно, исправлены. А что делать дальше? Логично будет пойти смотреть и исправлять остальные, правда ведь?... Да?..

Ну... почти.

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

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

Но не всё потерянно! На этот случай в нашем инструменте имеется особый механизм массового подавления:

Этот механизм даёт возможность скрыть все легаси-срабатывания, и анализатор будет выдавать предупреждения только на новый и изменённый код. Таким образом, можно начать пользоваться инструментом без сложностей, разбирая только новые потенциальные ошибки.

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

Более подробно мы рассказали про это в другой нашей статье.

Разбор интерфейса

После первого опыта давайте посмотрим на более продвинутые техники работы с отчётом. Для этого взглянем на интерфейс:

Здесь можно:

  • помечать интересные срабатывания;

  • подавлять ложноположительные срабатывания;

  • сортировать их по разным категориям;

  • выбирать уровни достоверности;

  • открывать документацию к диагностикам;

  • и ещё много чего полезного!

А теперь давайте разберем его более подробно.

Основной интерфейс отчёта

Давайте пройдемся ещё раз по функционалу интерфейса, но в этот раз охватим все функции.

Отбор лучших срабатываний

Для того, чтобы быстро оценить работу инструмента, имеется механизм Best, который соберёт 10 самых интересных срабатываний со всего отчёта. Чтобы его включить, нужно нажать кнопку Best в верхней части интерфейса:

Сортировка по уровням достоверности

В анализаторе можно сортировать сообщения по трём уровням достоверности: High, Medium, Low. Чем выше уровень достоверности, тем больше вероятность того, что ошибка реальна и может оказать влияние на программу. Для стандартной работы с результатами анализа мы советуем выключать уровень Low.

Включение/выключение групп диагностик

Вы можете включать и выключать группы диагностик, не заходя в настройки. К примеру, диагностики оптимизации или диагностики по стандарту OWASP:

Быстрая сортировка

Этот механизм позволяет отсортировать сообщения анализатора по введённому значению для определённого поля. Например, можно настроить вывод только сообщений диагностического правила V3022 из конкретного файла:

Отметка интересных срабатываний

Анализатор позволяет добавить срабатывания в Избранное. Эти отметки сохраняются в отчёте. По ним также можно отсортировать срабатывания:

Дополнительные возможности

  • отметить срабатывание ложным;

  • не проверять файлы из выбранного каталога;

  • подавить все сообщения в отчёте;

  • отобразить подавленные сообщения.

Работа с сообщениями анализатора

При нажатии правой кнопкой по сообщению анализатора у нас появляется контекстное меню:

В нём можно:

  • отметить срабатывание ложным;

  • убрать отметку ложного срабатывания;

  • подавить сообщение;

  • скрыть все подобные сообщения по номеру диагностики;

  • не проверять файлы и скрыть срабатывания из выбранного каталога;

  • отобразить дополнительные колонки (ID, Project, SAST, CWE);

  • отобразить полный путь до файла;

  • скопировать сообщение анализатора;

  • отметить сообщение анализатора;

  • добавить TODO комментарий;

  • перейти к другому сообщению по ID.

За дополнительной информацией о работе с отчётом в плагине PVS-Studio для Visual Studio вы всегда можете обратиться к документации.

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

Заключение

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

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

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Gleb Aslamov. How to get and use free PVS-Studio license. Part 3: working with report and warnings.

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