Как мы уменьшили количество ошибок
в Unreal Engine
с помощью статического анализа кода
Евгений Рыжков
evg@viva64.com
ООО «СиПроВер»
www.viva64.com
1/26
Структура доклада
• Этапы внедрения анализатора
кода в живой проект.
• Примеры ошибок из кода
Unreal Engine.
• Типы исправлений в коде.
• Зачем вообще знакомиться со
статическим анализом.
2/26
Этапы внедрения статического
анализатора кода
3/26
Этап 0. Научить инструмент проверять
проект
• Убедиться, что весь код компилируется
• Убедиться, что весь код корректно проверяется анализатором
4/26
Пример ошибки из кода Unreal Engine
if (*Buffer == TCHAR('"')) {
while (*Buffer &&
*Buffer != TCHAR('"') &&
*Buffer != TCHAR('n') &&
*Buffer != TCHAR('r'))
{
Buffer++;
}
V637 Two opposite conditions were encountered. The second condition is always false.
Check lines: 310, 312. propertystruct.cpp 310
5/26
Этап 1. Борьба с имеющимися
сообщениями
• Запустили анализатор на существующем проекте – получили 2000
сообщений.
• Бросаться их сразу править – плохая идея. Менеджер проекта
негодует.
• Оставить как есть – не увидите новых сообщений.
• Обязательно в инструменте должна быть возможность скрыть
этим сообщения. Где-то нет такой возможности? Не тот
инструмент.
6/26
Пример ошибки из кода Unreal Engine
V612 An unconditional 'return' within a loop.
unrealaudiotestgenerators.cpp 49
static float WrapTwoPi(float Value)
{
while (Value >= TWO_PI)
{
return Value -= TWO_PI;
}
while (Value < 0)
{
return Value += TWO_PI;
}
return Value;
} 7/26
Этап 2. Настройка регулярного запуска на билд-
сервере (и на машинах разработчиков)
• Абсолютно необходимая вещь – это настройка запуска на билд-
сервере.
• Надо давать всем разработчикам возможность ознакомиться с
результатами проверки.
• В идеале регулярный запуск нужен не только на билд-сервере по
ночам, но и на машинах разработчиков во время работы.
8/26
void FillUpTransformBasedOnRig(....)
{
....
const URig* Rig = Skeleton->GetRig();
int32 NodeNum = Rig->GetNodeNum();
if (Rig && NodeNum > 0)
{
....
}
....
}
Пример ошибки из кода Unreal Engine
V595 The 'Rig' pointer was utilized before it was verified
against nullptr. Check lines: 1844, 1846.
animsequence.cpp 1844
9/26
Этап 3. Правим новые ошибки
• Убедить разработчиков править все новые ошибки, чтобы было 0
сообщений.
• Здесь инструменты статического анализа могут предложить
только плетку менеджеру проекта, к сожалению.
10/26
Пример ошибки из кода Unreal Engine
V579 The Memcmp function receives the pointer and its size as arguments. It is possibly a
mistake. Inspect the third argument. pimplrecastnavmesh.cpp 172
bool FRecastQueryFilter::IsEqual(
const INavigationQueryFilterInterface* Other) const
{
return FMemory::Memcmp(this, Other, sizeof(this)) == 0;
}
11/26
Этап 4. Правим старые ошибки
• Очень желательно все-таки победить старые ошибки.
• Поговорим про скорость правки ошибок.
12/26
Ожидаемая скорость
правки ошибок
• Количество ошибок
уменьшается
равномерно с каждым
рабочим днём.
13/26
Фактическая скорость
правки ошибок
• На самом деле,
сообщения исчезают в
начале быстрее, чем
потом.
14/26
На что ушли 17 рабочих дней в UE?
Количество предупреждений
анализатора в различные дни
Сначала правятся легкие ошибки, потом
посложнее и самые сложные в конце
15/26
На что ушли 17 рабочих дней в UE:
расшифровка
• С нашей стороны работало 2 программиста.
• Код UE нам совершенно не знаком.
• Коммуникации с программистами заказчика затруднены
(Hello, USA TimeZone).
16/26
Старые ошибки поправлены!
Можно расслабиться?
17/26
Пример ошибки из кода Unreal Engine
V561 It's probably better to assign value to 'Existing' variable than to declare it anew. Previous
declaration: streamablemanager.cpp, line 325. streamablemanager.cpp 332
FStreamable* Existing = StreamableItems.FindRef(TargetName);
....
if (!Existing)
{
TargetName = ResolveRedirects(TargetName);
FStreamable* Existing = StreamableItems.FindRef(TargetName);
}
if (Existing && Existing->bAsyncLoadRequestOutstanding)
....
18/26
Типы найденных ошибок (не код)
• Настоящие ошибки.
• «Запахи».
• Правки, чтобы «угодить» анализатору кода.
19/26
«Пахнущий код»: ошибки нет, но
анализатор прав
if (InitializationState == Working)
{
//bool AllSuccessful = true;
//Insert other dependencies here
if (InitializationState == Working
/* && AllSuccessful */)
{
InitializationState = Success;
}
}
V571 Recurring check. This condition was already verified in line 357. questionblock.cpp 363
20/26
Примеры ошибок, которые
появились "на наших глазах"
21/26
Пример ошибки из кода Unreal Engine
virtual FString ToString() const override
{
if (Token.TokenType == FBasicToken::TOKEN_Identifier ||
FBasicToken::TOKEN_Guid)
{
....
}
V560 A part of conditional expression is always true: FBasicToken::TOKEN_Guid.
k2node_mathexpression.cpp 235
22/26
Пример ошибки из кода Unreal Engine
static void GetArrayOfSpeakers(....)
{
Speakers.Reset();
uint32 ChanCount = 0;
for (uint32 SpeakerTypeIndex = 0;
SpeakerTypeIndex < ESpeaker::SPEAKER_TYPE_COUNT,
ChanCount < NumChannels; ++SpeakerTypeIndex)
....
}
V521 Such expressions using the ',' operator are dangerous. Make sure the expression is
correct. unrealaudiodevicewasapi.cpp 128
,
23/26
Выводы по результатам проверки
Unreal Engine
• Код проекта Unreal Engine весьма качественный.
• Править чужой незнакомый код часто очень сложно. Свой –
намного легче.
• Скорость "переработки" предупреждений не линейна.
• Максимальная польза от статического анализа может быть
получена только при его регулярном использовании.
24/26
Пара слов о программистской культуре
в России и мире, или «Зачем вообще
знакомиться со статическим анализом?»
• Люди на западе применяют эту практику давно и успешно.
• Знание принципов и инструментов статического анализа кода
дает +10 на собеседовании разработчика и +20 при внедрении в
своем проекте. И должность Team Leader в придачу.
25/26
Q&A
• Написать письмо: evg@viva64.com
• Подписаться на твиттер: https://twitter.com/Code_Analysis
• Зайти на сайт: www.viva64.com
• Подойти на конференции и спросить что-то (в большинстве
случаем мы не кусаемся).
26/26

Как мы уменьшили количество ошибок в Unreal Engine с помощью статического анализа кода

  • 1.
    Как мы уменьшиликоличество ошибок в Unreal Engine с помощью статического анализа кода Евгений Рыжков evg@viva64.com ООО «СиПроВер» www.viva64.com 1/26
  • 2.
    Структура доклада • Этапывнедрения анализатора кода в живой проект. • Примеры ошибок из кода Unreal Engine. • Типы исправлений в коде. • Зачем вообще знакомиться со статическим анализом. 2/26
  • 3.
  • 4.
    Этап 0. Научитьинструмент проверять проект • Убедиться, что весь код компилируется • Убедиться, что весь код корректно проверяется анализатором 4/26
  • 5.
    Пример ошибки изкода Unreal Engine if (*Buffer == TCHAR('"')) { while (*Buffer && *Buffer != TCHAR('"') && *Buffer != TCHAR('n') && *Buffer != TCHAR('r')) { Buffer++; } V637 Two opposite conditions were encountered. The second condition is always false. Check lines: 310, 312. propertystruct.cpp 310 5/26
  • 6.
    Этап 1. Борьбас имеющимися сообщениями • Запустили анализатор на существующем проекте – получили 2000 сообщений. • Бросаться их сразу править – плохая идея. Менеджер проекта негодует. • Оставить как есть – не увидите новых сообщений. • Обязательно в инструменте должна быть возможность скрыть этим сообщения. Где-то нет такой возможности? Не тот инструмент. 6/26
  • 7.
    Пример ошибки изкода Unreal Engine V612 An unconditional 'return' within a loop. unrealaudiotestgenerators.cpp 49 static float WrapTwoPi(float Value) { while (Value >= TWO_PI) { return Value -= TWO_PI; } while (Value < 0) { return Value += TWO_PI; } return Value; } 7/26
  • 8.
    Этап 2. Настройкарегулярного запуска на билд- сервере (и на машинах разработчиков) • Абсолютно необходимая вещь – это настройка запуска на билд- сервере. • Надо давать всем разработчикам возможность ознакомиться с результатами проверки. • В идеале регулярный запуск нужен не только на билд-сервере по ночам, но и на машинах разработчиков во время работы. 8/26
  • 9.
    void FillUpTransformBasedOnRig(....) { .... const URig*Rig = Skeleton->GetRig(); int32 NodeNum = Rig->GetNodeNum(); if (Rig && NodeNum > 0) { .... } .... } Пример ошибки из кода Unreal Engine V595 The 'Rig' pointer was utilized before it was verified against nullptr. Check lines: 1844, 1846. animsequence.cpp 1844 9/26
  • 10.
    Этап 3. Правимновые ошибки • Убедить разработчиков править все новые ошибки, чтобы было 0 сообщений. • Здесь инструменты статического анализа могут предложить только плетку менеджеру проекта, к сожалению. 10/26
  • 11.
    Пример ошибки изкода Unreal Engine V579 The Memcmp function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. pimplrecastnavmesh.cpp 172 bool FRecastQueryFilter::IsEqual( const INavigationQueryFilterInterface* Other) const { return FMemory::Memcmp(this, Other, sizeof(this)) == 0; } 11/26
  • 12.
    Этап 4. Правимстарые ошибки • Очень желательно все-таки победить старые ошибки. • Поговорим про скорость правки ошибок. 12/26
  • 13.
    Ожидаемая скорость правки ошибок •Количество ошибок уменьшается равномерно с каждым рабочим днём. 13/26
  • 14.
    Фактическая скорость правки ошибок •На самом деле, сообщения исчезают в начале быстрее, чем потом. 14/26
  • 15.
    На что ушли17 рабочих дней в UE? Количество предупреждений анализатора в различные дни Сначала правятся легкие ошибки, потом посложнее и самые сложные в конце 15/26
  • 16.
    На что ушли17 рабочих дней в UE: расшифровка • С нашей стороны работало 2 программиста. • Код UE нам совершенно не знаком. • Коммуникации с программистами заказчика затруднены (Hello, USA TimeZone). 16/26
  • 17.
  • 18.
    Пример ошибки изкода Unreal Engine V561 It's probably better to assign value to 'Existing' variable than to declare it anew. Previous declaration: streamablemanager.cpp, line 325. streamablemanager.cpp 332 FStreamable* Existing = StreamableItems.FindRef(TargetName); .... if (!Existing) { TargetName = ResolveRedirects(TargetName); FStreamable* Existing = StreamableItems.FindRef(TargetName); } if (Existing && Existing->bAsyncLoadRequestOutstanding) .... 18/26
  • 19.
    Типы найденных ошибок(не код) • Настоящие ошибки. • «Запахи». • Правки, чтобы «угодить» анализатору кода. 19/26
  • 20.
    «Пахнущий код»: ошибкинет, но анализатор прав if (InitializationState == Working) { //bool AllSuccessful = true; //Insert other dependencies here if (InitializationState == Working /* && AllSuccessful */) { InitializationState = Success; } } V571 Recurring check. This condition was already verified in line 357. questionblock.cpp 363 20/26
  • 21.
  • 22.
    Пример ошибки изкода Unreal Engine virtual FString ToString() const override { if (Token.TokenType == FBasicToken::TOKEN_Identifier || FBasicToken::TOKEN_Guid) { .... } V560 A part of conditional expression is always true: FBasicToken::TOKEN_Guid. k2node_mathexpression.cpp 235 22/26
  • 23.
    Пример ошибки изкода Unreal Engine static void GetArrayOfSpeakers(....) { Speakers.Reset(); uint32 ChanCount = 0; for (uint32 SpeakerTypeIndex = 0; SpeakerTypeIndex < ESpeaker::SPEAKER_TYPE_COUNT, ChanCount < NumChannels; ++SpeakerTypeIndex) .... } V521 Such expressions using the ',' operator are dangerous. Make sure the expression is correct. unrealaudiodevicewasapi.cpp 128 , 23/26
  • 24.
    Выводы по результатампроверки Unreal Engine • Код проекта Unreal Engine весьма качественный. • Править чужой незнакомый код часто очень сложно. Свой – намного легче. • Скорость "переработки" предупреждений не линейна. • Максимальная польза от статического анализа может быть получена только при его регулярном использовании. 24/26
  • 25.
    Пара слов опрограммистской культуре в России и мире, или «Зачем вообще знакомиться со статическим анализом?» • Люди на западе применяют эту практику давно и успешно. • Знание принципов и инструментов статического анализа кода дает +10 на собеседовании разработчика и +20 при внедрении в своем проекте. И должность Team Leader в придачу. 25/26
  • 26.
    Q&A • Написать письмо:evg@viva64.com • Подписаться на твиттер: https://twitter.com/Code_Analysis • Зайти на сайт: www.viva64.com • Подойти на конференции и спросить что-то (в большинстве случаем мы не кусаемся). 26/26