>
>
>
Что нового в PVS-Studio для Unreal Engi…

Евгений Фёклин
Статей: 5

Что нового в PVS-Studio для Unreal Engine?

Статический анализатор PVS-Studio постоянно совершенствуется для повышения эффективности анализа кода. В этой статье мы расскажем о последних обновлениях анализатора, которые существенно улучшают анализ проектов на основе Unreal Engine.

Ложное срабатывание из-за макроса check

История началась с обращения Epic Games (разработчиков Unreal Engine) о большом количестве ложных срабатываний при использовании встроенных макросов:

struct AAA 
{
  virtual int  aaa() { return 1; }
  virtual void bbb() {}
};

static void ccc(AAA* ptr)
{
  check(ptr);
  int ddd = ptr->aaa();

  if (ptr) // <= V595 The 'ptr' pointer was utilized before
           //         it was verified agains nullptr.
  {
    ptr->bbb();
  }
}

Ожидается, что если поток управления перейдёт на следующую инструкцию после передачи указателя в макрос check, то указатель ptr будет ненулевым. Анализатор считал иначе и выдавал при этом ложное срабатывание. Также Epic Games уточнили, что эта проблема возникает только при использовании конфигурации Shipping.

Конфигурация Shipping обычно предназначена для релизной сборки, но разработчики часто используют её в процессе разработки, чтобы:

  • Применить максимальные оптимизации: конфигурация Shipping включает все доступные оптимизации, что позволяет выявить потенциальные проблемы производительности на ранних этапах.
  • Сохранить возможность отладки: несмотря на оптимизации, конфигурация Shipping по-прежнему позволяет использовать отладочную информацию и некоторые инструменты отладки.

При более подробном изучении проблемы мы выяснили, что ложное срабатывание возникает из-за макроса CA_ASSUME, который находится внутри макроса check:

#define CA_ASSUME( Expr ) __analysis_assume( !!( Expr ) )

Выше мы уже упоминали, что Shipping сборки близки к Release, а потому после препроцессирования он разворачивается в noop-операцию (т.е. не выполняющую никаких действий). Примерно так:

{
  ....
  do
  {
    __noop(!!(ptr));
  }
  while ((0, 0) __pragma(warning(pop)));
};

Интересный факт: Epic Games сообщили нам, что Clang Static Analyzer не выдаёт ложных срабатываний на таком паттерне. Это натолкнуло нас на мысль посмотреть имплементацию макроса CA_ASSUME для Clang Static Analyzer:

__declspec(dllimport, noreturn) void CA_AssumeNoReturn();

#define CA_ASSUME( Expr ) ( __builtin_expect(!bool(Expr), 0) \
                              ? CA_AssumeNoReturn() \
                              : (void)0 )

Макрос разворачивается в вызов noreturn-функции, если переданное выражение вычисляется как false. Именно это и помогает Clang Static Analyzer понять, что указатель после вызова макроса ненулевой.

После обсуждения с Epic Games было принято решение сделать отдельную имплементацию проверочных макросов, что обеспечит корректную работу анализатора PVS-Studio. Функционал начнёт работать, начиная с релизов PVS-Studio 7.33 и Unreal Engine 5.5.

Новый формат отчёта

Совместно с разработчиками Unreal Engine мы изменили промежуточный формат вывода предупреждений. Ядро C и C++ анализатора уже достаточно давно генерирует предупреждения в новом формате, главная фишка которого для пользователя — поддержка многофайловой навигации по коду. К сожалению, если вы проверяли свои проекты, основанные на Unreal Engine, при помощи интеграции с UnrealBuildTool, то этот функционал был недоступен до релиза PVS-Studio 7.30.

К счастью, теперь без проблем можно узнать, место разыменования нулевого указателя и путь, по которому он к нему пришёл.

Поддержка анализа через SN-DBS

SN-DBS — это распределённая система сборки, разработанная для значительного повышения производительности компиляции. Достигается это за счёт распределения задач сборки между несколькими узлами, что особенно полезно для крупномасштабных проектов.

Ранее при попытке анализа проекта, собираемого через SN-DBS, с помощью PVS-Studio происходила проблема: анализ проходил только на той части файлов, которые обрабатывались на мастер-ноде. В логах сборки при этом появлялось сообщение об ошибке.

Исправление этой проблемы уже готово и планируется к добавлению в предстоящий релиз Unreal Engine 5.5.

Новые настройки в UnrealBuildTool

Интеграция PVS-Studio через файл target файл

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

Для интеграции вам нужно добавить параметр 'StaticAnalyzer' со значением ' PVSStudio':

