﻿# Первая часть исследования Nau Engine

Этой статьёй мы начинаем трилогию об игровом движке Nau Engine\. В первой части мы сосредоточимся на его функциональности, уделяя особое внимание трём ключевым блокам ошибок: проблемам с памятью, копипасте и логическим ошибкам\.

![1221_NauEngineOne_ru/image1.png](https://import.viva64.com/docx/blog/1221_NauEngineOne_ru/image1.png)

## О Nau Engine

[Nau Engine](https://nauengine.org/) — это игровой движок, разработанный для упрощения процесса создания и поддержки игр\. Он основан на трёх принципах: универсальность, доступность и кроссплатформенность\.

1\. Универсальность: Nau Engine предлагает инструменты для всех этапов создания игры — от разработки до пострелизной поддержки\. Это включает системы для аналитики, обработки ошибок и обновления контента\.

2\. Доступность: движок нацелен на разработчиков с разным уровнем опыта\. Он предоставляет интуитивно понятные инструменты и шаблоны, что помогает новичкам легко начать, а более опытным пользователям — углубляться в детали\.

3\. Кроссплатформенность: Nau Engine позволяет адаптировать игры для различных платформ, таких как ПК, мобильные устройства и веб, что делает разработку более гибкой и удобной\.

Особое внимание стоит уделить адаптированной ECS \(Entity Component System\) библиотеке от движка Dagor, которая интегрирована в Nau Engine\. Эта библиотека не только обеспечивает высокую производительность, но и навевает приятные воспоминания о таких культовых играх, как:

![1221_NauEngineOne_ru/image2.png](https://import.viva64.com/docx/blog/1221_NauEngineOne_ru/image2.png)

В целом Nau Engine стремится помогать разработчикам на всех этапах создания игр, обеспечивая необходимыми инструментами и возможностями для реализации креативных идей\.

Следует отметить, что для анализа использовалось состояние репозитория на момент коммита [020cbfe](https://github.com/NauEngine/NauEnginePublic/tree/020cbfec7bd4cff7ff4303e0f31d75eebed584be)\.

## Результаты проверки

### Копипаста

**Фрагмент N1**

```cpp
NAU_ASSERT(irRequest.usage.access == req.usage.access 
        && irRequest.usage.type == irRequest.usage.type);
```

Предупреждение PVS\-Studio: [_V501_](https://pvs-studio.ru/ru/docs/warnings/v501/)_ There are identical sub\-expressions 'irRequest\.usage\.type' to the left and to the right of the '\=\=' operator\. [irGraphBuilder\.cpp 719](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/graphics/src/daBfg/frontend/irGraphBuilder.cpp#L719)_

В этом фрагменте нас интересует макрос `NAU_ASSERT`, который служит для проверки условий во время выполнения кода\. Если быть точнее, то интересно нам второе сравнение внутри этого макроса, а именно: `irRequest.usage.type == irRequest.usage.type`\. При отдельном написании сразу становится видно, что в левой и правой части сравнения используется одно и то же выражение, а следовательно, условие всегда будет истинным\.

Вероятно, автор кода хотел сравнить `irRequest.usage.type` с `req.usage.type`, чтобы проверить, совпадают ли типы в двух разных объектах:

```cpp
NAU_ASSERT(irRequest.usage.access == req.usage.access 
        && irRequest.usage.type   == req.usage.type);
```

**Фрагмент N2**

```cpp
bool VFXModFXInstance::deserialize(const nau::DataBlock* blk)
{
  ....
  m_life.part_life_min = blk->getReal("lifeMin", 5.0f);
  m_life.part_life_max = blk->getReal("lifeMin", 5.0f);
  ....
}
```

Предупреждение PVS\-Studio: [_V656_](https://pvs-studio.ru/ru/docs/warnings/v656/)_ Variables 'm\_life\.part\_life\_min', 'm\_life\.part\_life\_max' are initialized through the call to the same function\. It's probably an error or un\-optimized code\. Consider inspecting the 'blk\-\>getReal\("lifeMin", 5\.0f\)' expression\. Check lines: 94, 95\. [vfx\_mod\_fx\_instance\.cpp 95](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/vfx/src/vfx_mod_fx_instance.cpp#L95)_

В функции `VFXModFXInstance::deserialize` наблюдается проблема, связанная с инициализацией переменных `m_life.part_life_min` и `m_life.part_life_max`\. Из\-за копипасты обе переменные получают свои значения через вызов одной и той же функции с использованием одного и того же ключа `"lifeMin"`\. Очевидно, что для минимального и максимального времени жизни частиц должны использоваться разные значения\. Поэтому можем предположить, что во втором случае вместо `"lifeMin"` должен использоваться ключ `"lifeMax"`:

```cpp
m_life.part_life_min = blk->getReal("lifeMin", 5.0f);
m_life.part_life_max = blk->getReal("lifeMax", 5.0f);
```

**Фрагмент N3**

```cpp
Material* Material::clone() const
{
  auto material = new (std::nothrow) Material();
  if (material)
  {
    ....
    material->_textureSlots = material->_textureSlots;
    material->_textureSlotIndex = material->_textureSlotIndex;
    ....
  }
....
}
```

Предупреждения PVS\-Studio:

[_V570_](https://pvs-studio.ru/ru/docs/warnings/v570/)_ The 'material\-\>\_textureSlots' variable is assigned to itself\. [CCMaterial\.cpp 527](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/renderer/CCMaterial.cpp#L527)_

[_V570_](https://pvs-studio.ru/ru/docs/warnings/v570/)_ The 'material\-\>\_textureSlotIndex' variable is assigned to itself\. [CCMaterial\.cpp 528](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/renderer/CCMaterial.cpp#L528)_

Здесь происходит присваивание переменных `_textureSlots` и `_textureSlotIndex` самим себе, что не имеет смысла и может быть ошибкой\. Судя по названию функции\-члена класса \(`clone`\), нужно скопировать состояния объекта в новый\. Исходя из этого, исправление может быть таким:

```cpp
material->_textureSlots = _textureSlots;
material->_textureSlotIndex = _textureSlotIndex;
```

### Логические ошибки

**Фрагмент N4**

```cpp
template <typename T>
requires(std::is_enum_v<T>)
class TypedFlag
{
public:
  using EnumType = T;
  using ValueType = std::underlying_type_t<T>;
  ....
private:
  ValueType m_value = 0;
  ....
  friend TypedFlag<T> operator|(TypedFlag<T> value, TypedFlag<T> flags)
  {
  }
  ....
};
```

Предупреждение PVS\-Studio: [_V591_](https://pvs-studio.ru/ru/docs/warnings/v591/)_ Non\-void function should return a value\. [typed\_flag\.h 145](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/kernel/include/nau/utils/typed_flag.h#L145)_

В перегрузке оператора `|` для шаблона класса `TypedFlag` отсутствует возвращаемое значение, что может привести к ошибкам в логике работы программы\. Особенно интересна эта ситуация тем, что для [оператора `|=`](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/kernel/include/nau/utils/typed_flag.h#L151-L154) логика реализована:

```cpp
friend TypedFlag<T>& operator|=(TypedFlag<T>& value, T flag)
{
  return value.set(flag);
}
```

Есть несколько способов исправить эту проблему\.

**Способ N1**\. Реализовать функциональность операторов\. Они должны возвращать результат, который соответствует логике объединения флагов\. Например:

```cpp
return TypedFlag<T> { value }.set(flags);
```

**Способ N2**\. Удалить оператор\. Если функционал оператора `|` не нужен, его можно удалить, чтобы избежать путаницы или, например, можно объявить оператор как удалённый:

```cpp
friend TypedFlag<T> operator|(TypedFlag<T> value, TypedFlag<T> flags) = delete;
```

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

И вот ещё случаи:

* _V591 Non\-void function should return a value\. [typed\_flag\.h 149](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/kernel/include/nau/utils/typed_flag.h#L149)_
* _V591 Non\-void function should return a value\. [typed\_flag\.h 163](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/kernel/include/nau/utils/typed_flag.h#L163)_
* _V591 Non\-void function should return a value\. [string\.h 511](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/kernel/include/nau/string/string.h#L511)_

**Фрагмент N5**

```cpp
EntityId EntityManager::createEntitySync(....)
{
  ....
  if (EASTL_UNLIKELY(result != RequestResources::Loaded))
  {
#if NAU_DEBUG
    if (result == RequestResources::Loaded)
    {
      ....
    }
#endif
    if (result == RequestResources::Error)
    {
      ....
    }
  }
  ....
}
```

Предупреждение PVS\-Studio: [_V637_](https://pvs-studio.ru/ru/docs/warnings/v637/)_ Two opposite conditions were encountered\. The second condition is always false\. Check lines: 711, 714\. [entityManager2\.cpp 711](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/dagorECS/src/core/entityManager2.cpp#L711)_

В коде присутствует логическая ошибка, которая может ввести в заблуждение\. Рассмотрим детали:

* Условие `if (EASTL_UNLIKELY(result != RequestResources::Loaded))`: оно проверяет, что результат не равен `RequestResources::Loaded`\. Если это условие истинно, значит, ресурсы не загружены\.
* Блок `#if NAU_DEBUG`: внутри него есть проверка `if (result == RequestResources::Loaded)`, которая никогда не будет истинной, так как мы уже проверили, что `result` не равен `RequestResources::Loaded`\. Это создаёт противоречие и может ввести в заблуждение\.

**Фрагмент N6**

```cpp
IGenSave* create_async_writer(....)
{
  AsyncWriterCB* ret = new AsyncWriterCB(buf_size);
  if (!ret->open(fname, mode))
  {
    if (ret)
    {
      delete ret;
      ret = nullptr;
    }
  }
  return ret;
}
```

Предупреждение PVS\-Studio: [_V668_](https://pvs-studio.ru/ru/docs/warnings/v668/)_ There is no sense in testing the 'ret' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [asyncWrite\.cpp 363](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/kernel/src/dag_ioSys/asyncWrite.cpp#L363)_

В этом коде происходит проверка указателя `ret` на `nullptr` после вызова `new`\. Это может быть источником ошибок, поскольку указатель `ret` всегда будет указывать на выделенную память, если выделение прошло успешно\. В случае если память не удастся выделить, будет выброшено исключение, и код ниже не будет выполнен\.

Кроме того, указатель `ret` разыменовывается до проверки, что делает саму проверку `if (ret)` ещё более бессмысленной\. Если бы `ret` действительно был равен `nullptr`, программа бы уже аварийно завершилась при попытке разыменования\.

И вот ещё случаи:

* _V668 There is no sense in testing the 'ret' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [asyncWrite\.cpp 377](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/kernel/src/dag_ioSys/asyncWrite.cpp#L377)_
* _V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCFontCharMap\.cpp 58](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/2d/CCFontCharMap.cpp#L58)_
* _V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCFontCharMap\.cpp 77](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/2d/CCFontCharMap.cpp#L77)_
* _V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCFontCharMap\.cpp 89](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/2d/CCFontCharMap.cpp#L89)_
* _V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCFontFNT\.cpp 523](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/2d/CCFontFNT.cpp#L523)_
* _V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCFontFNT\.cpp 545](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/2d/CCFontFNT.cpp#L545)_
* _V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCFontFNT\.cpp 569](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/2d/CCFontFNT.cpp#L569)_
* _V668 There is no sense in testing the 'layerGradient' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCLayer\.cpp 579](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/2d/CCLayer.cpp#L579)_
* _V668 There is no sense in testing the 'layerGradient' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCLayer\.cpp 592](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/2d/CCLayer.cpp#L592)_
* _V668 There is no sense in testing the 'pwszBuffer' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCDevice\-win32\.cpp 90](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/platform/win32/CCDevice-win32.cpp#L90)_
* _V668 There is no sense in testing the 'pwszBuffer' pointer against null, as the memory was allocated using the 'new' operator\. The exception will be generated in the case of memory allocation error\. [CCDevice\-win32\.cpp 326](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/platform/win32/CCDevice-win32.cpp#L326)_

**Фрагмент N7**

```cpp
void Properties::skipWhiteSpace()
{
  signed char c;
  do
  {
    c = readChar();
  } while (isspace(c) && c != EOF);
  ....
  if (c != EOF)
  {
    ....
  }
}
```

Предупреждения PVS\-Studio:

[_V739_](https://pvs-studio.ru/ru/docs/warnings/v739/)_ EOF should not be compared with a value of the 'char' type\. The 'c' should be of the 'int' type\. [CCProperties\.cpp 464](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/base/CCProperties.cpp#L464)_

[_V739_](https://pvs-studio.ru/ru/docs/warnings/v739/)_ EOF should not be compared with a value of the 'char' type\. The 'c' should be of the 'int' type\. [CCProperties\.cpp 468](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/base/CCProperties.cpp#L468)_

Функция\-член `Properties::skipWhiteSpace` использует переменную типа `signed char` для хранения символа, считанного с помощью функции `Properties::readChar`\. Последняя, [судя по комментарию](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/base/CCProperties.cpp#L411-L419), симулирует поведение [`std::getchar`](https://en.cppreference.com/w/cpp/io/c/getchar):

```cpp
//
// Stream simulation
//
signed char Properties::readChar()
{
  if (eof())
    return EOF;
  return _data->_bytes[(*_dataIdx)++];
}
```

Код содержит две ошибки:

**Ошибка N1**\. Функция `std::getchar` неспроста возвращает значения типа `int`\. Функция возвращает символ \(1 байт\) или `EOF` в результате ошибки\. `EOF` — константа, имеющая негативное значение, обычно `-1`\. Поэтому перед тем как преобразовывать результат `std::getchar` к символу, нужно отсечь вариант с `EOF`:

```cpp
if (int res = std::getchar(); res != EOF)
{
  char ch = res;
  // your logic with character
}
```

Что может произойти, если не обработать такую ситуацию? Пользователи, использующие [Extended ASCII Codes](https://en.wikipedia.org/wiki/Extended_ASCII), иногда сталкиваются с ошибкой, когда один из символов их алфавита некорректно обрабатывается программами\. Например, последняя буква русского алфавита в кодировке Windows\-1251 как раз имеет код `0xFF` и воспринимается некоторыми программами как конец файла\.

Корректная имплементация функции `Properties::readChar` должна выглядеть так:

```cpp
//
// Stream simulation
//
int Properties::readChar()
{
  if (eof())
    return EOF;
  return _data->_bytes[(*_dataIdx)++];
}
```

**Ошибка N2**\. Функция `Properties::skipWhiteSpace` после исправления `Properties::readChar` также должна работать с типом `int` до тех пор, пока не отсечёт вариант с `EOF`:

```cpp
void Properties::skipWhiteSpace()
{
  int c;
  do
  {
    c = readChar();
  }
  while (c != EOF && isspace(c));
  ....
  if (c != EOF)
  {
    // Now we can cast 'c' to 'signed char'
    signed char ch = c;
    ....
  }
}
```

**Фрагмент N8**

```cpp
void Sweep::EdgeEvent(....)
{
  ....
  if (o1 == COLLINEAR) {
    if( triangle->Contains(&eq, p1)) {
    ....
    } else {
      std::runtime_error("EdgeEvent - collinear points not supported");
      assert(0);
    }
    return;
  }
  ....
  if (o2 == COLLINEAR) {
    if (triangle->Contains(&eq, p2)){
      ....
    } else {
      std::runtime_error("EdgeEvent - collinear points not supported");
      assert(0);
    }
    return;
  }
  ....
}
```

Предупреждения PVS\-Studio:

[_V596_](https://pvs-studio.ru/ru/docs/warnings/v596/)_ The object was created but it is not being used\. The 'throw' keyword could be missing: throw runtime\_error\(FOO\); [sweep\.cc 123](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/external/poly2tri/sweep/sweep.cc#L123)_

[_V596_](https://pvs-studio.ru/ru/docs/warnings/v596/)_ The object was created but it is not being used\. The 'throw' keyword could be missing: throw runtime\_error\(FOO\); [sweep\.cc 140](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/external/poly2tri/sweep/sweep.cc#L140)_

В функции\-члене `Sweep::EdgeEvent` создаются объекты исключений `std::runtime_error` при обнаружении коллинеарных точек, но они не выбрасываются\. Это приводит к тому, что программа может продолжать выполнение, игнорируя возникшую ошибку, что может вызвать некорректное поведение\.

Исправленный код:

```cpp
throw std::runtime_error("EdgeEvent - collinear points not supported");
```

**Фрагмент N9**

```cpp
std::size_t UniformLocation::operator()(const UniformLocation &uniform) const
{
    return (((size_t) shaderStage) & 0xF)
           |((size_t)(location[0] << 4))
           |((size_t)(location[1] << 8));
}
```

Предупреждения PVS\-Studio:

[_V1028_](https://pvs-studio.ru/ru/docs/warnings/v1028/)_ Possible overflow\. Consider casting operands of the 'location\[0\] << 4' operator to the 'size\_t' type, not the result\. [Types\.cpp 40](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/renderer/backend/Types.cpp#L40)_

[_V1028_](https://pvs-studio.ru/ru/docs/warnings/v1028/)_ Possible overflow\. Consider casting operands of the 'location\[1\] << 8' operator to the 'size\_t' type, not the result\. [Types\.cpp 40](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/renderer/backend/Types.cpp#L40)_

Оператор `UniformLocation::operator()` выполняет битовые операции над значениями, полученными из переменных `shaderStage` и `location`, чтобы вернуть уникальный идентификатор\.

Проблема заключается в том, что операции сдвига \(`<<`\) могут привести к переполнению, если значения `location[0]` или `location[1]` слишком велики\. Следует привести `location[0]` и `location[1]` к типу `size_t` перед выполнением операции сдвига:

```cpp
std::size_t UniformLocation::operator()(const UniformLocation &uniform) const
{
    return (((size_t) shaderStage) & 0xF)
           |((size_t)(location[0]) << 4)
           |((size_t)(location[1]) << 8);
}
```

### Проблемы с памятью

**Фрагмент N10**

```cpp
char** m_memBlock = nullptr;

template<class Type>
class ThreadLocalValue final
{
  ....
private:
  void resizeLines(size_t sizeReq)
  {
    ....
    auto newSize = sizeReq + 1;
    ....
    m_memBlock = static_cast<char**>(realloc(m_memBlock, 
                                             sizeof(char*) * newSize));
    ....
  }
  ....
};
```

Предупреждение PVS\-Studio: [_V701_](https://pvs-studio.ru/ru/docs/warnings/v701/)_ realloc\(\) possible leak: when realloc\(\) fails in allocating memory, original pointer 'm\_memBlock' is lost\. Consider assigning realloc\(\) to a temporary pointer\. [thread\_local\_value\.h 235](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/kernel/include/nau/threading/thread_local_value.h#L235)_

В данном коде `m_memBlock` представляет собой массив указателей на `char`, а метод `resizeLines` изменяет его размер с помощью `realloc`\. 

Если `realloc` успешно выделяет память, он может вернуть либо тот же указатель, либо новый, переместив данные\. Однако, если выделение памяти не удаётся, `realloc` возвращает `nullptr`, а прежнее значение `m_memBlock` теряется\.

Чтобы избежать утечки, необходимо сначала присвоить результат `realloc` временной переменной, проверить его на `nullptr` и только затем обновлять `m_memBlock`\.

И вот ещё случаи:

* _V701 realloc\(\) possible leak: when realloc\(\) fails in allocating memory, original pointer '\_buffer' is lost\. Consider assigning realloc\(\) to a temporary pointer\. [CCDrawNode\.cpp 102](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/src/elements/draw_node.cpp#L102)_
* _V701 realloc\(\) possible leak: when realloc\(\) fails in allocating memory, original pointer '\_bufferGLPoint' is lost\. Consider assigning realloc\(\) to a temporary pointer\. [CCDrawNode\.cpp 116](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/src/elements/draw_node.cpp#L116)_
* _V701 realloc\(\) possible leak: when realloc\(\) fails in allocating memory, original pointer '\_bufferGLLine' is lost\. Consider assigning realloc\(\) to a temporary pointer\. [CCDrawNode\.cpp 130](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/src/elements/draw_node.cpp#L130)_
* _V701 realloc\(\) possible leak: when realloc\(\) fails in allocating memory, original pointer '\_triBatchesToDraw' is lost\. Consider assigning realloc\(\) to a temporary pointer\. [CCRenderer\.cpp 629](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/renderer/CCRenderer.cpp#L629)_
* _V701 realloc\(\) possible leak: when realloc\(\) fails in allocating memory, original pointer '\* out' is lost\. Consider assigning realloc\(\) to a temporary pointer\. [ZipUtils\.cpp 185](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/base/ZipUtils.cpp#L185)_
* _V701 realloc\(\) possible leak: when realloc\(\) fails in allocating memory, original pointer 'arr\-\>arr' is lost\. Consider assigning realloc\(\) to a temporary pointer\. [ccCArray\.cpp 100](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/base/ccCArray.cpp#L100)_

**Фрагмент N11**

```cpp
IAssetContainerLoader* loader = nullptr;
const auto importSettingsProviders = ....;
RuntimeReadonlyDictionary::Ptr importSettings;
for (const auto& importSettingsProvider : importSettingsProviders)
{
  if (importSettings = 
      importSettingsProvider->getAssetImportSettings(containerPath, 
                                                     *loader); 
      importSettings)
  {
    break;
  }
}
```

Предупреждение PVS\-Studio: [_V522_](https://pvs-studio.ru/ru/docs/warnings/v522/)_ Dereferencing of the null pointer 'loader' might take place\. [asset\_file\_content\_provider\.cpp 34](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/assets/src/asset_file_content_provider.cpp#L34)_

В начале кода указатель `loader` инициализируется значением `nullptr`\. Затем в цикле вызывается функция\-член `getAssetImportSettings`, при этом аргументом передаётся разыменованный указатель `loader`\. Странно, что между этими строками он никаким образом не модифицируется\. Получаем гарантированное неопределённое поведение\.

И вот ещё случай:

* _V522 Dereferencing of the null pointer 'object' might take place\. The null pointer is passed into 'replace' function\. Inspect the second argument\. Check lines: 'CCVector\.h:481', 'CCLayer\.cpp:976'\. [CCVector\.h 481](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/ui/cocos2d-x/cocos/base/CCVector.h#L481)_

**Фрагмент N12**

```cpp
struct DelayedEntityCreationChunk
{
  ....
  DelayedEntityCreationChunk(uint16_t cap) : capacity(cap)
  {
    queue = (DelayedEntityCreation*)
      malloc(capacity * sizeof(DelayedEntityCreation));
  }
  ....
};
```

Предупреждение PVS\-Studio: [_V630_](https://pvs-studio.ru/ru/docs/warnings/v630/)_ The 'malloc' function is used to allocate memory for an array of objects which are classes containing constructors and destructors\. [entityManager\.h 1391](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/dagorECS/include/daECS/core/entityManager.h#L1391)_

В этом фрагменте анализатор ругается на использование `malloc` для выделения памяти под массив объектов классов с конструкторами и деструкторами\. В общем случае так делать не стоит, лучше применять другие способы создания объектов на куче\. Однако в данной ситуации это ложное срабатывание, так как создание объектов происходит позже через метод [`emplace_back`](https://github.com/NauEngine/NauEnginePublic/blob/020cbfec7bd4cff7ff4303e0f31d75eebed584be/engine/core/modules/dagorECS/include/daECS/core/entityManager.h#L1440-L1446) \(примерно то же самое происходит и в векторе\)\.

Тут у читателя может возникнуть вопрос: "А зачем тогда вообще включать этот фрагмент в статью?"\. Я собирался убрать его, но тут моё внимание привлекло ещё кое\-что в этом классе:

```cpp
struct DelayedEntityCreationChunk
{
  ....
  DelayedEntityCreation* queue = nullptr;
  uint16_t readFrom = 0, writeTo = 0, capacity;

  DelayedEntityCreationChunk(uint16_t cap) :
    capacity(cap)
  {
    queue = (DelayedEntityCreation*)
         malloc(capacity * sizeof(DelayedEntityCreation));
  }

  ~DelayedEntityCreationChunk()
  {
    for(auto i = begin(), e = end(); i != e; ++i)
      i->~DelayedEntityCreation();
    free(queue);
  }

  DelayedEntityCreationChunk(const DelayedEntityCreationChunk&) = delete;
  DelayedEntityCreationChunk&
    operator=(const DelayedEntityCreationChunk&) = delete;

  DelayedEntityCreationChunk(DelayedEntityCreationChunk&& a)
  {
    memcpy(this, &a, sizeof(DelayedEntityCreationChunk));
    memset(&a, 0, sizeof(DelayedEntityCreationChunk));
  }

  DelayedEntityCreationChunk& operator=(DelayedEntityCreationChunk&& a)
  {
    alignas(DelayedEntityCreationChunk) 
      char buf[sizeof(DelayedEntityCreationChunk)];

    memcpy(buf, this, sizeof(DelayedEntityCreationChunk));
    memcpy(this, &a, sizeof(DelayedEntityCreationChunk));
    memcpy(&a, buf, sizeof(DelayedEntityCreationChunk));
    return *this;
  }

  ....

  template <typename... Args>
  bool emplace_back(EntityId eid, Args &&...args)
  {
    DAECS_EXT_ASSERT(!full());
    new (queue + (writeTo++)) DelayedEntityCreation(eid,
                                             eastl::forward<Args>(args)...);
    return full();
  }
  ....
};
```

Какими свойствами обладает этот класс:

* [Нетривиально разрушаемый](https://en.cppreference.com/w/cpp/language/destructor#Trivial_destructor), т\.к\. определён пользовательский деструктор\.
* [Нетривиально перемещаемый](https://en.cppreference.com/w/cpp/language/move_constructor#Trivial_move_constructor), т\.к\. определены пользовательские конструктор и оператор перемещения\. При этом операции перемещения не помечены как `noexcept`, что в некоторых ситуациях [не есть хорошо](https://en.cppreference.com/w/cpp/utility/move_if_noexcept)\.
* [Move\-only](https://en.cppreference.com/w/cpp/language/copy_constructor#Deleted_copy_constructor), т\.к\. удалены конструктор и оператор копирования\.

При этом можно заметить, что при работе с объектами этого класса в конструкторе и операторе перемещения активно используются `memcpy` и `memset`\. Стандарт чётко регламентирует их поведение только для [тривиально копируемых](https://en.cppreference.com/w/cpp/language/classes#Trivially_copyable_class) типов, в ином случае оно [может быть не определено](https://stackoverflow.com/questions/29777492)\. 

Рекомендую переделать код на примерно следующий:

```cpp
struct DelayedEntityCreationChunk
{
  ....
  DelayedEntityCreation* queue = nullptr;
  uint16_t readFrom = 0, writeTo = 0, capacity;
  ....
public:
  DelayedEntityCreationChunk(DelayedEntityCreationChunk &&a) noexcept
    : queue { std::exchange(a.queue, {}) }
    , readFrom { std::exchange(a.readFrom, {}) }
    , writeTo { std::exchange(a.writeTo, {}) }
    , capacity { std::exchange(a.capacity, {}) }
  {
  }

  void swap(DelayedEntityCreationChunk &other) noexcept
  {
    auto lhs = std::tie(queue, readFrom, writeTo, capacity);
    auto rhs = std::tie(other.queue, other.readFrom,
                        other.writeTo, other.capacity);

    std::swap(lhs, rhs);
  }

  DelayedEntityCreationChunk&
    operator=(DelayedEntityCreationChunk &&a) noexcept
  {
    if (this == std::addressof(a)) return *this;

    auto tmp = std::move(a);
    swap(tmp);
    return *this;
  }
  ....
};
```

## Заключение  

В первой части нашего исследования игрового движка Nau Engine мы погрузились в его функциональность, уделив внимание проблемам с памятью, копипасте и логическим ошибкам\. Эти проблемы могут стать настоящими камнями преткновения для разработчиков, стремящихся создавать увлекательные игры\. Хотя Nau Engine предлагает множество заманчивых возможностей, обнаруженные недостатки могут существенно повлиять на производительность и общее впечатление от игр\.

В следующей части нашей трилогии мы будем искать пути оптимизации и улучшения работы движка\.

Благодарю за внимание\!