﻿# Серьёзные ошибки в коде CryEngine V

В мае 2016 года немецкая компания Crytek решила опубликовать на GitHub исходный код игрового движка CryEngine V\. Проект находится в стадии активной разработки, что влечёт за собой появление множества ошибок в коде\. Мы уже проверяли проект с помощью PVS\-Studio для Windows, а теперь смогли проверить проект с помощью PVS\-Studio для Linux\. Материала снова набралось на большую статью с описанием только очень серьёзных ошибок\.

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

## Введение

[CryEngine](https://www.cryengine.com/) — игровой движок, созданный немецкой компанией [Crytek](https://www.crytek.com/) в 2002 году, и первоначально используемый в шутере от первого лица Far Cry\. На CryEngine разных версий сделано много отличных игр от нескольких игровых студий, которые лицензировали движок: Far Cry, Crysis, Entropia Universe, Blue Mars, Warface, Homefront: The Revolution, Sniper: Ghost Warrior, Armored Warfare, Evolve и многие другие\. В марте 2016 года компания Crytek анонсировала выход своего нового движка CryEngine V и вскоре опубликовала исходный код на GitHub\.

Для проверки открытого исходного кода использовался статический анализатор [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) версии 6\.14 для Linux\. Теперь разработчикам кроссплатформенных проектов стало ещё удобнее следить за качеством кода с помощью одного инструмента анализа кода\. Скачать версию для Linux можно в виде архива или пакета для пакетного менеджера\. Для большинства дистрибутивов можно настроить [установку и обновление](https://pvs-studio.ru/ru/docs/manual/0039/), используя наш репозиторий\.

В статью вошли предупреждения общего назначения и только уровня достоверности "High" \(есть ещё Medium и Low\)\. Честно говоря, я не осилил досмотреть внимательно и все "High" предупреждения, т\.к\. почти сразу насобирал ошибок для статьи при быстром просмотре\. За проект я брался несколько раз и долго не мог найти время написать обзорную статью, поэтому про приведённые баги могу сказать, что они живут в коде уже не один месяц\. Также некоторые ошибки не исправили из [предыдущей](https://pvs-studio.ru/ru/blog/posts/cpp/0417/) статьи о проверке проекта\.

Скачать и проверить исходный код в Linux было очень просто\. Вот список всех необходимых команд:

```cpp
mkdir ~/projects && cd ~/projects
git clone https://github.com/CRYTEK/CRYENGINE.git
cd CRYENGINE/
git checkout main
chmod +x ./download_sdks.py
./download_sdks.py
pvs-studio-analyzer trace -- \
  sh ./cry_waf.sh build_linux_x64_clang_profile -p gamesdk
pvs-studio-analyzer analyze \
  -l /path/to/PVS-Studio.lic \
  -o ~/projects/CRYENGINE/cryengine.log \
  -r ~/projects/CRYENGINE/ \
  -C clang++-3.8 -C clang-3.8 \
  -e ~/projects/CRYENGINE/Code/SDKs \
  -j4

plog-converter -a GA:1,2 -t tasklist \
  -o ~/projects/CRYENGINE/cryengine_ga.tasks \
  ~/projects/CRYENGINE/cryengine.log
```

Файл отчёта _cryengine\_ga\.tasks_ можно открыть и просмотреть в QtCreator\. Что же удалось найти в исходном коде CryEngine V?

## Несчастная функция Active\(\)

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions to the left and to the right of the '\=\=' operator: bActive \=\= bActive LightEntity\.h 124

```cpp
void SetActive(bool bActive)
{
  if (bActive == bActive)
    return;

  m_bActive = bActive;
  OnResetState();
}
```

Из\-за опечатки функция ничего не делает\. Мне кажется, если бы был конкурс "Мисс Опечатка", то этот фрагмент кода точно бы занял первое место\. Думаю, эта ошибка имеет все шансы попасть в раздел "[C/C\+\+ bugs of the month](https://hownot2code.com/category/cc-bugs-of-the-month/)"\.

Но это ещё не всё, вот функция из другого класса:

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions 'm\_staticObjects' to the left and to the right of the '\|\|' operator\. FeatureCollision\.h 66

```cpp
class CFeatureCollision : public CParticleFeature
{
public:
  CRY_PFX2_DECLARE_FEATURE

public:
  CFeatureCollision();
  ....

  bool  IsActive() const  { return m_terrain ||
                                   m_staticObjects ||
                                   m_staticObjects; }
  ....
  bool m_terrain;
  bool m_staticObjects;
  bool m_dynamicObjects;
};
```

Тут в функции _IsActive\(\)_ два раза используется переменная _m\_staticObjects_, хотя рядом есть неиспользуемая переменная _m\_dynamicObjects_\. Возможно, в коде хотели использовать именно её\.

## Code above has no bugs

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'outArrIndices\[i\] < 0' is always false\. Unsigned type value is never < 0\. CGFLoader\.cpp 881

```cpp
static bool CompactBoneVertices(....,
  DynArray<uint16>& outArrIndices, ....)           // <= uint16
{
  ....
  outArrIndices.resize(3 * inFaceCount, -1);

  int outVertexCount = 0;
  for (int i = 0; i < verts.size(); ++i)
  {
    ....
    outArrIndices[....] = outVertexCount - 1;
  }

  // Making sure that the code above has no bugs   // <= LOL
  for (int i = 0; i < outArrIndices.size(); ++i)
  {
    if (outArrIndices[i] < 0)                      // <= LOL
    {
      return false;
    }
  }
  
  return true;
}
```

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

Так вот, есть массив беззнаковых чисел \- _outArrIndices_\. Далее массив заполняется по некому алгоритму\. После чего выполняется гениальная проверка каждого элемента массива, чтобы среди них не было ни одного отрицательного числа\. Элементы массива имеют тип _uint16_\.

## Ошибки при работе с памятью

[V512](https://pvs-studio.ru/ru/docs/warnings/v512/) A call of the 'memcpy' function will lead to underflow of the buffer 'hashableData'\. GeomCacheRenderNode\.cpp 285

```cpp
void CGeomCacheRenderNode::Render(....)
{
  ....
  CREGeomCache* pCREGeomCache = iter->second.m_pRenderElement;
  ....
  uint8 hashableData[] =
  {
    0, 0, 0, 0, 0, 0, 0, 0,
    (uint8)std::distance(pCREGeomCache->....->begin(), &meshData),
    (uint8)std::distance(meshData....->....begin(), &chunk),
    (uint8)std::distance(meshData.m_instances.begin(), &instance)
  };

  memcpy(hashableData, pCREGeomCache, sizeof(pCREGeomCache));
  ....
}
```

Обратите внимание на аргументы функции _memcpy\(\)_\. Объект _pCREGeomCache_ планируют скопировать в массив _hashableData_, но случайно вычислили с помощью оператора _sizeof_ не размер объекта, а размер указателя\. Из\-за ошибки объект копируется не полностью, а всего 4 или 8 байт\.

[V568](https://pvs-studio.ru/ru/docs/warnings/v568/) It's odd that 'sizeof\(\)' operator evaluates the size of a pointer to a class, but not the size of the 'this' class object\. ClipVolumeManager\.cpp 145

```cpp
void
CClipVolumeManager::GetMemoryUsage(class ICrySizer* pSizer) const
{
  pSizer->AddObject(this, sizeof(this));
  for (size_t i = 0; i < m_ClipVolumes.size(); ++i)
    pSizer->AddObject(m_ClipVolumes[i].m_pVolume);
}
```

Похожую ошибку допустили, вычислив вместо размера класса, размер указателя _this_\. Правильный вариант: _sizeof\(\*this\)_\.

[V530](https://pvs-studio.ru/ru/docs/warnings/v530/) The return value of function 'release' is required to be utilized\. ClipVolumes\.cpp 492

```cpp
vector<unique_ptr<CFullscreenPass>> m_jitteredDepthPassArray;

void CClipVolumesStage::PrepareVolumetricFog()
{
  ....
  for (int32 i = 0; i < m_jitteredDepthPassArray.size(); ++i)
  {
    m_jitteredDepthPassArray[i].release();
  }

  m_jitteredDepthPassArray.resize(depth);

  for (int32 i = 0; i < depth; ++i)
  {
    m_jitteredDepthPassArray[i] = CryMakeUnique<....>();
    m_jitteredDepthPassArray[i]->SetViewport(viewport);
    m_jitteredDepthPassArray[i]->SetFlags(....);
  }
  ....
}
```

Если обратиться к документации по классу _std::unique\_ptr_, то функция _release\(\)_ должна использоваться примерно следующим образом:

```cpp
std::unique_ptr<Foo> up(new Foo());
Foo* fp = up.release();
delete fp;
```

Скорее всего, в данном фрагменте кода хотели использовать функцию _reset\(\)_ вместо _release\(\)_\.

[V549](https://pvs-studio.ru/ru/docs/warnings/v549/) The first argument of 'memcpy' function is equal to the second argument\. ObjectsTree\_Serialize\.cpp 1135

```cpp
void COctreeNode::LoadSingleObject(....)
{
  ....
  float* pAuxDataDst = pObj->GetAuxSerializationDataPtr(....);
  const float* pAuxDataSrc = StepData<float>(....);
  memcpy(pAuxDataDst, pAuxDataDst, min(....) * sizeof(float));
  ....
}
```

Забыли передать _pAuxDataSrc_ в функцию _memcpy\(\)_\. Вместо этого в качестве источника и приёмника используют одну и ту же переменную _pAuxDataDst_\. Никто не застрахован от опечаток\. 

## Странный код

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions to the left and to the right of the '\|\|' operator: val \=\= 0 \|\| val \=\= \- 0 XMLCPB\_AttrWriter\.cpp 363

```cpp
void CAttrWriter::PackFloatInSemiConstType(float val, ....)
{
  uint32 type = PFSC_VAL;

  if (val == 0 || val == -0)  // <=
    type = PFSC_0;
  else if (val == 1)
    type = PFSC_1;
  else if (val == -1)
    type = PFSC_N1;

  ....
}
```

Разработчики планировали сравнить вещественную переменную _val_ с нулём и отрицательным нулём \(signed zero / negative zero\), но сделали это неправильно\. Объявив целочисленные константы, значения нулей стали одинаковыми\.

Скорее всего, код стоит исправить следующим образом, объявив константы вещественного типа:

```cpp
if (val == 0.0f || val == -0.0f)
    type = PFSC_0;
```

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

Но если необходимо идентифицировать именно отрицательный ноль, то правильно это сделать с помощью функции [std::signbit](https://en.cppreference.com/w/cpp/numeric/math/signbit)\.

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions 'm\_joints\[i\]\.limits\[1\]\[j\]' to the left and to the right of the '\-' operator\. articulatedentity\.cpp 1326

```cpp
int CArticulatedEntity::Step(float time_interval)
{
  ....
  for (j=0;j<3;j++) if (!(m_joints[i].flags & angle0_locked<<j)&&
    isneg(m_joints[i].limits[0][j]-m_joints[i].qext[j]) +
    isneg(m_joints[i].qext[j]-m_joints[i].limits[1][j]) + 
    isneg(m_joints[i].limits[1][j]-m_joints[i].limits[1][j]) < 2)
  {
    ....
}
```

В последней части условного выражения присутствует вычитание из переменной _m\_joints\[i\]\.limits\[1\]\[j\]_ самой себя\. Код выглядит подозрительным\. В выражении много индексов, возможно, в одном из них ошибка\.

Ещё одно такое место:

* V501 There are identical sub\-expressions 'm\_joints\[op\[1\]\]\.limits\[1\]\[i\]' to the left and to the right of the '\-' operator\. articulatedentity\.cpp 513

[V590](https://pvs-studio.ru/ru/docs/warnings/v590/) Consider inspecting this expression\. The expression is excessive or contains a misprint\. GoalOp\_Crysis2\.cpp 3779

```cpp
void COPCrysis2FlightFireWeapons::ParseParam(....)
{
  ....
  bool paused;
  value.GetValue(paused);

  if (paused && (m_State != eFP_PAUSED) &&
                (m_State != eFP_PAUSED_OVERRIDE))
  {
    m_NextState = m_State;
    m_State = eFP_PAUSED;
    m_PausedTime = 0.0f;
    m_PauseOverrideTime = 0.0f;
  }
  else if (!paused && (m_State == eFP_PAUSED) &&        // <=
                      (m_State != eFP_PAUSED_OVERRIDE)) // <=
  {
    m_State = m_NextState;
    m_NextState = eFP_STOP;

    m_PausedTime = 0.0f;
    m_PauseOverrideTime = 0.0f;
  }
  ....
}
```

Условное выражение написано таким образом, что результат не зависит от подвыражения _m\_State \!\= eFP\_PAUSED\_OVERRIDE_\. Хотя кому я рассказываю, этот фрагмент кода разработчики так и не исправили после первой статьи\.

Если кому интересно, то подобную разновидность ошибок я уже описывал ранее в отдельной статье "[Логические выражения в C/C\+\+\. Как ошибаются профессионалы](https://pvs-studio.ru/ru/blog/posts/cpp/0390/)"\.

[V529](https://pvs-studio.ru/ru/docs/warnings/v529/) Odd semicolon ';' after 'for' operator\. boolean3d\.cpp 1077

```cpp
int CTriMesh::Slice(...)
{
  ....
  pmd->pMesh[0]=pmd->pMesh[1] = this;  AddRef();AddRef();
  for(pmd0=m_pMeshUpdate; pmd0->next; pmd0=pmd0->next); // <=
    pmd0->next = pmd;
  ....
}
```

Ещё один фрагмент кода, неисправленный с момента предыдущей проверки\. По коду по\-прежнему не ясно: тут ошибочное форматирование или логическая ошибка?

## Про указатели

[V522](https://pvs-studio.ru/ru/docs/warnings/v522/) Dereferencing of the null pointer 'pCEntity' might take place\. BreakableManager\.cpp 2396

```cpp
int CBreakableManager::HandlePhysics_UpdateMeshEvent(....)
{
  CEntity* pCEntity = 0;
  ....
  if (pmu && pSrcStatObj && GetSurfaceType(pSrcStatObj))
  {
    ....
    if (pEffect)
    {
      ....
      if (normal.len2() > 0)
        pEffect->Spawn(true, pCEntity->GetSlotWorldTM(...); // <=
    }
  }

  ....

  if (iForeignData == PHYS_FOREIGN_ID_ENTITY)
  {
    pCEntity = (CEntity*)pForeignData;
    if (!pCEntity || !pCEntity->GetPhysicalProxy())
      return 1;
  }
  ....
}
```

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

Теперь рассмотрим вариант потенциального разыменования нулевого указателя\.

[V595](https://pvs-studio.ru/ru/docs/warnings/v595/) The 'pTrack' pointer was utilized before it was verified against nullptr\. Check lines: 60, 61\. AudioNode\.cpp 60

```cpp
void CAudioNode::Animate(SAnimContext& animContext)
{
  ....
  const bool bMuted = gEnv->IsEditor() && (pTrack->GetFlags() &
    IAnimTrack::eAnimTrackFlags_Muted);
  if (!pTrack || pTrack->GetNumKeys() == 0 ||
       pTrack->GetFlags() & IAnimTrack::eAnimTrackFlags_Disabled)
  {
    continue;
  }
  ....
}
```

Автор этого фрагмента кода сначала воспользовался указателем _pTrack_, а на следующей же строке кода проверяет его валидность перед разыменованием\. Скорее всего, это потенциальное место некорректной работы программы\.

Предупреждений [V595](https://pvs-studio.ru/ru/docs/warnings/v595/) достаточно много, и в статью они не поместятся\. Очень часто такой код является настоящей ошибкой, но благодаря везению, работает корректно\.

[V571](https://pvs-studio.ru/ru/docs/warnings/v571/) Recurring check\. The 'if \(rLightInfo\.m\_pDynTexture\)' condition was already verified in line 69\. ObjMan\.cpp 70

```cpp
// Safe memory helpers
#define SAFE_RELEASE(p){ if (p) { (p)->Release(); (p) = NULL; } }

void CObjManager::UnloadVegetationModels(bool bDeleteAll)
{
  ....
  SVegetationSpriteLightInfo& rLightInfo = ....;
  if (rLightInfo.m_pDynTexture)
    SAFE_RELEASE(rLightInfo.m_pDynTexture);
  ....
}
```

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

Ещё одно место с лишним кодом:

* V571 Recurring check\. The 'if \(m\_pSectorGroups\)' condition was already verified in line 48\. PartitionGrid\.cpp 50

[V575](https://pvs-studio.ru/ru/docs/warnings/v575/) The 'memcpy' function doesn't copy the whole string\. Use 'strcpy / strcpy\_s' function to preserve terminal null\. SystemInit\.cpp 4045

```cpp
class CLvlRes_finalstep : public CLvlRes_base
{
  ....
  for (;; )
  {
    if (*p == '/' || *p == '\\' || *p == 0)
    {
      char cOldChar = *p;
      *p = 0; // create zero termination
      _finddata_t fd;

      bool bOk = FindFile(szFilePath, szFile, fd);

      if (bOk)
        assert(strlen(szFile) == strlen(fd.name));

      *p = cOldChar; // get back the old separator

      if (!bOk)
        return;

      memcpy((void*)szFile, fd.name, strlen(fd.name)); // <=

      if (*p == 0)
        break;

      ++p;
      szFile = p;
    }
    else ++p;
  }
  ....
}
```

Возможно, в этом коде допущена ошибка\. При копировании строки теряется последний [терминальный ноль](https://pvs-studio.ru/ru/blog/terms/0088/)\. В данном случае, необходимо копировать _strlen\(\) \+ 1_ символ, либо использовать специальные функции для копирования строк: _strcpy_ или _strcpy\_s_\.

## Проблемы с запятой

[V521](https://pvs-studio.ru/ru/docs/warnings/v521/) Such expressions using the ',' operator are dangerous\. Make sure the expression '\!sWords\[iWord\]\.empty\(\), iWord \+\+' is correct\. TacticalPointSystem\.cpp 3243

```cpp
bool CTacticalPointSystem::Parse(....) const
{
  string sInput(sSpec);
  const int MAXWORDS = 8;
  string sWords[MAXWORDS];

  int iC = 0, iWord = 0;
  for (; iWord < MAXWORDS; !sWords[iWord].empty(), iWord++) // <=
  {
    sWords[iWord] = sInput.Tokenize("_", iC);
  }
  ....
}
```

Обратите внимание на секцию цикла _for_ с счётчиками\. Что там делает логическое выражение? Скорее всего, его стоит перенести в другое место и написать так:

```cpp
for (; iWord < MAXWORDS && !sWords[iWord].empty(); iWord++) {...}
```

[V521](https://pvs-studio.ru/ru/docs/warnings/v521/) Such expressions using the ',' operator are dangerous\. Make sure the expression is correct\. HommingSwarmProjectile\.cpp 187

```cpp
void CHommingSwarmProjectile::HandleEvent(....)
{
  ....
  explodeDesc.normal = -pCollision->n,pCollision->vloc[0];
  ....
}
```

Ещё один странный фрагмент кода с оператором ','\.

## Подозрительные условия

[V571](https://pvs-studio.ru/ru/docs/warnings/v571/) Recurring check\. The 'if \(pos \=\= npos\)' condition was already verified in line 1530\. CryString\.h 1539

```cpp
//! Find last single character.
// \return -1 if not found, distance from beginning otherwise.
template<class T>
inline typename CryStringT<T>::....::rfind(....) const
{
  const_str str;
  if (pos == npos)
  {
    // find last single character
    str = _strrchr(m_str, ch);
    // return -1 if not found, distance from beginning otherwise
    return (str == NULL) ?
      (size_type) - 1 : (size_type)(str - m_str);
  }
  else
  {
    if (pos == npos)
    {
      pos = length();
    }
    if (pos > length())
    {
      return npos;
    }

    value_type tmp = m_str[pos + 1];
    m_str[pos + 1] = 0;
    str = _strrchr(m_str, ch);
    m_str[pos + 1] = tmp;
  }
  return (str == NULL) ?
   (size_type) - 1 : (size_type)(str - m_str);
}
```

Анализатор обнаружил повторную проверку переменной _pos_\. Из\-за такой ошибки часть кода никогда не выполняется\. Также в функции есть дублирующийся код, поэтому функцию стоит переписать получше\.

Такой код благополучно продублировали ещё в одном месте:

* V571 Recurring check\. The 'if \(pos \=\= npos\)' condition was already verified in line 1262\. CryFixedString\.h 1271

[V523](https://pvs-studio.ru/ru/docs/warnings/v523/) The 'then' statement is equivalent to the 'else' statement\. ScriptTable\.cpp 789

```cpp
bool CScriptTable::AddFunction(const SUserFunctionDesc& fd)
{
  ....
  char sFuncSignature[256];
  if (fd.sGlobalName[0] != 0)
    cry_sprintf(sFuncSignature, "%s.%s(%s)", fd.sGlobalName,
      fd.sFunctionName, fd.sFunctionParams);
  else
    cry_sprintf(sFuncSignature, "%s.%s(%s)", fd.sGlobalName,
      fd.sFunctionName, fd.sFunctionParams);
  ....
}
```

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

* V523 The 'then' statement is equivalent to the 'else' statement\. BudgetingSystem\.cpp 718
* V523 The 'then' statement is equivalent to the 'else' statement\. D3DShadows\.cpp 627
* V523 The 'then' statement is equivalent to the 'else' statement\. livingentity\.cpp 967

## Неопределённое поведение

[V610](https://pvs-studio.ru/ru/docs/warnings/v610/) Undefined behavior\. Check the shift operator '<<'\. The left operand '\-1' is negative\. physicalplaceholder\.h 25

```cpp
class CPhysicalEntity;
const int NO_GRID_REG = -1<<14;
const int GRID_REG_PENDING = NO_GRID_REG+1;
const int GRID_REG_LAST = NO_GRID_REG+2;
```

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

Вот ещё несколько сомнительных мест:

* V610 Undefined behavior\. Check the shift operator '<<'\. The left operand '\~\(TFragSeqStorage\(0\)\)' is negative\. UDPDatagramSocket\.cpp 757
* V610 Undefined behavior\. Check the shift operator '<<'\. The right operand \('cpu' \= \[0\.\.1023\]\) is greater than or equal to the length in bits of the promoted left operand\. CryThreadUtil\_posix\.h 115
* V610 Undefined behavior\. Check the shift operator '\>\>'\. The right operand is negative \('comp' \= \[\-1\.\.3\]\)\. ShaderComponents\.cpp 399
* V610 Undefined behavior\. Check the shift operator '<<'\. The left operand '\-1' is negative\. trimesh\.cpp 4126
* V610 Undefined behavior\. Check the shift operator '<<'\. The left operand '\-1' is negative\. trimesh\.cpp 4559
* V610 Unspecified behavior\. Check the shift operator '\>\>'\. The left operand '\-NRAYS' is negative\. trimesh\.cpp 4618
* V610 Undefined behavior\. Check the shift operator '<<'\. The left operand '\-1' is negative\. tetrlattice\.cpp 324
* V610 Undefined behavior\. Check the shift operator '<<'\. The left operand '\-1' is negative\. tetrlattice\.cpp 350
* V610 Undefined behavior\. Check the shift operator '<<'\. The left operand '\-1' is negative\. tetrlattice\.cpp 617
* V610 Undefined behavior\. Check the shift operator '<<'\. The left operand '\-1' is negative\. tetrlattice\.cpp 622

Другой тип неопределённого поведения связан с неоднократным изменением переменной между двумя точками следования:

[V567](https://pvs-studio.ru/ru/docs/warnings/v567/) Undefined behavior\. The 'm\_current' variable is modified while being used twice between sequence points\. OperatorQueue\.cpp 101

```cpp
boolCOperatorQueue::Prepare(....)
{
  ++m_current &= 1;
  m_ops[m_current].clear();
  return true;
}
```

К сожалению, такое место не единственное в коде:

* V567 Undefined behavior\. The 'm\_commandBufferIndex' variable is modified while being used twice between sequence points\. XConsole\.cpp 180
* V567 Undefined behavior\. The 'itail' variable is modified while being used twice between sequence points\. trimesh\.cpp 3119
* V567 Undefined behavior\. The 'ihead' variable is modified while being used twice between sequence points\. trimesh\.cpp 3126
* V567 Undefined behavior\. The 'ivtx' variable is modified while being used twice between sequence points\. boolean3d\.cpp 957
* V567 Undefined behavior\. The 'ivtx' variable is modified while being used twice between sequence points\. boolean3d\.cpp 965
* V567 Undefined behavior\. The 'ivtx' variable is modified while being used twice between sequence points\. boolean3d\.cpp 983
* V567 Undefined behavior\. The 'm\_iNextAnimIndex' variable is modified while being used twice between sequence points\. HitDeathReactionsDefs\.cpp 192

## Вопросы к разработчикам

В коде CryEngine V я нашёл интересный способ общения разработчиков через комментарии в коде\.

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

[V763](https://pvs-studio.ru/ru/docs/warnings/v763/) Parameter 'enable' is always rewritten in function body before being used\.

```cpp
void CNetContext::EnableBackgroundPassthrough(bool enable)
{
  SCOPED_GLOBAL_LOCK;
  // THIS IS A TEMPORARY HACK TO MAKE THE GAME PLAY NICELY,
  // ASK peter@crytek WHY IT'S STILL HERE
  enable = false;
  ....
}
```

Далее я решил с помощью поиска по содержимому файлов поикать подобные тексты и выписал несколько мест:

```cpp
....
// please ask me when you want to change [tetsuji]
....
// please ask me when you want to change [dejan]
....
//if there are problems with this function, ask Ivo
uint32 numAnims = 
  pCharacter->GetISkeletonAnim()->GetNumAnimsInFIFO(layer);
if (numAnims)
  return pH->EndFunction(true);
....
//ask Ivo for details
//if (pCharacter->GetCurAnimation() &&
//    pCharacter->GetCurAnimation()[0] != '\0')
//  return pH->EndFunction(pCharacter->GetCurAnimation());
....
/////////////////////////////////////////////////////////////////
// Strange, !do not remove... ask Timur for the meaning of this.
/////////////////////////////////////////////////////////////////
if (m_nStrangeRatio > 32767)
{
  gEnv->pScriptSystem->SetGCFrequency(-1); // lets get nasty.
}
/////////////////////////////////////////////////////////////////
// Strange, !do not remove... ask Timur for the meaning of this.
/////////////////////////////////////////////////////////////////
if (m_nStrangeRatio > 1000)
{
  if (m_pProcess && (m_pProcess->GetFlags() & PROC_3DENGINE))
    m_nStrangeRatio += cry_random(1, 11);
}
/////////////////////////////////////////////////////////////////
....
// tank specific:
// avoid steering input around 0.5 (ask Anton)
....
CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING,
  "....: Wrong edited item. Ask AlexL to fix this.");
....
// If this renders black ask McJohn what's wrong.
glGenerateMipmap(GL_TEXTURE_2D);
....
```

Ну и самый главный вопрос к разработчикам: почему они не пользуются специализированными инструментами для повышения качества кода? Я, конечно, имею в виду PVS\-Studio\. :\)

Еще раз хочу отметить, что в статье приведена только часть ошибок\. Я не досмотрел до конца даже предупреждения уровня High\. Так что проект ещё ждет тех, кто внимательно проверит его\. Я, к сожалению, не могу найти столько времени, меня [ждут](https://github.com/viva64/pvs-studio-check-list) десятки других проектов\.

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

За много лет разработки статического анализатора кода я пришёл к выводу, что в командах разработчиков с некоторого количества людей уже невозможно писать код без ошибок\. Нет, я не против Code Review, но посчитайте сами, сколько времени понадобится руководителю, чтобы сделать обзор кода 10 человек? А на следующий день? А большего числа людей? В таких условиях Code Review необходим при изменении только ключевых компонентов продукта\. В больших объёмах такой подход уже крайне неэффективен\. Автоматизированная проверка кода статическими анализаторами поможет значительно выправить ситуацию в лучшую сторону\. Это не замена существующим тестам, а совсем иной подход к контролю качества кода \(к слову, в тестах тоже находятся ошибки с помощью статического анализа кода\)\. Исправление ошибок во время написания кода почти ничего не стоит, в отличии от тех, которые находятся на этапе работы тестировщиков, а ошибки в выпущенном продукте несут уже колоссальные расходы\.

Скачать и попробовать PVS\-Studio можно по [этой](https://pvs-studio.ru/ru/pvs-studio/) ссылке\.

Обсудить варианты лицензирования, цены и варианты скидок можно написав нам в [поддержку](https://pvs-studio.ru/ru/about-feedback/)\.

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

Не огорчайте единорога своим кодом\.\.\.