﻿# Godot: к вопросу о регулярном использовании статических анализаторов кода

Аудитория наших читателей растёт, поэтому мы вновь и вновь пишем статьи, в которых объясняем, как правильно использовать методологию статического анализа кода\. Мы считаем очень важным объяснить, что инструменты статического анализа должны использоваться не эпизодически, а регулярно\. В очередной раз продемонстрируем это на практическом примере, перепроверив проект Godot\.

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

## Используйте анализаторы регулярно

Готовясь к выступлению на конференции разработчиков игр, я решил обзавестись новыми примерами интересных ошибок, которые способен выявить инструмент [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/)\. С этой целью были проверены несколько игровых движков, одним из которых был Godot\. Никаких особенно интересных ошибок для доклада я в нём не нашел, зато мне захотелось написать статью про обыкновенные ошибки\. Эти ошибки очень хорошо демонстрируют актуальность регулярного использования инструментов статического анализа кода\.

Следует отметить, что мы уже [проверяли](https://pvs-studio.ru/ru/blog/posts/cpp/0321/) этот проект в 2015 году, и автор поработал с выписанными нами ошибками\. [Здесь](https://github.com/godotengine/godot/commit/4804462ee06c1b3e2d1b50b857ce8693d3c0936d) можно увидеть соответствующий коммит\.

Прошло 3 года\. Проект изменился\. Анализатор PVS\-Studio тоже изменился, и в нём появились новые диагностики\. Поэтому нет ничего удивительного, что я легко и быстро смог выписать достаточное количество примеров ошибок для написания этой статьи\.

**Однако важно другое\.** При разработке Godot или любого другого проекта постоянно появляются и исправляются новые ошибки\. Ненайденные ошибки "оседают" в коде надолго, и затем многие из них могут быть выявлены при запуске статического анализа кода\. Из\-за этого иногда возникает ложное ощущение, что статические анализаторы находят только какие\-то малоинтересные ошибки в редко используемых участках кода\. Конечно, так оно и есть, если использовать анализатор неправильно и запускать его только время от времени, например, незадолго до выпуска релиза\.

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

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

Теперь перейдём к багам, которые так любят читатели наших публикаций\.

## Ошибки из\-за Copy\-Paste

Давайте посмотрим, что я заметил, изучая отчёт PVS\-Studio\. Начну с моей любимой диагностики V501, которая находит [ошибки практически в каждом проекте](https://pvs-studio.ru/ru/blog/examples/v501/), который мы проверяем :\)\.

**Ошибка N1**

```cpp
virtual bool can_export(....)
{
  ....
  if (!exists_export_template("uwp_" + platform_infix + "_debug.zip", &err) ||
      !exists_export_template("uwp_" + platform_infix + "_debug.zip", &err)) {
    valid = false;
    r_missing_templates = true;
  }
  ....
}
```

Предупреждение PVS\-Studio: V501 CWE\-570 There are identical sub\-expressions '\!exists\_export\_template\("uwp\_" \+ platform\_infix \+ "\_debug\.zip", & err\)' to the left and to the right of the '\|\|' operator\. export\.cpp 1135

Классическая Copy\-Paste ошибка\. Вызов функции скопирован, но не отредактирован\. Имя второго обрабатываемого файла должно заканчиваться на "\_release\.zip"\.

**Ошибки N2, N3**

```cpp
static String dump_node_code(SL::Node *p_node, int p_level) {
  ....
  if (bnode->statements[i]->type == SL::Node::TYPE_CONTROL_FLOW ||
      bnode->statements[i]->type == SL::Node::TYPE_CONTROL_FLOW) {
    code += scode; //use directly
  } else {
    code += _mktab(p_level) + scode + ";\n";
  }
  ....
}
```

Предупреждение PVS\-Studio: V501 CWE\-570 There are identical sub\-expressions 'bnode\-\>statements\[i\]\-\>type \=\= SL::Node::TYPE\_CONTROL\_FLOW' to the left and to the right of the '\|\|' operator\. test\_shader\_lang\.cpp 183

```cpp
void EditorSpinSlider::_notification(int p_what) {
  if (p_what == MainLoop::NOTIFICATION_WM_FOCUS_OUT ||
      p_what == MainLoop::NOTIFICATION_WM_FOCUS_OUT) {
    if (grabbing_spinner) {
      Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
      grabbing_spinner = false;
      grabbing_spinner_attempt = false;
    }
  }
  ....
}
```

Предупреждение PVS\-Studio: V501 CWE\-570 There are identical sub\-expressions 'p\_what \=\= MainLoop::NOTIFICATION\_WM\_FOCUS\_OUT' to the left and to the right of the '\|\|' operator\. editor\_spin\_slider\.cpp 157

Думаю, ошибки хорошо видны и не требуют каких\-либо пояснений\. Точно такой же классический Copy\-Paste, как и в первом случае\.

**Ошибка N4**

```cpp
String SoftBody::get_configuration_warning() const {
  ....
  Transform t = get_transform();
  if ((ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 ||
       ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 ||
       ABS(t.basis.get_axis(0).length() - 1.0) > 0.05)) {
    if (!warning.empty())
  ....
}
```

Предупреждение PVS\-Studio: V501 CWE\-570 There are identical sub\-expressions to the left and to the right of the '\|\|' operator\. soft\_body\.cpp 399

Здесь первая строка была скопирована дважды\. Но номер оси координат был изменён только во второй строке\. А третью строчку отредактировать забыли\. Это не что иное, как "[Эффект последней строки](https://pvs-studio.ru/ru/blog/posts/cpp/0260/)"\.

**Примечание\.** На данный момент, помимо "Эффекта последней строки", мною сделаны следующие интересные наблюдения: "[Самая опасная функция в мире С/С\+\+](https://pvs-studio.ru/ru/blog/posts/cpp/0360/)", "[Зло живёт в функциях сравнения](https://pvs-studio.ru/ru/blog/posts/cpp/0509/)"\. И сейчас я сделаю анонс новой статьи, написанием которой я планирую заняться в ближайшее время\. Рабочее название "0, 1, 2"\. Должно получиться интересно и поучительно\. Приглашаю подписываться на один из каналов, чтобы не пропустить: [twitter](https://twitter.com/pvsstudio_rus/), [vk\.com](https://vk.com/pvsstudio_rus), [telegram](https://t.me/pvsstudio_rus) и "олдскульный" [rss](http://feeds.feedburner.com/viva64-blog-ru)\.

**Ошибка N5**

```cpp
void ScrollContainer::_notification(int p_what) {
  ....
  if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this)
    size.y -= h_scroll->get_minimum_size().y;

  if (v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this)
    size.x -= h_scroll->get_minimum_size().x;
  ....
}
```

Предупреждение PVS\-Studio: V778 CWE\-682 Two similar code fragments were found\. Perhaps, this is a typo and 'v\_scroll' variable should be used instead of 'h\_scroll'\. scroll\_container\.cpp 249

По поводу этого фрагмента кода у меня нет полной уверенности, что здесь есть ошибка\. Однако я согласен с анализатором, что второй блок выглядит очень подозрительно\. Скорее всего, код писался с помощью Copy\-Paste и во втором блоке текста забыли заменить _h\_scroll_ на _v\_scroll_\.

Вероятно, код должен быть таким:

```cpp
if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this)
  size.y -= h_scroll->get_minimum_size().y;

if (v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this)
  size.x -= v_scroll->get_minimum_size().x;
```

**Ошибка N6**

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

```cpp
void ShaderGLES2::bind_uniforms() {
  ....
  const Map<uint32_t, Variant>::Element *E = uniform_defaults.front();

  while (E) {
    int idx = E->key();
    int location = version->uniform_location[idx];

    if (location < 0) {
      E = E->next();
      continue;
    }

    Variant v;
    v = E->value();
    _set_uniform_variant(location, v);
    E = E->next();
  }

  const Map<uint32_t, CameraMatrix>::Element *C = uniform_cameras.front();

  while (C) {
    int idx = E->key();                                  // <=
    int location = version->uniform_location[idx];

    if (location < 0) {
      C = C->next();
      continue;
    }

    glUniformMatrix4fv(location, 1, GL_FALSE, &(C->get().matrix[0][0]));
    C = C->next();
  }

  uniforms_dirty = false;
}
```

Предупреждение PVS\-Studio: V522 CWE\-476 Dereferencing of the null pointer 'E' might take place\. shader\_gles2\.cpp 102

Ошибка выявлена косвенным образом\. Благодаря анализу [потока данных](https://pvs-studio.ru/ru/blog/posts/cpp/0592/) PVS\-Studio выявил, что указатель _E_ может быть нулевым в момент его разыменования\.

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

## Опечатки

**Ошибка N7**

Программистам, пишущим не на языке C или C\+\+, сложно представить, что можно допустить опечатку, написав вместо звёздочки '\*' запятую ',', и при этом код будет компилироваться\. Однако это так\.

```cpp
LRESULT OS_Windows::WndProc(....) {
  ....
  BITMAPINFO bmi;
  ZeroMemory(&bmi, sizeof(BITMAPINFO));
  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmi.bmiHeader.biWidth = dib_size.x;
  bmi.bmiHeader.biHeight = dib_size.y;
  bmi.bmiHeader.biPlanes = 1;
  bmi.bmiHeader.biBitCount = 32;
  bmi.bmiHeader.biCompression = BI_RGB;
  bmi.bmiHeader.biSizeImage = dib_size.x, dib_size.y * 4;
  ....
}
```

Предупреждение PVS\-Studio: V521 CWE\-480 Such expressions using the ',' operator are dangerous\. Make sure the expression is correct\. os\_windows\.cpp 776

Переменной _bmi\.bmiHeader\.biSizeImage_ присваивается значение переменной _dib\_size\.x_\. Далее выполняется оператор запятая ',', чей [приоритет](https://pvs-studio.ru/ru/blog/terms/0064/) ниже, чем у оператора '\='\. Результат же выражения _dib\_size\.y \* 4_ никак не используется\.

Вместо запятой в выражении должен использоваться оператор умножения '\*'\. Во\-первых, такое выражение будет иметь смысл\. Во\-вторых, ниже есть похожий, но уже корректный вариант инициализации такой же переменной:

```cpp
bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4;
```

**Ошибки N8, N9**

```cpp
void Variant::set(....) {
  ....
  int idx = p_index;
  if (idx < 0)
    idx += 4;
  if (idx >= 0 || idx < 4) {
    Color *v = reinterpret_cast<Color *>(_data._mem);
    (*v)[idx] = p_value;
    valid = true;
  }
  ....
}
```

Предупреждение PVS\-Studio: V547 CWE\-571 Expression 'idx \>\= 0 \|\| idx < 4' is always true\. variant\_op\.cpp 2152

Любой индекс будет считаться корректным\. Чтобы исправить ошибку, надо заменить оператор _\|\|_ на _&&_:

```cpp
if (idx >= 0 && idx < 4) {
```

Эта логическая ошибка возникла, скорее всего, по невнимательности, поэтому я склонен отнести её к опечаткам\.

Точно такую же ошибку можно наблюдать в этом же файле чуть ниже\. Виной размножения ошибки, по всей видимости, является Copy\-Paste\. 

Размноженная ошибка: V547 CWE\-571 Expression 'idx \>\= 0 \|\| idx < 4' is always true\. variant\_op\.cpp 2527

**Ошибка N10**

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

Пример ошибки, от которой так и хочется воскликнуть: WTF?\!

```cpp
void AnimationNodeBlendSpace1D::add_blend_point(
  const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index)
{
  ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS);
  ERR_FAIL_COND(p_node.is_null());

  ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used);

  if (p_at_index == -1 || p_at_index == blend_points_used) {
    p_at_index = blend_points_used;
  } else {
    for (int i = blend_points_used - 1; i > p_at_index; i++) {
      blend_points[i] = blend_points[i - 1];
    }
  }
  ....
}
```

Предупреждение PVS\-Studio: V621 CWE\-835 Consider inspecting the 'for' operator\. It's possible that the loop will be executed incorrectly or won't be executed at all\. animation\_blend\_space\_1d\.cpp 113

Обратите внимание на условие остановки цикла: _i \> p\_at\_index_\. Оно всегда истинно, так как переменная _i_ инициализируется значением _blend\_points\_used \- 1_\. При этом из двух более ранних проверок следует, что _blend\_points\_used  \> p\_at\_index_\.

Условие может стать ложным, только когда возникнет переполнение знаковой переменной _i_, что является неопределённым поведением\. Более того, до переполнения не дойдёт, так как намного раньше произойдёт выход за границу массива\.

**Ошибка N11**

Ещё один не менее яркий случай опечатки в условии цикла\.

```cpp
void AnimationNodeStateMachineEditor::_state_machine_pos_draw() {
  ....
  int idx = -1;
  for (int i = 0; node_rects.size(); i++) {
    if (node_rects[i].node_name == playback->get_current_node()) {
      idx = i;
      break;
    }
  }
  ....
}
```

Предупреждение PVS\-Studio: V693 CWE\-835 Consider inspecting conditional expression of the loop\. It is possible that 'i < X\.size\(\)' should be used instead of 'X\.size\(\)'\. animation\_state\_machine\_editor\.cpp 852

Может произойти выход за границу массива, так как значение _i_ увеличивается бесконтрольно\. Безопасный код:

```cpp
for (int i = 0; i < node_rects.size(); i++) {
```

**Ошибка N12**

```cpp
GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(
  const GDScriptParser::DataType &p_datatype) const
{
  ....
  switch (p_datatype.kind) {
     ....
    case GDScriptParser::DataType::NATIVE: {
      result.kind = GDScriptDataType::NATIVE;
      result.native_type = p_datatype.native_type;
    } break;
    case GDScriptParser::DataType::SCRIPT: {
      result.kind = GDScriptDataType::SCRIPT;
      result.script_type = p_datatype.script_type;
      result.native_type = result.script_type->get_instance_base_type();
    }
    case GDScriptParser::DataType::GDSCRIPT: {
      result.kind = GDScriptDataType::GDSCRIPT;
      result.script_type = p_datatype.script_type;
      result.native_type = result.script_type->get_instance_base_type();
    } break;
  ....
}
```

Предупреждение PVS\-Studio: V796 CWE\-484 It is possible that 'break' statement is missing in switch statement\. gdscript\_compiler\.cpp 135

Случайно забыли написать оператор _break_\. Поэтому при попадании в _case GDScriptParser::DataType::SCRIPT_ переменным будут присвоены значения, как будто это _case GDScriptParser::DataType::GDSCRIPT_\.

**Ошибка N13**

Следующую ошибку можно классифицировать как Copy\-Paste\. Однако я не уверен, что столь короткая строка была скопирована\. Так что будем считать это простой опечаткой при наборе текста\.

```cpp
void CPUParticles::_particles_process(float p_delta) {
  ....
  if (flags[FLAG_DISABLE_Z]) {
    p.velocity.z = 0.0;
    p.velocity.z = 0.0;
  }
  ....
}
```

Предупреждение PVS\-Studio: V519 CWE\-563 The 'p\.velocity\.z' variable is assigned values twice successively\. Perhaps this is a mistake\. Check lines: 664, 665\. cpu\_particles\.cpp 665

Два раза происходит присваивание одной и той же переменной\. Ниже можно увидеть вот такой фрагмент кода:

```cpp
if (flags[FLAG_DISABLE_Z]) {
  p.velocity.z = 0.0;
  p.transform.origin.z = 0.0;
}
```

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

**Ошибка N14**

```cpp
bool AtlasTexture::is_pixel_opaque(int p_x, int p_y) const {
  if (atlas.is_valid()) {
    return atlas->is_pixel_opaque(
      p_x + region.position.x + margin.position.x,
      p_x + region.position.y + margin.position.y
    );
  }
  return true;
}
```

Предупреждение PVS\-Studio: V751 Parameter 'p\_y' is not used inside function body\. texture\.cpp 1085

Фрагмент из описания диагностики [V751](https://pvs-studio.ru/ru/docs/warnings/v751/):

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

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

```cpp
return atlas->is_pixel_opaque(
  p_x + region.position.x + margin.position.x,
  p_y + region.position.y + margin.position.y
);
```

Кстати, в исходном коде вызов функции написан в одну строчку\. Из\-за этого ошибку сложнее заметить\. Если бы автор кода написал фактические аргументы в столбик, как я сделал это в статье, то ошибка сразу бросилась бы в глаза\. Не забывайте, что табличное форматирование весьма полезно и позволяет избежать множества опечаток\. См\. главу "Выравнивайте однотипный код "таблицей" в статье "[Главный вопрос программирования, рефакторинга и всего такого](https://pvs-studio.ru/ru/blog/posts/cpp/0391/)"\.

**Ошибка N15**

```cpp
bool SpriteFramesEditor::can_drop_data_fw(....) const {
  ....
  Vector<String> files = d["files"];

  if (files.size() == 0)
    return false;

  for (int i = 0; i < files.size(); i++) {
    String file = files[0];
    String ftype = EditorFileSystem::get_singleton()->get_file_type(file);

    if (!ClassDB::is_parent_class(ftype, "Texture")) {
      return false;
    }
  }
  ....
}
```

Предупреждение PVS\-Studio: V767 Suspicious access to element of 'files' array by a constant index inside a loop\. sprite\_frames\_editor\_plugin\.cpp 602

В цикле обрабатывается один и тот же файл на всех итерациях цикла\. Опечатка здесь:

```cpp
String file = files[0];
```

Должно быть:

```cpp
String file = files[i];
```

## Прочие ошибки

**Ошибка N16**

```cpp
CSGBrush *CSGBox::_build_brush() {
  ....
  for (int i = 0; i < 6; i++) {
    ....
    if (i < 3)
      face_points[j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1);
    else
      face_points[3 - j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1);
    ....
  }
  ....
}
```

Анализатор PVS\-Studio выдаёт сразу два срабатывания на этот код:

* V547 CWE\-570 Expression 'i \>\= 3' is always false\. csg\_shape\.cpp 939
* V547 CWE\-571 Expression 'i \>\= 3' is always true\. csg\_shape\.cpp 941

И действительно, вот этот тернарный оператор в обоих выражениях выглядит очень странно:

```cpp
i >= 3 ? -1 : 1
```

В одном случае условие всегда истинно, а в другом \- ложно\. Затрудняюсь сказать, как правильно должен выглядеть этот код\. Возможно, он просто избыточен и можно написать так:

```cpp
for (int i = 0; i < 6; i++) {
  ....
  if (i < 3)
    face_points[j][(i + k) % 3] = v[k];
  else
    face_points[3 - j][(i + k) % 3] = -v[k];
  ....
}
```

Я могу быть неправ, и код надо исправить совсем другим способом\.

**Ошибка N17**

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

```cpp
bool CanvasItemEditor::_get_bone_shape(....) {
  ....
  Node2D *from_node = Object::cast_to<Node2D>(
                        ObjectDB::get_instance(bone->key().from));
  ....
  if (!from_node->is_inside_tree())
    return false; //may have been removed
  if (!from_node)
    return false;
  ....
}
```

Предупреждение PVS\-Studio: V595 CWE\-476 The 'from\_node' pointer was utilized before it was verified against nullptr\. Check lines: 565, 567\. canvas\_item\_editor\_plugin\.cpp 565

Указатель _from\_node _вначале разыменовывается для вызова функции _is\_inside\_tree_ и только затем проверятся на равенство _nullptr_\. Проверки следует поменять местами:

```cpp
if (!from_node)
  return false;
if (!from_node->is_inside_tree())
  return false; //may have been removed
```

**Ошибка N18**

```cpp
enum JoystickList {
  ....
  JOY_AXIS_MAX = 10,
  ....
};

static const char *_axes[] = {
  "Left Stick X",
  "Left Stick Y",
  "Right Stick X",
  "Right Stick Y",
  "",
  "",
  "L2",
  "R2"
};

int InputDefault::get_joy_axis_index_from_string(String p_axis) {
  for (int i = 0; i < JOY_AXIS_MAX; i++) {
    if (p_axis == _axes[i]) {
      return i;
    }
  }
  ERR_FAIL_V(-1);
}
```

Предупреждение PVS\-Studio: V557 CWE\-125 Array overrun is possible\. The value of 'i' index could reach 9\. input\_default\.cpp 1119

Массив _\_axes_ состоит из восьми элементов\. При этом константа _JOY\_AXIS\_MAX_, задающая количество итераций цикла, равна 10\. Получается, что в цикле происходит выход за границу массива\.

**Ошибка N19**

И последняя очень странная функция, которая, видимо, предназначена для тестирования чего\-то\. Функция длинная, поэтому я приведу её в виде картинки \(нажмите на картинку для увеличения\)\.

![0594_Godot_ru/image4.png](https://import.viva64.com/docx/blog/0594_Godot_ru/image4.png)

Предупреждение PVS\-Studio: V779 CWE\-561 Unreachable code detected\. It is possible that an error is present\. test\_math\.cpp 457

В функции встречается несколько безусловных операторов _return_\. На картинке я отметил их красными овалами\. Такое впечатление, что в эту функцию собрали несколько разных юнит\-тестов, но забыли удалить лишние _return NULL_\. В результате функция не проверяет то, что должна проверять\. Почти всё тело функции состоит из недостижимого кода\.

Возможно, конечно, это какая\-то хитрая задумка\. Но мне кажется, это получилось случайно и код следует исправить\.

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

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

В статье описаны ошибки, которых бы не существовало, если бы код регулярно анализировался с помощью PVS\-Studio\. Однако, что ещё более важно, используя анализ регулярно, можно было бы сразу найти и устранить множество других ошибок\. Более развернуто эту мысль описал мой коллега в заметке: "[Философия статического анализа кода: у нас 100 программистов, анализатор нашел мало ошибок, он бесполезен?](https://pvs-studio.ru/ru/blog/posts/0534/)"\. Очень рекомендую потратить 10 минут на прочтение этой короткой, но очень важной статьи\.

Спасибо за внимание\. Приглашаю всех скачать и попробовать статический анализ PVS\-Studio для проверки ваших собственных проектов\.