Для версии 5.0 и ниже:

WindowsPlatform.StaticAnalyzer = WindowsStaticAnalyzer.PVSStudio;

Для версии 5.1 и выше:

StaticAnalyzer = StaticAnalyzer.PVSStudio;

Кроме параметра 'StaticAnalyzer', вы можете добавить другие настройки, о которых расскажем ниже.

Отключение анализа автогенерируемых файлов

Начиная с Unreal Engine 5.4, анализ автогенерируемых файлов (с расширением *.gen.cpp) отключён по умолчанию. Сделано это в целях снижения времени анализа крупных проектов. При желании можно вернуть старое поведение с помощью специального флага:

 -StaticAnalyzerIncludeGenerated

Настройка вывода предупреждений в вывод UnrealBuildTool

В Unreal Engine 5.4 была добавлена настройка, которая задаёт уровень предупреждений, выдаваемых UnrealBuildTool при анализе. Она не влияет на работу PVS-Studio, но может привести к замедлению при получении отчёта. Это происходит из-за включённой по умолчанию настройки. Следовательно, UnrealBuildTool будет выводить больше предупреждений, чем необходимо, что приводит к увеличению времени генерации и обработки отчёта.

Чтобы отключить её и избежать вышеуказанной проблемы, добавьте следующий флаг в строку запуска UnrealBuildTool:

-StaticAnalyzerPVSPrintLevel = 0

Альтернативно, можно задать эту настройку в файле *.Target.cs:

StaticAnalyzerPVSPrintLevel = 0;

Отключение анализа ядра Unreal Engine

Начиная с Unreal Engine 5.4 стала доступна настройка, позволяющая запускать анализатор только на файлах проекта (пропуская анализ модуля Unreal Engine). Использование этой настройки позволяет значительно ускорить процесс анализа, особенно если в исходный код Unreal Engine не вносились никакие изменения.

Чтобы включить эту настройку, добавьте следующий флаг в строку запуска UnrealBuildTool:

-StaticAnalyzerProjectOnly

Альтернативно, можно задать эту настройку в файле *.Target.cs:

bStaticAnalyzerProjectOnly = true;

Оптимизация по потреблению памяти при анализе инстанцирований

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

  • Технология Single Compilation Unit: Unreal Engine использует технологию Single Compilation Unit, также известную как unity build или jumbo build. Это означает, что N юнитов трансляции объединяются в один. Хотя этот подход имеет свои преимущества для компиляции и анализа, он также приводит к большему объёму анализируемых препроцессированных файлов, более длительным временем обработки и увеличению потребления памяти.
  • Обширное использование шаблонов: Unreal Engine — современный С++ проект, а значит широко использует шаблоны. Ну а мы — современный анализатор, который проводит анализ всех инстанцирований, что приводит к увеличению как потребления памяти, так и времени анализа.

Мы значительно оптимизировали производительность анализатора (как время анализа, так потребляемую память), достигнув значительного прогресса в этом направлении. Тем не менее мы всё ещё рекомендуем отключать анализ unity pack'ов при работе с большими проектами. Как это сделать можно узнать здесь.

Новые диагностические правила для проектов на основе Unreal Engine

В PVS-Studio доступны диагностические правила для выявления багов, специфичных для проектов на основе Unreal Engine:

  • V1100: Объявление нестатического поля класса в виде указателя на тип, унаследованный от 'UObject', внутри класса/структуры, который не унаследован от типа 'UObject'. Сборщик мусора Unreal Engine может уничтожить объект, адресуемый этим указателем.
  • V1102: Объявление сущности, несоответствующей соглашениям о наименованиях для проектов на основе Unreal Engine. Соответствие этому соглашению требуется для корректной работы Unreal Header Tool.

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

Мы будем признательны за любые предложения по новым правилам. Более подробно об этом можно почитать здесь.

Борьба с ложными срабатываниями в старых диагностических правилах

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

  • V542: Очень подозрительное явное приведение типов
  • V557: Потенциальный доступ к памяти за границами массива
  • V623: Возможная ошибка при работе с тернарным оператором '?:'
  • V758: Обнаружение ссылки, которая может стать недействительной
  • V781: Ситуация, когда значение переменной используется в качестве размера или индекса массива до его проверки

Мы не останавливаемся на достигнутом! Сейчас работаем над улучшением следующего пакета диагностических правил.

Заключение

Надеемся, что описанные в статье улучшения помогут как разработчикам Epic Games, так и пользователям Unreal Engine.

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

Оставайтесь с нами, чтобы быть в курсе всех последних обновлений :)